From 40744ce8356cb9307f8cb9b2adf2c57b80b1ef9f Mon Sep 17 00:00:00 2001
From: Ivan Tham <pickfire@riseup.net>
Date: Sat, 5 Jun 2021 18:15:50 +0800
Subject: [PATCH] Add ctrl-w in insert mode

It seemed to panic when I pressed too many times, but that is from
lsp side.
---
 helix-core/src/lib.rs      |  1 +
 helix-core/src/movement.rs |  8 ++++----
 helix-core/src/words.rs    | 33 +++++++++++++++++++++++++++++++++
 helix-term/src/commands.rs | 20 +++++++++++++++++---
 helix-term/src/keymap.rs   |  1 +
 5 files changed, 56 insertions(+), 7 deletions(-)
 create mode 100644 helix-core/src/words.rs

diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 6b93de78..4be3e71b 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -16,6 +16,7 @@ pub mod selection;
 mod state;
 pub mod syntax;
 mod transaction;
+pub mod words;
 
 pub(crate) fn find_first_non_whitespace_char2(line: RopeSlice) -> Option<usize> {
     // find first non-whitespace char
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 297911ae..c5e2df4a 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -174,22 +174,22 @@ pub fn move_next_word_end(slice: RopeSlice, mut begin: usize, count: usize) -> O
 
 // used for by-word movement
 
-fn is_word(ch: char) -> bool {
+pub(crate) fn is_word(ch: char) -> bool {
     ch.is_alphanumeric() || ch == '_'
 }
 
-fn is_horiz_blank(ch: char) -> bool {
+pub(crate) fn is_horiz_blank(ch: char) -> bool {
     matches!(ch, ' ' | '\t')
 }
 
 #[derive(Debug, Eq, PartialEq)]
-enum Category {
+pub(crate) enum Category {
     Whitespace,
     Eol,
     Word,
     Punctuation,
 }
-fn categorize(ch: char) -> Category {
+pub(crate) fn categorize(ch: char) -> Category {
     if ch == '\n' {
         Category::Eol
     } else if ch.is_ascii_whitespace() {
diff --git a/helix-core/src/words.rs b/helix-core/src/words.rs
new file mode 100644
index 00000000..0099d56a
--- /dev/null
+++ b/helix-core/src/words.rs
@@ -0,0 +1,33 @@
+use crate::movement::{categorize, is_horiz_blank, is_word, skip_over_prev};
+use ropey::RopeSlice;
+
+#[must_use]
+pub fn nth_prev_word_boundary(slice: RopeSlice, mut char_idx: usize, count: usize) -> usize {
+    let mut with_end = false;
+
+    for _ in 0..count {
+        if char_idx == 0 {
+            break;
+        }
+
+        // return if not skip while?
+        skip_over_prev(slice, &mut char_idx, |ch| ch == '\n');
+
+        with_end = skip_over_prev(slice, &mut char_idx, is_horiz_blank);
+
+        // refetch
+        let ch = slice.char(char_idx);
+
+        if is_word(ch) {
+            with_end = skip_over_prev(slice, &mut char_idx, is_word);
+        } else if ch.is_ascii_punctuation() {
+            with_end = skip_over_prev(slice, &mut char_idx, |ch| ch.is_ascii_punctuation());
+        }
+    }
+
+    if with_end {
+        char_idx
+    } else {
+        char_idx + 1
+    }
+}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index c88a5eee..f340da91 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3,8 +3,8 @@ use helix_core::{
     movement::{self, Direction},
     object, pos_at_coords,
     regex::{self, Regex},
-    register, search, selection, Change, ChangeSet, Position, Range, Rope, RopeSlice, Selection,
-    SmallVec, Tendril, Transaction,
+    register, search, selection, words, Change, ChangeSet, Position, Range, Rope, RopeSlice,
+    Selection, SmallVec, Tendril, Transaction,
 };
 
 use helix_view::{
@@ -1782,7 +1782,6 @@ pub mod insert {
 
     pub fn delete_char_forward(cx: &mut Context) {
         let count = cx.count;
-        let doc = cx.doc();
         let (view, doc) = cx.current();
         let text = doc.text().slice(..);
         let transaction =
@@ -1795,6 +1794,21 @@ pub mod insert {
             });
         doc.apply(&transaction, view.id);
     }
+
+    pub fn delete_word_backward(cx: &mut Context) {
+        let count = cx.count;
+        let (view, doc) = cx.current();
+        let text = doc.text().slice(..);
+        let transaction =
+            Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
+                (
+                    words::nth_prev_word_boundary(text, range.head, count),
+                    range.head,
+                    None,
+                )
+            });
+        doc.apply(&transaction, view.id);
+    }
 }
 
 // Undo / Redo
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index e51dfbc5..6ef53915 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -360,6 +360,7 @@ pub fn default() -> Keymaps {
             } => commands::insert::insert_tab,
 
             ctrl!('x') => commands::completion,
+            ctrl!('w') => commands::insert::delete_word_backward,
         ),
     )
 }