From 35785e2ac15a70d5b87cec5514f5c7993e428be6 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Wed, 30 Oct 2024 00:49:20 +0900 Subject: [PATCH] Add vim keymap dialogs - Ctrl+h to open --- core/src/event.rs | 1 + core/src/state/notebook.rs | 37 ++--- core/src/state/notebook/inner_state.rs | 2 +- .../inner_state/editing_normal_mode.rs | 8 +- .../inner_state/editing_visual_mode.rs | 17 +- core/src/transition.rs | 9 + tui/src/action.rs | 3 +- tui/src/context.rs | 8 +- tui/src/transitions.rs | 3 + tui/src/views/dialog.rs | 6 +- tui/src/views/dialog/vim_keymap.rs | 155 ++++++++++++++++++ 11 files changed, 210 insertions(+), 39 deletions(-) create mode 100644 tui/src/views/dialog/vim_keymap.rs diff --git a/core/src/event.rs b/core/src/event.rs index 562c552..77e2a57 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -96,6 +96,7 @@ pub enum KeyEvent { CapI, CapO, CapS, + CtrlH, CtrlR, DollarSign, Caret, diff --git a/core/src/state/notebook.rs b/core/src/state/notebook.rs index 0e75e5f..a481933 100644 --- a/core/src/state/notebook.rs +++ b/core/src/state/notebook.rs @@ -186,8 +186,7 @@ impl NotebookState { vec![ "[o] Open note".to_owned(), "[h] Close parent".to_owned(), - "[j] Down".to_owned(), - "[k] Up".to_owned(), + "[j|k] Down | Up".to_owned(), "[1-9] Set steps".to_owned(), "[m] More actions".to_owned(), "[Esc] Quit".to_owned(), @@ -197,8 +196,7 @@ impl NotebookState { vec![ "[l] Toggle".to_owned(), "[h] Close parent".to_owned(), - "[j] Down".to_owned(), - "[k] Up".to_owned(), + "[j|k] Down | Up".to_owned(), "[1-9] Set steps".to_owned(), "[m] More actions".to_owned(), "[Esc] Quit".to_owned(), @@ -213,32 +211,25 @@ impl NotebookState { ] } EditingNormalMode(VimNormalState::Idle) => { - /* TODO: - [o] insert new line below - [O] insert new line above - [0] move to line start - [$] move to line end + /* + h j k l w e b [1-9] o O 0 $ a, A, I, G, g, s, S, x, ^, y, d, u, Ctrl+r */ - vec![ "[i] Insert mode".to_owned(), + "[v] Visual mode".to_owned(), "[n] Browse".to_owned(), - "[h|j|k|l] Move cursor".to_owned(), - "[w|e|b] Word forward|end|back".to_owned(), - "[1-9] Set steps".to_owned(), "[t] Toggle line number".to_owned(), + "[Ctrl+h] Show Vim keymap".to_owned(), "[Esc] Quit".to_owned(), ] } EditingNormalMode(VimNormalState::Numbering(n)) => { - // TODO: s, S, x, y, d - + // h j k l [0-9] s S x y d w e b G vec![ format!("[h|j|k|l] Move cursor {n} steps"), - format!("[w|e|b] Word forward|end|back {n} steps"), - format!("[G] Go to line {n}"), "[0-9] Append steps".to_owned(), + "[Ctrl+h] Show Vim keymap".to_owned(), "[Esc] Cancel".to_owned(), ] } @@ -285,18 +276,20 @@ impl NotebookState { ] } EditingVisualMode(VimVisualState::Idle) => { - //todo + // more in the keymap vec![ - "[hjkl] Move".to_owned(), + "[h|j|k|l] Move cursor".to_owned(), "[1-9] Append steps".to_owned(), + "[Ctrl+h] Show Vim keymap".to_owned(), "[Esc] Cancel".to_owned(), ] } EditingVisualMode(VimVisualState::Numbering(n)) => { - //todo + // more in the keymap vec![ - format!("[h|j|k|l] Move cursor {n} steps"), - "[1-9] Append steps".to_owned(), + format!("[h|j|k|l] move cursor {n} steps"), + "[0-9] append steps".to_owned(), + "[Ctrl+h] Show Vim keymap".to_owned(), "[Esc] Cancel".to_owned(), ] } diff --git a/core/src/state/notebook/inner_state.rs b/core/src/state/notebook/inner_state.rs index f5000df..c0ad7e5 100644 --- a/core/src/state/notebook/inner_state.rs +++ b/core/src/state/notebook/inner_state.rs @@ -11,7 +11,7 @@ use crate::{db::Db, state::notebook::NotebookState, Event, NotebookTransition, R pub use editing_normal_mode::VimNormalState; pub use editing_visual_mode::VimVisualState; -#[derive(Clone)] +#[derive(Clone, Copy)] pub enum InnerState { NoteSelected, NoteMoreActions, diff --git a/core/src/state/notebook/inner_state/editing_normal_mode.rs b/core/src/state/notebook/inner_state/editing_normal_mode.rs index eeefb27..4833be0 100644 --- a/core/src/state/notebook/inner_state/editing_normal_mode.rs +++ b/core/src/state/notebook/inner_state/editing_normal_mode.rs @@ -3,7 +3,9 @@ use { crate::{ db::Db, state::notebook::{directory, note, InnerState, NotebookState}, - transition::{NormalModeTransition, NotebookTransition, VisualModeTransition}, + transition::{ + NormalModeTransition, NotebookTransition, VimKeymapKind, VisualModeTransition, + }, Error, Event, KeyEvent, NotebookEvent, NumKey, Result, }, }; @@ -136,6 +138,7 @@ async fn consume_idle( NumberingMode.into() } + Key(KeyEvent::CtrlH) => Ok(NotebookTransition::ShowVimKeymap(VimKeymapKind::NormalIdle)), event @ Key(_) => Ok(NotebookTransition::Inedible(event)), _ => Err(Error::Wip("todo: Notebook::consume".to_owned())), } @@ -227,6 +230,9 @@ async fn consume_numbering( IdleMode.into() } + Key(KeyEvent::CtrlH) => Ok(NotebookTransition::ShowVimKeymap( + VimKeymapKind::NormalNumbering, + )), event @ Key(_) => { state.inner_state = InnerState::EditingNormalMode(VimNormalState::Idle); diff --git a/core/src/state/notebook/inner_state/editing_visual_mode.rs b/core/src/state/notebook/inner_state/editing_visual_mode.rs index 99546df..5fa197e 100644 --- a/core/src/state/notebook/inner_state/editing_visual_mode.rs +++ b/core/src/state/notebook/inner_state/editing_visual_mode.rs @@ -1,7 +1,7 @@ use crate::{ db::Db, state::notebook::{InnerState, NotebookState, VimNormalState}, - transition::{NormalModeTransition, NotebookTransition, VisualModeTransition}, + transition::{NormalModeTransition, NotebookTransition, VimKeymapKind, VisualModeTransition}, Error, Event, KeyEvent, NumKey, Result, }; @@ -78,6 +78,7 @@ async fn consume_idle( NumberingMode.into() } + Key(KeyEvent::CtrlH) => Ok(NotebookTransition::ShowVimKeymap(VimKeymapKind::VisualIdle)), event @ Key(_) => Ok(NotebookTransition::Inedible(event)), _ => Err(Error::Wip("todo: Notebook::consume".to_owned())), } @@ -167,17 +168,6 @@ async fn consume_numbering( MoveCursorToLine(n).into() } - Key(KeyEvent::X | KeyEvent::D) => { - state.inner_state = InnerState::EditingNormalMode(VimNormalState::Idle); - - DeleteSelection.into() - } - - Key(KeyEvent::Y) => { - state.inner_state = InnerState::EditingNormalMode(VimNormalState::Idle); - - YankSelection.into() - } Key(KeyEvent::Esc) => { state.inner_state = InnerState::EditingNormalMode(VimNormalState::Idle); @@ -185,6 +175,9 @@ async fn consume_numbering( NormalModeTransition::IdleMode, )) } + Key(KeyEvent::CtrlH) => Ok(NotebookTransition::ShowVimKeymap( + VimKeymapKind::VisualNumbering, + )), event @ Key(_) => { state.inner_state = InnerState::EditingNormalMode(VimNormalState::Idle); diff --git a/core/src/transition.rs b/core/src/transition.rs index 390c39b..03cb21e 100644 --- a/core/src/transition.rs +++ b/core/src/transition.rs @@ -79,6 +79,15 @@ pub enum NotebookTransition { SelectPrev(usize), EditingNormalMode(NormalModeTransition), EditingVisualMode(VisualModeTransition), + ShowVimKeymap(VimKeymapKind), +} + +#[derive(Clone, Copy, Display)] +pub enum VimKeymapKind { + NormalIdle, + NormalNumbering, + VisualIdle, + VisualNumbering, } #[derive(Display)] diff --git a/tui/src/action.rs b/tui/src/action.rs index 0fa1efa..7e5640a 100644 --- a/tui/src/action.rs +++ b/tui/src/action.rs @@ -384,6 +384,8 @@ fn to_event(input: Input) -> Option { let ctrl = key.modifiers == KeyModifiers::CONTROL; let event = match code { + KeyCode::Char('h') if ctrl => KeyEvent::CtrlH, + KeyCode::Char('r') if ctrl => KeyEvent::CtrlR, KeyCode::Char('a') => KeyEvent::A, KeyCode::Char('b') => KeyEvent::B, KeyCode::Char('d') => KeyEvent::D, @@ -409,7 +411,6 @@ fn to_event(input: Input) -> Option { KeyCode::Char('I') => KeyEvent::CapI, KeyCode::Char('O') => KeyEvent::CapO, KeyCode::Char('S') => KeyEvent::CapS, - KeyCode::Char('r') if ctrl => KeyEvent::CtrlR, KeyCode::Char('1') => NumKey::One.into(), KeyCode::Char('2') => NumKey::Two.into(), KeyCode::Char('3') => NumKey::Three.into(), diff --git a/tui/src/context.rs b/tui/src/context.rs index 2b369df..fe52082 100644 --- a/tui/src/context.rs +++ b/tui/src/context.rs @@ -3,6 +3,7 @@ pub mod notebook; use { crate::{log, logger::*, Action}, + glues_core::transition::VimKeymapKind, ratatui::{ crossterm::event::{Event as Input, KeyCode, KeyEvent}, style::{Style, Stylize}, @@ -55,6 +56,7 @@ pub struct Context { pub help: bool, pub editor_keymap: bool, + pub vim_keymap: Option, } impl Default for Context { @@ -71,6 +73,7 @@ impl Default for Context { help: false, editor_keymap: false, + vim_keymap: None, } } } @@ -86,7 +89,10 @@ impl Context { } pub async fn consume(&mut self, input: &Input) -> Action { - if self.editor_keymap { + if self.vim_keymap.is_some() { + self.vim_keymap = None; + return Action::None; + } else if self.editor_keymap { self.editor_keymap = false; return Action::None; } else if self.help { diff --git a/tui/src/transitions.rs b/tui/src/transitions.rs index 174baea..1b990f2 100644 --- a/tui/src/transitions.rs +++ b/tui/src/transitions.rs @@ -55,6 +55,9 @@ impl App { pub(super) async fn handle_notebook_transition(&mut self, transition: NotebookTransition) { match transition { + NotebookTransition::ShowVimKeymap(kind) => { + self.context.vim_keymap = Some(kind); + } NotebookTransition::OpenDirectory { id, .. } => { log!("Opening directory {id}"); let NotebookState { root, .. } = self.glues.state.get_inner().log_unwrap(); diff --git a/tui/src/views/dialog.rs b/tui/src/views/dialog.rs index 09a423e..9165524 100644 --- a/tui/src/views/dialog.rs +++ b/tui/src/views/dialog.rs @@ -5,6 +5,7 @@ mod editor_keymap; mod help; mod note_actions; mod prompt; +mod vim_keymap; use { crate::{ @@ -15,7 +16,10 @@ use { }; pub fn draw(frame: &mut Frame, context: &mut Context) { - if context.editor_keymap { + if let Some(kind) = context.vim_keymap { + vim_keymap::draw(frame, kind); + return; + } else if context.editor_keymap { editor_keymap::draw(frame); return; } else if context.help { diff --git a/tui/src/views/dialog/vim_keymap.rs b/tui/src/views/dialog/vim_keymap.rs new file mode 100644 index 0000000..58cf962 --- /dev/null +++ b/tui/src/views/dialog/vim_keymap.rs @@ -0,0 +1,155 @@ +use { + glues_core::transition::VimKeymapKind, + ratatui::{ + layout::{Alignment, Constraint::Length, Flex, Layout}, + style::{Style, Stylize}, + text::Line, + widgets::{Block, Clear, Padding, Paragraph, Wrap}, + Frame, + }, +}; + +pub fn draw(frame: &mut Frame, keymap_kind: VimKeymapKind) { + let (title, height, message) = match keymap_kind { + VimKeymapKind::NormalIdle => ( + "VIM NORMAL MODE KEYMAP", + 41, + vec![ + Line::from("TO INSERT MODE".white().on_dark_gray()), + Line::raw("[i] Go to insert mode"), + Line::raw("[I] Go to insert mode at the beginning of the line"), + Line::raw("[o] Insert a new line below and go to insert mode"), + Line::raw("[O] Insert a new line above and go to insert mode"), + Line::raw("[a] Move cursor forward and go to insert mode"), + Line::raw("[A] Move cursor to the end of the line and go to insert mode"), + Line::raw("[s] Delete character and go to insert mode"), + Line::raw("[S] Delete line and go to insert mode"), + Line::raw(""), + Line::from("TO OTHER MODES".white().on_dark_gray()), + Line::raw("[v] Go to visual mode (select text to edit or copy)"), + Line::raw("[g] Go to gateway mode (access extended commands)"), + Line::raw("[y] Go to yank mode (prepare to copy text)"), + Line::raw("[d] Go to delete mode (prepare to delete text)"), + Line::raw("[1-9] Go to numbering mode (repeat or extend actions with numbers)"), + Line::raw(""), + Line::from("MOVE CURSOR".white().on_dark_gray()), + Line::raw("[h] Move cursor left"), + Line::raw("[j] Move cursor down"), + Line::raw("[k] Move cursor up"), + Line::raw("[l] Move cursor right"), + Line::raw("[w] Move cursor to the start of the next word"), + Line::raw("[e] Move cursor to the end of the next word"), + Line::raw("[b] Move cursor to the start of the previous word"), + Line::raw("[0] Move cursor to the start of the line"), + Line::raw("[$] Move cursor to the end of the line"), + Line::raw("[^] Move cursor to the first non-blank character of the line"), + Line::raw("[G] Move cursor to the end of the file"), + Line::raw(""), + Line::from("EDIT TEXT".white().on_dark_gray()), + Line::raw("[x] Delete character under the cursor"), + Line::raw("[u] Undo the last change"), + Line::raw("[Ctrl+r] Redo the last undone change"), + ], + ), + VimKeymapKind::NormalNumbering => ("VIM NORMAL MODE KEYMAP - NUMBERING", 30, vec![ + Line::from("EXTENDING NUMBERING MODE".white().on_dark_gray()), + Line::raw("[0-9] Append additional digits to extend the current command"), + Line::raw(""), + Line::from("TO INSERT MODE".white().on_dark_gray()), + Line::raw("[s] Delete specified number of characters and go to insert mode"), + Line::raw("[S] Delete the entire line and go to insert mode"), + Line::raw(""), + Line::from("TO OTHER MODES".white().on_dark_gray()), + Line::raw("[y] Go to yank mode with repeat count (prepare to copy text)"), + Line::raw("[d] Go to delete mode with repeat count (prepare to delete text)"), + Line::raw(""), + Line::from("MOVE CURSOR AND RETURN TO NORMAL MODE".white().on_dark_gray()), + Line::raw("[h] Move cursor left by the specified number of times"), + Line::raw("[j] Move cursor down by the specified number of times"), + Line::raw("[k] Move cursor up by the specified number of times"), + Line::raw("[l] Move cursor right by the specified number of times"), + Line::raw("[w] Move cursor to the start of the next word, repeated by the specified number"), + Line::raw("[e] Move cursor to the end of the next word, repeated by the specified number"), + Line::raw("[b] Move cursor to the start of the previous word, repeated by the specified number"), + Line::raw("[G] Move cursor to the specified line number"), + Line::raw(""), + Line::from("EDIT TEXT AND RETURN TO NORMAL MODE".white().on_dark_gray()), + Line::raw("[x] Delete specified number of characters and return to normal mode"), + Line::raw(""), + ]), + VimKeymapKind::VisualIdle => ("VIM VISUAL MODE KEYMAP", 30, vec![ + Line::from("MOVE CURSOR".white().on_dark_gray()), + Line::raw("[h] Move cursor left"), + Line::raw("[j] Move cursor down"), + Line::raw("[k] Move cursor up"), + Line::raw("[l] Move cursor right"), + Line::raw("[w] Move cursor to the start of the next word"), + Line::raw("[e] Move cursor to the end of the next word"), + Line::raw("[b] Move cursor to the start of the previous word"), + Line::raw("[0] Move cursor to the start of the line"), + Line::raw("[$] Move cursor to the end of the line"), + Line::raw("[^] Move cursor to the first non-blank character of the line"), + Line::raw("[G] Move cursor to the end of the file"), + Line::raw(""), + Line::from("TO INSERT MODE".white().on_dark_gray()), + Line::from(vec![ + "[s] ".into(), + "or ".dark_gray(), + "[S] Substitute selected text and go to insert mode".into(), + ]), + Line::raw(""), + Line::from("TO EXTENDED MODES".white().on_dark_gray()), + Line::raw("[g] Go to gateway mode for additional commands"), + Line::raw("[1-9] Specify repeat count for subsequent actions"), + Line::raw(""), + Line::from("EDIT TEXT AND RETURN TO NORMAL MODE".white().on_dark_gray()), + Line::from(vec![ + "[d] ".into(), + "or ".dark_gray(), + "[x] Delete selected text".into(), + ]), + Line::raw("[y] Yank (copy) selected text"), + ]), + VimKeymapKind::VisualNumbering => ("VIM VISUAL MODE KEYMAP - NUMBERING", 20, vec![ + Line::from("EXTENDING NUMBERING MODE".white().on_dark_gray()), + Line::raw("[0-9] Append additional digits to extend the current command"), + Line::raw(""), + Line::from("MOVE CURSOR".white().on_dark_gray()), + Line::raw("[h] Move cursor left by the specified number of times"), + Line::raw("[j] Move cursor down by the specified number of times"), + Line::raw("[k] Move cursor up by the specified number of times"), + Line::raw("[l] Move cursor right by the specified number of times"), + Line::raw("[w] Move cursor to the start of the next word, repeated by the specified number"), + Line::raw("[e] Move cursor to the end of the next word, repeated by the specified number"), + Line::raw("[b] Move cursor to the start of the previous word, repeated by the specified number"), + Line::raw("[G] Move cursor to the specified line number"), + ]), + }; + + let [area] = Layout::horizontal([Length(90)]) + .flex(Flex::Center) + .areas(frame.area()); + let [area] = Layout::vertical([Length(height)]) + .flex(Flex::Center) + .areas(area); + + let block = Block::bordered() + .padding(Padding::new(2, 2, 1, 1)) + .title(title.white().on_dark_gray()) + .title_alignment(Alignment::Center); + + let inner_area = block.inner(area); + let [message_area, control_area] = Layout::vertical([Length(height - 5), Length(1)]) + .flex(Flex::SpaceBetween) + .areas(inner_area); + let paragraph = Paragraph::new(message) + .wrap(Wrap { trim: true }) + .style(Style::default()) + .alignment(Alignment::Left); + let control = Line::from("Press any key to close".dark_gray()).centered(); + + frame.render_widget(Clear, area); + frame.render_widget(block, area); + frame.render_widget(paragraph, message_area); + frame.render_widget(control, control_area); +}