conf: InstanceStatusConfig,
last_size: Option<(usize, usize)>,
keystate: ComposerKeyState,
+ goal_column: Option<usize>,
header: FileHeader,
headersep: EditorHeaderSeparator,
}
conf,
last_size: None,
keystate: ComposerKeyState::Start,
+ goal_column: None,
header,
headersep: EditorHeaderSeparator::new(),
}
}
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]
{
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
}
}