From 01907b349771106865853dc2ea90aaa0913a7297 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Wed, 24 Feb 2021 17:29:28 +0900
Subject: [PATCH] commands: Implement count for a few more commands.

---
 TODO.md                    |  13 +++-
 helix-core/src/state.rs    | 120 ++++++++++++++++++++-----------------
 helix-term/src/commands.rs |  27 +++++----
 helix-view/src/editor.rs   |   2 +-
 4 files changed, 89 insertions(+), 73 deletions(-)

diff --git a/TODO.md b/TODO.md
index e378d457..9f20ebb2 100644
--- a/TODO.md
+++ b/TODO.md
@@ -12,16 +12,16 @@
 - [x] % for whole doc selection
 - [x] vertical splits
 - [x] input counts (30j)
-  - [ ] input counts for b, w, e
+  - [x] input counts for b, w, e
 - [ ] respect view fullscreen flag
 - [x] retain horiz when moving vertically
+- [w] retain horiz when moving via ctrl-u/d
 - [x] deindent
 - [ ] ensure_cursor_in_view always before rendering? or always in app after event process?
 - [x] update lsp on redo/undo
 - [ ] Implement marks (superset of Selection/Range)
 - [ ] ctrl-v/ctrl-x on file picker
 - [ ] linewise selection work
-- [ ] goto definition
 - [ ] nixos packaging
 - [ ] CI binary builds
 
@@ -33,6 +33,13 @@
 - [ ] buffers should sit on editor.buffers, view simply refs them
 
 
+- [ ] lsp: signature help
+- [ ] lsp: hover
+- [ ] lsp: document symbols (nested/vec)
+- [ ] lsp: code actions
+- [ ] lsp: formatting
+- [ ] lsp: goto
+
 2
 - [ ] tab completion for paths on the prompt
 - [ ] extend selection (treesitter select parent node) (replaces viw, vi(, va( etc )
@@ -41,7 +48,7 @@
 - [ ] completion signature popups/docs
 - [ ] multiple views into the same file
 - [ ] selection align
-- [ ] store some state: file positions, prompt history
+- [ ] store some state between restarts: file positions, prompt history
 
 3
 - [ ] diagnostics popups
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
index 78c9deb0..90e75eb4 100644
--- a/helix-core/src/state.rs
+++ b/helix-core/src/state.rs
@@ -120,76 +120,84 @@ impl State {
         }
     }
 
-    pub fn move_next_word_start(slice: RopeSlice, mut pos: usize) -> usize {
+    pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
         // TODO: confirm it's fine without using graphemes, I think it should be
-        let ch = slice.char(pos);
-        let next = slice.char(pos.saturating_add(1));
-        if categorize(ch) != categorize(next) {
-            pos += 1;
+        for _ in 0..count {
+            let ch = slice.char(pos);
+            let next = slice.char(pos.saturating_add(1));
+            if categorize(ch) != categorize(next) {
+                pos += 1;
+            }
+
+            // refetch
+            let ch = slice.char(pos);
+
+            if is_word(ch) {
+                skip_over_next(slice, &mut pos, is_word);
+            } else if ch.is_ascii_punctuation() {
+                skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
+            }
+
+            // TODO: don't include newline?
+            skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
         }
 
-        // refetch
-        let ch = slice.char(pos);
-
-        if is_word(ch) {
-            skip_over_next(slice, &mut pos, is_word);
-        } else if ch.is_ascii_punctuation() {
-            skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
-        }
-
-        // TODO: don't include newline?
-        skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
-
         pos
     }
 
-    pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize) -> usize {
+    pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
         // TODO: confirm it's fine without using graphemes, I think it should be
-        let ch = slice.char(pos);
-        let prev = slice.char(pos.saturating_sub(1)); // TODO: just return original pos if at start
+        for _ in 0..count {
+            let ch = slice.char(pos);
+            let prev = slice.char(pos.saturating_sub(1)); // TODO: just return original pos if at start
 
-        if categorize(ch) != categorize(prev) {
-            pos -= 1;
+            if categorize(ch) != categorize(prev) {
+                pos -= 1;
+            }
+
+            // TODO: skip while eol
+
+            // TODO: don't include newline?
+            skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_whitespace());
+
+            // refetch
+            let ch = slice.char(pos);
+
+            if is_word(ch) {
+                skip_over_prev(slice, &mut pos, is_word);
+            } else if ch.is_ascii_punctuation() {
+                skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_punctuation());
+            }
+            pos = pos.saturating_add(1)
         }
 
-        // TODO: skip while eol
-
-        // TODO: don't include newline?
-        skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_whitespace());
-
-        // refetch
-        let ch = slice.char(pos);
-
-        if is_word(ch) {
-            skip_over_prev(slice, &mut pos, is_word);
-        } else if ch.is_ascii_punctuation() {
-            skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_punctuation());
-        }
-
-        pos.saturating_add(1)
+        pos
     }
 
-    pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, _count: usize) -> usize {
-        // TODO: confirm it's fine without using graphemes, I think it should be
-        let ch = slice.char(pos);
-        let next = slice.char(pos.saturating_add(1));
-        if categorize(ch) != categorize(next) {
-            pos += 1;
+    pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
+        for _ in 0..count {
+            // TODO: confirm it's fine without using graphemes, I think it should be
+            let ch = slice.char(pos);
+            let next = slice.char(pos.saturating_add(1));
+            if categorize(ch) != categorize(next) {
+                pos += 1;
+            }
+
+            // TODO: don't include newline?
+            skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
+
+            // refetch
+            let ch = slice.char(pos);
+
+            if is_word(ch) {
+                skip_over_next(slice, &mut pos, is_word);
+            } else if ch.is_ascii_punctuation() {
+                skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
+            }
+            pos = pos.saturating_sub(1)
         }
 
-        // TODO: don't include newline?
-        skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
-
-        // refetch
-        let ch = slice.char(pos);
-
-        if is_word(ch) {
-            skip_over_next(slice, &mut pos, is_word);
-        } else if ch.is_ascii_punctuation() {
-            skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
-        }
-
-        pos.saturating_sub(1)
+        pos
     }
 
     pub fn move_selection(
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 2b8fe405..7d7ad0c9 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -128,8 +128,7 @@ pub fn move_line_start(cx: &mut Context) {
 pub fn move_next_word_start(cx: &mut Context) {
     let count = cx.count;
     let doc = cx.doc();
-    // TODO: count
-    let pos = State::move_next_word_start(doc.text().slice(..), doc.selection().cursor());
+    let pos = State::move_next_word_start(doc.text().slice(..), doc.selection().cursor(), count);
 
     doc.set_selection(Selection::point(pos));
 }
@@ -137,7 +136,7 @@ pub fn move_next_word_start(cx: &mut Context) {
 pub fn move_prev_word_start(cx: &mut Context) {
     let count = cx.count;
     let doc = cx.doc();
-    let pos = State::move_prev_word_start(doc.text().slice(..), doc.selection().cursor());
+    let pos = State::move_prev_word_start(doc.text().slice(..), doc.selection().cursor(), count);
 
     doc.set_selection(Selection::point(pos));
 }
@@ -169,11 +168,12 @@ pub fn move_file_end(cx: &mut Context) {
 pub fn extend_next_word_start(cx: &mut Context) {
     let count = cx.count;
     let doc = cx.doc();
-    let mut selection = doc.selection().transform(|mut range| {
-        let pos = State::move_next_word_start(doc.text().slice(..), doc.selection().cursor());
+    let selection = doc.selection().transform(|mut range| {
+        let pos =
+            State::move_next_word_start(doc.text().slice(..), doc.selection().cursor(), count);
         range.head = pos;
         range
-    }); // TODO: count
+    });
 
     doc.set_selection(selection);
 }
@@ -181,22 +181,23 @@ pub fn extend_next_word_start(cx: &mut Context) {
 pub fn extend_prev_word_start(cx: &mut Context) {
     let count = cx.count;
     let doc = cx.doc();
-    let mut selection = doc.selection().transform(|mut range| {
-        let pos = State::move_prev_word_start(doc.text().slice(..), doc.selection().cursor());
+    let selection = doc.selection().transform(|mut range| {
+        let pos =
+            State::move_prev_word_start(doc.text().slice(..), doc.selection().cursor(), count);
         range.head = pos;
         range
-    }); // TODO: count
+    });
     doc.set_selection(selection);
 }
 
 pub fn extend_next_word_end(cx: &mut Context) {
     let count = cx.count;
     let doc = cx.doc();
-    let mut selection = doc.selection().transform(|mut range| {
+    let selection = doc.selection().transform(|mut range| {
         let pos = State::move_next_word_end(doc.text().slice(..), doc.selection().cursor(), count);
         range.head = pos;
         range
-    }); // TODO: count
+    });
 
     doc.set_selection(selection);
 }
@@ -417,13 +418,13 @@ pub fn search_selection(cx: &mut Context) {
 //
 
 pub fn select_line(cx: &mut Context) {
-    // TODO: count
+    let count = cx.count;
     let doc = cx.doc();
     let pos = doc.selection().primary();
     let text = doc.text();
     let line = text.char_to_line(pos.head);
     let start = text.line_to_char(line);
-    let end = text.line_to_char(line + 1).saturating_sub(1);
+    let end = text.line_to_char(line + count).saturating_sub(1);
 
     doc.set_selection(Selection::single(start, end));
 }
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index b70a6abd..1c737b3e 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -12,7 +12,7 @@ pub struct Editor {
     pub tree: Tree,
     // pub documents: Vec<Document>,
     pub count: Option<usize>,
-    pub theme: Theme, // TODO: share one instance
+    pub theme: Theme,
     pub language_servers: helix_lsp::Registry,
 }