diff --git a/README.md b/README.md index 1b56e87..7995393 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ a plethora of other features that I couldn't find in any other pager. ## Built-in Keybindings * Custom keybindings will be added in the future. -### Viewer Mode +### Normal Mode This is the default mode. You can scroll through files. | Keybinding | Description | @@ -76,13 +76,14 @@ In this mode, you can enter commands to interact with the pager. | `:findl ` | Create a new filter searching for the literal. | | `:` | Go to the specific line number. | -### Selection Mode +### Visual Mode In this mode, you can select lines to bookmark. -| Keybinding | Description | -| ------------------- | -------------------------------- | -| `Up` and `Down` | Move the select cursor. | -| `Space` and `Enter` | Toggle bookmark at current line. | +| Keybinding | Description | +| ------------------------- | ------------------------------------------------ | +| `Up` and `Down` | Move the select cursor. | +| `Shift` + `Up` and `Down` | Expand the select cursor into a selection range. | +| `Space` and `Enter` | Toggle bookmark at current line. | ### Filter Mode In this mode, you can toggle filters from bookmarks or searches to omit or include certain lines in the viewer. @@ -98,9 +99,9 @@ In this mode, you can toggle filters from bookmarks or searches to omit or inclu ### Mode-Independent | Keybinding | Description | | --------------- | ----------------------------------------- | -| `Esc` | Exit selection mode (enter viewer mode). | +| `Esc` | Exit selection mode (enter normal mode). | | `:` | Enter command mode. | -| `i` | Enter selection mode. | +| `v` | Enter visual mode. | | `Tab` | Enter filter mode. | | `` ` `` and `~` | Switch selected view. | | `1` .. `9` | Switch selected view to the `n`th buffer. | diff --git a/crates/cli/app/actions.rs b/crates/cli/app/actions.rs index 4abea63..9a7aa0f 100644 --- a/crates/cli/app/actions.rs +++ b/crates/cli/app/actions.rs @@ -6,7 +6,8 @@ pub enum Action { Exit, SwitchMode(InputMode), Command(CommandAction), - Viewer(ViewerAction), + Normal(NormalAction), + Visual(VisualAction), Filter(FilterAction), } @@ -17,7 +18,7 @@ pub enum Delta { Boundary, } -pub enum ViewerAction { +pub enum NormalAction { PanVertical { direction: Direction, delta: Delta, @@ -29,6 +30,13 @@ pub enum ViewerAction { target_view: Option, }, FollowOutput, + SwitchActive(Direction), + SwitchActiveIndex { + target_view: usize, + }, +} + +pub enum VisualAction { Move { direction: Direction, select: bool, @@ -39,9 +47,6 @@ pub enum ViewerAction { target_view: usize, line_number: usize, }, - SwitchActiveIndex { - target_view: usize, - }, } pub enum FilterAction { diff --git a/crates/cli/app/keybinding.rs b/crates/cli/app/keybinding.rs index b674d15..8b4a30b 100644 --- a/crates/cli/app/keybinding.rs +++ b/crates/cli/app/keybinding.rs @@ -1,5 +1,5 @@ use super::{ - actions::{Action, CommandAction, CommandJump, Delta, FilterAction, ViewerAction}, + actions::{Action, CommandAction, CommandJump, Delta, FilterAction, NormalAction, VisualAction}, InputMode, }; use crate::direction::Direction; @@ -31,10 +31,10 @@ impl Keybinding { fn mode_dependent_bind(input_mode: InputMode, event: &mut Event) -> Option { match input_mode { - InputMode::Viewer => match event { + InputMode::Normal => match event { Event::Key(key) => match key.code { KeyCode::Up | KeyCode::Down => { - Some(Action::Viewer(ViewerAction::PanVertical { + Some(Action::Normal(NormalAction::PanVertical { direction: Direction::back_if(key.code == KeyCode::Up), delta: if key.modifiers.contains(KeyModifiers::SHIFT) { Delta::HalfPage @@ -45,7 +45,7 @@ impl Keybinding { })) } KeyCode::Left | KeyCode::Right => { - Some(Action::Viewer(ViewerAction::PanHorizontal { + Some(Action::Normal(NormalAction::PanHorizontal { direction: Direction::back_if(key.code == KeyCode::Left), delta: if key.modifiers.contains(KeyModifiers::SHIFT) { Delta::HalfPage @@ -56,7 +56,7 @@ impl Keybinding { })) } KeyCode::Home | KeyCode::End | KeyCode::Char('g') => { - Some(Action::Viewer(ViewerAction::PanVertical { + Some(Action::Normal(NormalAction::PanVertical { direction: Direction::back_if(matches!( key.code, KeyCode::Home | KeyCode::Char('g') @@ -65,16 +65,16 @@ impl Keybinding { target_view: None, })) } - KeyCode::Char('G') => Some(Action::Viewer(ViewerAction::FollowOutput)), + KeyCode::Char('G') => Some(Action::Normal(NormalAction::FollowOutput)), KeyCode::PageUp | KeyCode::PageDown | KeyCode::Char(' ') => { - Some(Action::Viewer(ViewerAction::PanVertical { + Some(Action::Normal(NormalAction::PanVertical { direction: Direction::back_if(key.code == KeyCode::PageUp), delta: Delta::Page, target_view: None, })) } KeyCode::Char(c @ ('u' | 'd')) => { - Some(Action::Viewer(ViewerAction::PanVertical { + Some(Action::Normal(NormalAction::PanVertical { direction: Direction::back_if(c == 'u'), delta: Delta::HalfPage, target_view: None, @@ -116,9 +116,9 @@ impl Keybinding { }, _ => None, }, - InputMode::Select => match event { + InputMode::Visual => match event { Event::Key(key) => match key.code { - KeyCode::Up | KeyCode::Down => Some(Action::Viewer(ViewerAction::Move { + KeyCode::Up | KeyCode::Down => Some(Action::Visual(VisualAction::Move { direction: Direction::back_if(key.code == KeyCode::Up), select: key.modifiers.contains(KeyModifiers::SHIFT), delta: if key @@ -130,20 +130,20 @@ impl Keybinding { Delta::Number(1) }, })), - KeyCode::Home | KeyCode::End => Some(Action::Viewer(ViewerAction::Move { + KeyCode::Home | KeyCode::End => Some(Action::Visual(VisualAction::Move { direction: Direction::back_if(key.code == KeyCode::Home), select: key.modifiers.contains(KeyModifiers::SHIFT), delta: Delta::Boundary, })), KeyCode::PageUp | KeyCode::PageDown => { - Some(Action::Viewer(ViewerAction::Move { + Some(Action::Visual(VisualAction::Move { direction: Direction::back_if(key.code == KeyCode::PageUp), select: key.modifiers.contains(KeyModifiers::SHIFT), delta: Delta::Page, })) } KeyCode::Char(' ') | KeyCode::Enter => { - Some(Action::Viewer(ViewerAction::ToggleSelectedLine)) + Some(Action::Visual(VisualAction::ToggleSelectedLine)) } _ => None, }, @@ -202,13 +202,13 @@ impl Keybinding { Event::Key(key) => match key.code { KeyCode::Char(':') => Some(Action::SwitchMode(InputMode::Command)), KeyCode::Tab => Some(Action::SwitchMode(InputMode::Filter)), - KeyCode::Char(c @ ('`' | '~')) => Some(Action::Viewer(ViewerAction::SwitchActive( + KeyCode::Char(c @ ('`' | '~')) => Some(Action::Normal(NormalAction::SwitchActive( Direction::back_if(c == '~'), ))), - KeyCode::Esc => Some(Action::SwitchMode(InputMode::Viewer)), - KeyCode::Char('i') => Some(Action::SwitchMode(InputMode::Select)), + KeyCode::Esc => Some(Action::SwitchMode(InputMode::Normal)), + KeyCode::Char('i') => Some(Action::SwitchMode(InputMode::Visual)), KeyCode::Char(c @ '1'..='9') => { - Some(Action::Viewer(ViewerAction::SwitchActiveIndex { + Some(Action::Normal(NormalAction::SwitchActiveIndex { target_view: c as usize - '1' as usize, })) } diff --git a/crates/cli/app/mod.rs b/crates/cli/app/mod.rs index c50f822..fa29476 100644 --- a/crates/cli/app/mod.rs +++ b/crates/cli/app/mod.rs @@ -4,7 +4,7 @@ mod mouse; mod widgets; use self::{ - actions::{Action, CommandAction, Delta, ViewerAction}, + actions::{Action, CommandAction, Delta, NormalAction, VisualAction}, keybinding::Keybinding, mouse::MouseHandler, widgets::{CommandWidget, MultiplexerWidget}, @@ -35,8 +35,8 @@ pub type Terminal<'a> = ratatui::Terminal>; #[derive(PartialEq, Clone, Copy)] pub enum InputMode { Command, - Viewer, - Select, + Normal, + Visual, Filter, } @@ -51,7 +51,7 @@ pub struct App { impl App { pub fn new() -> Self { Self { - mode: InputMode::Viewer, + mode: InputMode::Normal, command: CommandApp::new(), mux: MultiplexerApp::new(), status: StatusApp::new(), @@ -143,14 +143,14 @@ impl App { self.command.submit(); self.mode = new_mode; - if new_mode == InputMode::Select { + if new_mode == InputMode::Visual { if let Some(viewer) = self.mux.active_viewer_mut() { viewer.move_selected_into_view() } } } - Action::Viewer(action) => match action { - ViewerAction::PanVertical { + Action::Normal(action) => match action { + NormalAction::PanVertical { direction, delta, target_view, @@ -171,7 +171,7 @@ impl App { viewer.viewport_mut().pan_vertical(direction, delta); } } - ViewerAction::PanHorizontal { + NormalAction::PanHorizontal { direction, delta, target_view, @@ -192,15 +192,18 @@ impl App { viewer.viewport_mut().pan_horizontal(direction, delta); } } - ViewerAction::FollowOutput => { + NormalAction::FollowOutput => { if let Some(viewer) = self.mux.active_viewer_mut() { viewer.viewport_mut().follow_output(); } } - ViewerAction::SwitchActiveIndex { target_view } => { + NormalAction::SwitchActiveIndex { target_view } => { self.mux.move_active_index(target_view) } - ViewerAction::Move { + NormalAction::SwitchActive(direction) => self.mux.move_active(direction), + }, + Action::Visual(action) => match action { + VisualAction::Move { direction, select, delta, @@ -215,13 +218,13 @@ impl App { viewer.move_select(direction, select, delta) } } - ViewerAction::ToggleSelectedLine => { + VisualAction::ToggleSelectedLine => { if let Some(viewer) = self.mux.active_viewer_mut() { viewer.toggle_select_bookmarks(); viewer.filterer.compute_composite(); } } - ViewerAction::ToggleLine { + VisualAction::ToggleLine { target_view, line_number, } => { @@ -295,7 +298,7 @@ impl App { CommandAction::Paste(s) => self.command.enter_str(&s), CommandAction::Backspace => { if !self.command.delete() { - self.mode = InputMode::Viewer; + self.mode = InputMode::Normal; } } CommandAction::Submit => { @@ -303,7 +306,7 @@ impl App { if !self.process_command(command) { return false; } - self.mode = InputMode::Viewer; + self.mode = InputMode::Normal; } }, }; diff --git a/crates/cli/app/widgets.rs b/crates/cli/app/widgets.rs index 2a04a92..24bcf2b 100644 --- a/crates/cli/app/widgets.rs +++ b/crates/cli/app/widgets.rs @@ -1,5 +1,5 @@ use super::{ - actions::{Action, Delta, FilterAction, ViewerAction}, + actions::{Action, Delta, FilterAction, NormalAction}, mouse::MouseHandler, InputMode, }; @@ -13,7 +13,7 @@ use crate::{ status::StatusApp, viewer::{Instance, LineData, LineType}, }, - direction::Direction, + direction::Direction, app::actions::VisualAction, }; use crossterm::event::MouseEventKind; use ratatui::{prelude::*, widgets::*}; @@ -37,8 +37,8 @@ impl<'a> Widget for StatusWidget<'a> { let accent_color = match self.input_mode { InputMode::Command => colors::COMMAND_ACCENT, - InputMode::Viewer => colors::VIEWER_ACCENT, - InputMode::Select => colors::SELECT_ACCENT, + InputMode::Normal => colors::VIEWER_ACCENT, + InputMode::Visual => colors::SELECT_ACCENT, InputMode::Filter => colors::FILTER_ACCENT, }; @@ -47,8 +47,8 @@ impl<'a> Widget for StatusWidget<'a> { v.push( Span::from(match self.input_mode { InputMode::Command => " COMMAND ", - InputMode::Viewer => " VIEWER ", - InputMode::Select => " SELECT ", + InputMode::Normal => " NORMAL ", + InputMode::Visual => " VISUAL ", InputMode::Filter => " FILTER ", }) .fg(colors::WHITE) @@ -184,7 +184,7 @@ impl ViewerWidget<'_> { handle.on_mouse(area, |event| match event.kind { MouseEventKind::ScrollUp | MouseEventKind::ScrollDown => { - Some(Action::Viewer(ViewerAction::PanVertical { + Some(Action::Normal(NormalAction::PanVertical { direction: Direction::back_if(event.kind == MouseEventKind::ScrollUp), delta: Delta::Number(5), target_view: Some(self.view_index), @@ -352,7 +352,7 @@ impl ViewerLineWidget<'_> { if let Some(line) = self.line { handle.on_mouse(area, |event| match event.kind { - MouseEventKind::Down(_) => Some(Action::Viewer(ViewerAction::ToggleLine { + MouseEventKind::Down(_) => Some(Action::Visual(VisualAction::ToggleLine { line_number: line.line_number, target_view: self.view_index, })), @@ -391,7 +391,7 @@ impl TabWidget<'_> { .render(area, buf); handle.on_mouse(area, |event| match event.kind { - MouseEventKind::Down(_) => Some(Action::Viewer(ViewerAction::SwitchActiveIndex { + MouseEventKind::Down(_) => Some(Action::Normal(NormalAction::SwitchActiveIndex { target_view: self.view_index, })), _ => None, diff --git a/crates/cli/components/mux.rs b/crates/cli/components/mux.rs index 648d62e..0d0847c 100644 --- a/crates/cli/components/mux.rs +++ b/crates/cli/components/mux.rs @@ -1,3 +1,4 @@ +use crate::direction::Direction; use super::viewer::Instance; #[derive(Clone, Copy)] @@ -60,6 +61,13 @@ impl MultiplexerApp { self.active } + pub fn move_active(&mut self, direction: Direction) { + self.move_active_index(match direction { + Direction::Back => self.active.saturating_sub(1), + Direction::Next => self.active.saturating_add(1), + }) + } + pub fn move_active_index(&mut self, index: usize) { self.active = index.min(self.views.len().saturating_sub(1)); }