From: Simon Tatham Date: Tue, 2 Jan 2024 14:38:25 +0000 (+0000) Subject: Editor: implement cut to end of line. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=e45a1de6de62e614501714dc05cd9f9fd6780542;p=mastodonochrome.git Editor: implement cut to end of line. --- diff --git a/src/editor.rs b/src/editor.rs index b692e62..3d874bb 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -148,9 +148,13 @@ impl EditorCore { } } - fn insert(&mut self, text: &str) { + fn insert_after(&mut self, text: &str) { self.text = self.text[..self.point].to_owned() + text + &self.text[self.point..]; + } + + fn insert(&mut self, text: &str) { + self.insert_after(text); self.point += text.len(); } @@ -168,13 +172,13 @@ impl EditorCore { self.point = self.text.len(); } - fn cut(&mut self, start: usize, end: usize) { + fn delete(&mut self, start: usize, end: usize) -> String { assert!(self.is_char_boundary(start)); assert!(self.is_char_boundary(end)); assert!(end >= start); if end == start { - return; + return "".to_owned(); } let new_point = if self.point <= start { @@ -185,9 +189,17 @@ impl EditorCore { self.point + start - end }; - self.paste_buffer = self.text[start..end].to_owned(); + let deleted_text = self.text[start..end].to_owned(); self.text = self.text[..start].to_owned() + &self.text[end..]; self.point = new_point; + deleted_text + } + + fn cut(&mut self, start: usize, end: usize) { + let deleted = self.delete(start, end); + if !deleted.is_empty() { + self.paste_buffer = deleted; + } } fn handle_keypress(&mut self, key: OurKey) { @@ -1057,6 +1069,47 @@ impl Composer { let (_x, y) = self.cursor_pos.expect("post_update should have run"); self.goto_xy(usize::MAX, y); } + + fn cut_to_end_of_line(&mut self) { + let (_x, y) = self.cursor_pos.expect("post_update should have run"); + let eol_index = self.layout.partition_point(|cell| cell.y <= y); + let eol_pos = match self.layout.get(eol_index) { + Some(cell) => cell.pos, + None => self.core.text.len(), + }; + + let slice = &self.core.text[self.core.point..eol_pos]; + + if slice == "\n" { + // If the slice is _just_ \n, we delete it, so that you + // get the emacs-like behaviour where hammering on ^K + // alternates between deleting the contents of a line and + // the empty line it left behind. But we don't put that + // boring \n in the paste buffer. + self.core.delete(self.core.point, eol_pos); + } else if slice.ends_with("\n") { + // If the slice goes up to but not including a \n, + // literally cut everything up to but not including the + // end of the line. + self.core.cut(self.core.point, eol_pos - 1); + } else { + // Otherwise, the line we cut to the end of is a non-final + // line of a wrapped paragraph. Counterintuitively, in + // this case, I _insert_ a \n in place of the cut text, + // because that way the text on later lines doesn't rewrap + // confusingly. + // + // I'm not _sure_ this is the best thing, but the naïve + // thing isn't great either, and I think on balance this + // does more or less what my reflexes expect. + // + // Put another way: after ^K actually cuts some + // non-newline text, we expect that the cursor should + // always be on a newline or end-of-buffer. + self.core.cut(self.core.point, eol_pos); + self.core.insert_after("\n"); + } + } } #[test] @@ -1320,10 +1373,13 @@ impl ActivityState for Composer { (Start, Ctrl('A')) | (Start, Home) => self.beginning_of_line(), (Start, Ctrl('E')) | (Start, End) => self.end_of_line(), - (Start, Return) => { - self.core.insert("\n"); - self.post_update(); - } + (Start, Ctrl('K')) => self.cut_to_end_of_line(), + + // Return is an ordinary self-inserting key, but we have + // to handle it specially here, because EditorCore doesn't + // handle it at all, because in BottomLineEditor it _is_ + // special + (Start, Return) => self.core.insert("\n"), // ^O is a prefix key that is followed by various less // common keystrokes