chiark / gitweb /
Editor: implement cut to end of line.
authorSimon Tatham <anakin@pobox.com>
Tue, 2 Jan 2024 14:38:25 +0000 (14:38 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 2 Jan 2024 17:30:18 +0000 (17:30 +0000)
src/editor.rs

index b692e624cf1f400b0de9e05896cbe532ed8ff5d6..3d874bb31aef8ebbec7f365e304d6956ba507103 100644 (file)
@@ -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