Skip to content

Commit

Permalink
Add vim normal mode hjkl navigation support to editor (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
panarch authored Oct 27, 2024
1 parent 9eb6f64 commit 579a57c
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 41 deletions.
24 changes: 20 additions & 4 deletions core/src/state/notebook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use {
Error, Event, Glues, NotebookTransition, Result,
},
consume::{directory, note, traverse},
inner_state::InnerState::{self, *},
inner_state::{
InnerState::{self, *},
VimState,
},
};

pub use directory_item::{DirectoryItem, DirectoryItemChildren, TreeItem};
Expand Down Expand Up @@ -89,11 +92,16 @@ impl NotebookState {
NoteTreeNumber(n) => {
format!("Steps: '{n}' selected")
}
EditingNormalMode => {
EditingNormalMode(VimState::Idle) => {
let name = &self.get_selected_note()?.name;

format!("Note '{name}' normal mode")
}
EditingNormalMode(VimState::Numbering(n)) => {
let name = &self.get_selected_note()?.name;

format!("Note '{name}' normal mode, steps: '{n}'")
}
EditingInsertMode => {
let name = &self.get_selected_note()?.name;

Expand Down Expand Up @@ -134,15 +142,23 @@ impl NotebookState {
"[Esc] Cancel".to_owned(),
]
}
EditingNormalMode => {
EditingNormalMode(VimState::Idle) => {
vec![
"[i] Insert mode".to_owned(),
"[h|j|k|l] Move cursor".to_owned(),
"[1-9] Set steps".to_owned(),
"[b] Browse note tree".to_owned(),
"[n] Toggle line number".to_owned(),
"[h] Show editor keymap".to_owned(),
"[Esc] Quit".to_owned(),
]
}
EditingNormalMode(VimState::Numbering(n)) => {
vec![
format!("[h|j|k|l] Move cursor {n} steps"),
"[0-9] Append steps".to_owned(),
"[Esc] Cancel".to_owned(),
]
}
EditingInsertMode => {
vec![
"[Esc] Save note & Normal mode".to_owned(),
Expand Down
6 changes: 3 additions & 3 deletions core/src/state/notebook/consume/note.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
data::{Directory, Note},
db::Db,
state::notebook::{DirectoryItem, InnerState, NotebookState, SelectedItem},
state::notebook::{DirectoryItem, InnerState, NotebookState, SelectedItem, VimState},
Error, NotebookTransition, Result,
};

Expand Down Expand Up @@ -93,7 +93,7 @@ pub async fn open(
let content = db.fetch_note_content(note.id.clone()).await?;

state.editing = Some(note.clone());
state.inner_state = InnerState::EditingNormalMode;
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::OpenNote { note, content })
}
Expand All @@ -107,7 +107,7 @@ pub async fn edit(state: &mut NotebookState) -> Result<NotebookTransition> {
pub async fn view(state: &mut NotebookState) -> Result<NotebookTransition> {
let note = state.get_editing()?.clone();

state.inner_state = InnerState::EditingNormalMode;
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::ViewMode(note))
}
Expand Down
7 changes: 5 additions & 2 deletions core/src/state/notebook/inner_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod note_selected;
mod note_tree_number;

use crate::{db::Db, state::notebook::NotebookState, Event, NotebookTransition, Result};
pub use editing_normal_mode::VimState;

#[derive(Clone)]
pub enum InnerState {
Expand All @@ -15,7 +16,7 @@ pub enum InnerState {
DirectorySelected,
DirectoryMoreActions,
NoteTreeNumber(usize),
EditingNormalMode,
EditingNormalMode(VimState),
EditingInsertMode,
}

Expand All @@ -32,7 +33,9 @@ pub async fn consume(
NoteMoreActions => note_more_actions::consume(db, state, event).await,
DirectoryMoreActions => directory_more_actions::consume(db, state, event).await,
NoteTreeNumber(n) => note_tree_number::consume(db, state, *n, event).await,
EditingNormalMode => editing_normal_mode::consume(db, state, event).await,
EditingNormalMode(vim_state) => {
editing_normal_mode::consume(db, state, *vim_state, event).await
}
EditingInsertMode => editing_insert_mode::consume(db, state, event).await,
}
}
95 changes: 93 additions & 2 deletions core/src/state/notebook/inner_state/editing_normal_mode.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
use crate::{
db::Db,
state::notebook::{directory, note, NotebookState},
Error, Event, KeyEvent, NotebookEvent, NotebookTransition, Result,
state::notebook::{directory, note, InnerState, NotebookState},
transition::{NormalModeTransition, NotebookTransition},
Error, Event, KeyEvent, NotebookEvent, Result,
};

#[derive(Clone, Copy)]
pub enum VimState {
Idle,
Numbering(usize),
}

pub async fn consume(
db: &mut Db,
state: &mut NotebookState,
vim_state: VimState,
event: Event,
) -> Result<NotebookTransition> {
match vim_state {
VimState::Idle => consume_idle(db, state, event).await,
VimState::Numbering(n) => consume_numbering(state, n, event).await,
}
}

async fn consume_idle(
db: &mut Db,
state: &mut NotebookState,
event: Event,
Expand All @@ -18,6 +37,78 @@ pub async fn consume(
Notebook(UpdateNoteContent(content)) => note::update_content(db, state, content).await,
Key(KeyEvent::E) | Notebook(EditNote) => note::edit(state).await,
Key(KeyEvent::B) | Notebook(BrowseNoteTree) => note::browse(state).await,
Key(KeyEvent::Num(n)) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Numbering(n.into()));

Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::NumberingMode,
))
}
Key(KeyEvent::J) => Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorDown(1),
)),
Key(KeyEvent::K) => Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorUp(1),
)),
Key(KeyEvent::H) => Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorBack(1),
)),
Key(KeyEvent::L) => Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorForward(1),
)),
event @ Key(_) => Ok(NotebookTransition::Inedible(event)),
_ => Err(Error::Wip("todo: Notebook::consume".to_owned())),
}
}

async fn consume_numbering(
state: &mut NotebookState,
n: usize,
event: Event,
) -> Result<NotebookTransition> {
use Event::*;

match event {
Key(KeyEvent::Num(n2)) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Numbering(n2 + n * 10));

Ok(NotebookTransition::None)
}
Key(KeyEvent::J) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorDown(n),
))
}
Key(KeyEvent::K) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorUp(n),
))
}
Key(KeyEvent::H) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorBack(n),
))
}
Key(KeyEvent::L) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::MoveCursorForward(n),
))
}
Key(KeyEvent::Esc) => {
state.inner_state = InnerState::EditingNormalMode(VimState::Idle);

Ok(NotebookTransition::EditingNormalMode(
NormalModeTransition::IdleMode,
))
}
event @ Key(_) => Ok(NotebookTransition::Inedible(event)),
_ => Err(Error::Wip("todo: Notebook::consume".to_owned())),
}
Expand Down
11 changes: 11 additions & 0 deletions core/src/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ pub enum NotebookTransition {
// Additional frontend action required
SelectNext(usize),
SelectPrev(usize),
EditingNormalMode(NormalModeTransition),
}

#[derive(Display)]
pub enum NormalModeTransition {
IdleMode,
NumberingMode,
MoveCursorDown(usize),
MoveCursorUp(usize),
MoveCursorBack(usize),
MoveCursorForward(usize),
}

impl From<EntryTransition> for Transition {
Expand Down
72 changes: 45 additions & 27 deletions tui/src/context/notebook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub enum ContextState {
NoteTreeNumbering,
NoteActionsDialog,
DirectoryActionsDialog,
EditorNormalMode,
EditorNormalMode { idle: bool },
EditorInsertMode,
}

Expand Down Expand Up @@ -128,7 +128,7 @@ impl NotebookContext {
}

pub fn open_note(&mut self, note: Note, content: String) {
self.state = ContextState::EditorNormalMode;
self.state = ContextState::EditorNormalMode { idle: true };
self.opened_note = Some(note);
self.editor = TextArea::from(content.lines());
}
Expand All @@ -142,9 +142,8 @@ impl NotebookContext {
match self.state {
ContextState::NoteTreeBrowsing => self.consume_on_note_tree_browsing(code),
ContextState::NoteTreeNumbering => self.consume_on_note_tree_numbering(code),
ContextState::EditorNormalMode | ContextState::EditorInsertMode => {
self.consume_on_editor(input)
}
ContextState::EditorNormalMode { idle } => self.consume_on_editor_normal(input, idle),
ContextState::EditorInsertMode => self.consume_on_editor_insert(input),
ContextState::NoteActionsDialog => self.consume_on_note_actions(code),
ContextState::DirectoryActionsDialog => self.consume_on_directory_actions(code),
}
Expand Down Expand Up @@ -264,31 +263,12 @@ impl NotebookContext {
}
}

fn consume_on_editor(&mut self, input: &Input) -> Action {
fn consume_on_editor_normal(&mut self, input: &Input, idle: bool) -> Action {
let code = match input {
Input::Key(key) => key.code,
_ => return Action::None,
};

if self.state == ContextState::EditorInsertMode {
if code == KeyCode::Esc {
self.state = ContextState::EditorNormalMode;
return Action::Dispatch(NotebookEvent::ViewNote.into());
} else if matches!(
input,
Input::Key(KeyEvent {
code: KeyCode::Char('h'),
modifiers: KeyModifiers::CONTROL,
..
})
) {
return TuiAction::ShowEditorKeymap.into();
} else {
self.editor.input(input.clone());
return Action::None;
}
}

match code {
KeyCode::Char('b') => {
self.state = ContextState::NoteTreeBrowsing;
Expand All @@ -305,16 +285,54 @@ impl NotebookContext {

Action::None
}
KeyCode::Char('h') => TuiAction::ShowEditorKeymap.into(),
KeyCode::Esc => TuiAction::Confirm {
KeyCode::Esc if idle => TuiAction::Confirm {
message: "Do you want to quit?".to_owned(),
action: Box::new(TuiAction::Quit.into()),
}
.into(),
KeyCode::Esc
| KeyCode::Char('h')
| KeyCode::Char('j')
| KeyCode::Char('k')
| KeyCode::Char('l')
| KeyCode::Char('0')
| KeyCode::Char('1')
| KeyCode::Char('2')
| KeyCode::Char('3')
| KeyCode::Char('4')
| KeyCode::Char('5')
| KeyCode::Char('6')
| KeyCode::Char('7')
| KeyCode::Char('8')
| KeyCode::Char('9') => Action::PassThrough,
_ => Action::None,
}
}

fn consume_on_editor_insert(&mut self, input: &Input) -> Action {
let code = match input {
Input::Key(key) => key.code,
_ => return Action::None,
};

if code == KeyCode::Esc {
self.state = ContextState::EditorNormalMode { idle: true };
Action::Dispatch(NotebookEvent::ViewNote.into())
} else if matches!(
input,
Input::Key(KeyEvent {
code: KeyCode::Char('h'),
modifiers: KeyModifiers::CONTROL,
..
})
) {
TuiAction::ShowEditorKeymap.into()
} else {
self.editor.input(input.clone());
Action::None
}
}

fn consume_on_note_actions(&mut self, code: KeyCode) -> Action {
match code {
KeyCode::Char('j') => {
Expand Down
Loading

0 comments on commit 579a57c

Please sign in to comment.