From 6cfb1acb9d1456e854a2c44ee8fc057f45b29ea9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Mon, 22 Feb 2021 15:50:41 +0900
Subject: [PATCH] commands: Implement expand_selection.

---
 helix-core/src/lib.rs      |  1 +
 helix-core/src/object.rs   | 32 ++++++++++++++++++++++++++++++++
 helix-term/src/commands.rs | 13 +++++++++++++
 helix-term/src/keymap.rs   |  3 ++-
 4 files changed, 48 insertions(+), 1 deletion(-)
 create mode 100644 helix-core/src/object.rs

diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 14e58293..7b401557 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -5,6 +5,7 @@ pub mod graphemes;
 mod history;
 pub mod indent;
 pub mod macros;
+pub mod object;
 mod position;
 pub mod register;
 pub mod selection;
diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs
new file mode 100644
index 00000000..19ff9d96
--- /dev/null
+++ b/helix-core/src/object.rs
@@ -0,0 +1,32 @@
+use crate::{Range, RopeSlice, Selection, Syntax};
+use smallvec::smallvec;
+
+// TODO: to contract_selection we'd need to store the previous ranges before expand.
+// Maybe just contract to the first child node?
+pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
+    let tree = syntax.root_layer.tree.as_ref().unwrap();
+
+    selection.transform(|range| {
+        let from = text.char_to_byte(range.from());
+        let to = text.char_to_byte(range.to());
+
+        // find parent of a descendant that matches the range
+        let parent = match tree
+            .root_node()
+            .descendant_for_byte_range(from, to)
+            .and_then(|node| node.parent())
+        {
+            Some(parent) => parent,
+            None => return range,
+        };
+
+        let from = text.byte_to_char(parent.start_byte());
+        let to = text.byte_to_char(parent.end_byte());
+
+        if range.head < range.anchor {
+            Range::new(to, from)
+        } else {
+            Range::new(from, to)
+        }
+    })
+}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index c048ff9a..c37c0710 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1,6 +1,7 @@
 use helix_core::{
     comment, graphemes,
     indent::TAB_WIDTH,
+    object,
     regex::{self, Regex},
     register, selection,
     state::{Direction, Granularity, State},
@@ -1023,3 +1024,15 @@ pub fn toggle_comments(cx: &mut Context) {
 
     doc.apply(&transaction);
 }
+
+// tree sitter node selection
+
+pub fn expand_selection(cx: &mut Context) {
+    let doc = cx.doc();
+
+    if let Some(syntax) = &doc.syntax {
+        let text = doc.text().slice(..);
+        let selection = object::expand_selection(syntax, text, doc.selection());
+        doc.set_selection(selection);
+    }
+}
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 69c8e48c..81665bbe 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -164,10 +164,11 @@ pub fn default() -> Keymaps {
                 vec![alt!('s')] => commands::split_selection_on_newline,
                 vec![shift!('S')] => commands::split_selection,
                 vec![key!(';')] => commands::collapse_selection,
-                // TODO should be alt(;)
                 vec![alt!(';')] => commands::flip_selections,
                 vec![key!('%')] => commands::select_all,
                 vec![key!('x')] => commands::select_line,
+                // TODO: figure out what key to use
+                vec![key!('[')] => commands::expand_selection,
                 vec![key!('/')] => commands::search,
                 vec![key!('n')] => commands::search_next,
                 vec![key!('*')] => commands::search_selection,