From 63fe35b00b72bd203eb5e55914800845bf0c1654 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 2 Jan 2024 14:11:47 +0000 Subject: [PATCH] Editor: implement Up and Down keystrokes. --- src/editor.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 5ed64cb..a178af3 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -723,6 +723,7 @@ struct Composer { conf: InstanceStatusConfig, last_size: Option<(usize, usize)>, keystate: ComposerKeyState, + goal_column: Option, header: FileHeader, headersep: EditorHeaderSeparator, } @@ -744,6 +745,7 @@ impl Composer { conf, last_size: None, keystate: ComposerKeyState::Start, + goal_column: None, header, headersep: EditorHeaderSeparator::new(), } @@ -985,6 +987,49 @@ impl Composer { } self.cursor_pos = self.determine_cursor_pos(); } + + fn goto_xy(&mut self, x: usize, y: usize) -> bool { + // If we can't hit the desired x,y position exactly because + // it's in the middle of a wide character, I think it's more + // desirable to land on the wide character than to its right. + // + // This means we can't just search for the first layout cell + // that is >= (x,y), because that _is_ the one to its right. + // Instead we must search for the last one that is <= (x,y). + + let cell_index_after = self.layout + .partition_point(|cell| (cell.y, cell.x) <= (y, x)); + let cell_index = cell_index_after.checked_sub(1) + .expect("Cell 0 should be at (0,0) and always count as <= (y,x)"); + if let Some(cell) = self.layout.get(cell_index) { + self.core.point = cell.pos; + // Report failure if we didn't even get the right _line_ + cell.y == y + } else { + // overshoot to end of text + self.core.point = self.core.text.len(); + false + } + } + + fn next_line(&mut self) { + let (x, y) = self.cursor_pos.expect("post_update should have run"); + let x = self.goal_column.unwrap_or(x); + if !self.goto_xy(x, y+1) { + self.goal_column = None; + } + } + + fn prev_line(&mut self) { + let (x, y) = self.cursor_pos.expect("post_update should have run"); + let x = self.goal_column.unwrap_or(x); + if let Some(newy) = y.checked_sub(1) { + self.goto_xy(x, newy); + } else { + self.core.beginning_of_buffer(); + self.goal_column = None; + } + } } #[test] @@ -1215,22 +1260,44 @@ impl ActivityState for Composer { { use ComposerKeyState::*; + // Start by identifying whether a keystroke is an up/down one, + // so that we can consistently clear the goal column for all + // keystrokes that are not match (self.keystate, key) { + (Start, Ctrl('N')) | (Start, Down) | + (Start, Ctrl('P')) | (Start, Up) => { + if !self.goal_column.is_some() { + self.goal_column = self.cursor_pos.map(|(x, _y)| x); + } + } + _ => self.goal_column = None, + } + + match (self.keystate, key) { + (Start, Ctrl('N')) | (Start, Down) => self.next_line(), + (Start, Ctrl('P')) | (Start, Up) => self.prev_line(), + + (Start, Return) => { + self.core.insert("\n"); + self.post_update(); + } + + // ^O is a prefix key that is followed by various less + // common keystrokes (Start, Ctrl('O')) => { self.keystate = CtrlO; + return LogicalAction::Nothing; // no need to post_update } - (CtrlO, Pr('q')) | (CtrlO, Pr('Q')) => { - return LogicalAction::Pop; - } + (CtrlO, Pr('q')) | (CtrlO, Pr('Q')) => return LogicalAction::Pop, (CtrlO, _) => { self.keystate = Start; return LogicalAction::Beep; } - (Start, _) => { - self.core.handle_keypress(key); - self.post_update(); - } + + // Anything else goes to the EditorCore + (Start, _) => self.core.handle_keypress(key), } + self.post_update(); LogicalAction::Nothing } } -- 2.30.2