on master help sign in
feat(commands): shrink_selection (#1340)
2e02a1dMatouš Dzivjak
book/src/keymap.md+2
@@ -261,6 +261,8 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
261| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
261| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
262| `[space` | Add newline above | `add_newline_above` |
262| `[space` | Add newline above | `add_newline_above` |
263| `]space` | Add newline below | `add_newline_below` |
263| `]space` | Add newline below | `add_newline_below` |
264+| `]o` | Expand syntax tree object selection. | `expand_selection` |
265+| `[o` | Shrink syntax tree object selection. | `shrink_selection` |
264
266
265## Insert Mode
267## Insert Mode
266
268
helix-core/src/object.rs+27−2
@@ -1,7 +1,5 @@
1use crate::{Range, RopeSlice, Selection, Syntax};
1use crate::{Range, RopeSlice, Selection, Syntax};
2
2
3−// TODO: to contract_selection we'd need to store the previous ranges before expand.
4−// Maybe just contract to the first child node?
5pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
3pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
6 let tree = syntax.tree();
4 let tree = syntax.tree();
7
5
@@ -34,3 +32,30 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
34 }
32 }
35 })
33 })
36}
34}
35+
36+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
37+ let tree = syntax.tree();
38+
39+ selection.clone().transform(|range| {
40+ let from = text.char_to_byte(range.from());
41+ let to = text.char_to_byte(range.to());
42+
43+ let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
44+ // find first child, if not possible, fallback to the node that contains selection
45+ Some(descendant) => match descendant.child(0) {
46+ Some(child) => child,
47+ None => descendant,
48+ },
49+ None => return range,
50+ };
51+
52+ let from = text.byte_to_char(descendant.start_byte());
53+ let to = text.byte_to_char(descendant.end_byte());
54+
55+ if range.head < range.anchor {
56+ Range::new(to, from)
57+ } else {
58+ Range::new(from, to)
59+ }
60+ })
61+}
helix-core/src/selection.rs+64
@@ -140,6 +140,11 @@ impl Range {
140 self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
140 self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
141 }
141 }
142
142
143+ #[inline]
144+ pub fn contains_range(&self, other: &Self) -> bool {
145+ self.from() <= other.from() && self.to() >= other.to()
146+ }
147+
143 pub fn contains(&self, pos: usize) -> bool {
148 pub fn contains(&self, pos: usize) -> bool {
144 self.from() <= pos && pos < self.to()
149 self.from() <= pos && pos < self.to()
145 }
150 }
@@ -544,6 +549,39 @@ impl Selection {
544 pub fn len(&self) -> usize {
549 pub fn len(&self) -> usize {
545 self.ranges.len()
550 self.ranges.len()
546 }
551 }
552+
553+ // returns true if self ⊇ other
554+ pub fn contains(&self, other: &Selection) -> bool {
555+ // can't contain other if it is larger
556+ if other.len() > self.len() {
557+ return false;
558+ }
559+
560+ let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
561+ let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
562+
563+ loop {
564+ match (ele_self, ele_other) {
565+ (Some(ra), Some(rb)) => {
566+ if !ra.contains_range(rb) {
567+ // `self` doesn't contain next element from `other`, advance `self`, we need to match all from `other`
568+ ele_self = iter_self.next();
569+ } else {
570+ // matched element from `other`, advance `other`
571+ ele_other = iter_other.next();
572+ };
573+ }
574+ (None, Some(_)) => {
575+ // exhausted `self`, we can't match the reminder of `other`
576+ return false;
577+ }
578+ (_, None) => {
579+ // no elements from `other` left to match, `self` contains `other`
580+ return true;
581+ }
582+ }
583+ }
584+ }
547}
585}
548
586
549impl<'a> IntoIterator for &'a Selection {
587impl<'a> IntoIterator for &'a Selection {
@@ -982,4 +1020,30 @@ mod test {
982 &["", "abcd", "efg", "rs", "xyz"]
1020 &["", "abcd", "efg", "rs", "xyz"]
983 );
1021 );
984 }
1022 }
1023+ #[test]
1024+ fn test_selection_contains() {
1025+ fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool {
1026+ let sela = Selection::new(a.iter().map(|a| Range::new(a.0, a.1)).collect(), 0);
1027+ let selb = Selection::new(b.iter().map(|b| Range::new(b.0, b.1)).collect(), 0);
1028+ sela.contains(&selb)
1029+ }
1030+
1031+ // exact match
1032+ assert!(contains(vec!((1, 1)), vec!((1, 1))));
1033+
1034+ // larger set contains smaller
1035+ assert!(contains(vec!((1, 1), (2, 2), (3, 3)), vec!((2, 2))));
1036+
1037+ // multiple matches
1038+ assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
1039+
1040+ // smaller set can't contain bigger
1041+ assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
1042+
1043+ assert!(contains(
1044+ vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
1045+ vec!((3, 4), (7, 9))
1046+ ));
1047+ assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
1048+ }
985}
1049}
helix-term/src/commands.rs+35−1
@@ -362,6 +362,7 @@ impl MappableCommand {
362 rotate_selection_contents_forward, "Rotate selection contents forward",
362 rotate_selection_contents_forward, "Rotate selection contents forward",
363 rotate_selection_contents_backward, "Rotate selections contents backward",
363 rotate_selection_contents_backward, "Rotate selections contents backward",
364 expand_selection, "Expand selection to parent syntax node",
364 expand_selection, "Expand selection to parent syntax node",
365+ shrink_selection, "Shrink selection to previously expanded syntax node",
365 jump_forward, "Jump forward on jumplist",
366 jump_forward, "Jump forward on jumplist",
366 jump_backward, "Jump backward on jumplist",
367 jump_backward, "Jump backward on jumplist",
367 save_selection, "Save the current selection to the jumplist",
368 save_selection, "Save the current selection to the jumplist",
@@ -5467,6 +5468,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
5467 doc.apply(&transaction, view.id);
5468 doc.apply(&transaction, view.id);
5468 doc.append_changes_to_history(view.id);
5469 doc.append_changes_to_history(view.id);
5469}
5470}
5471+
5470fn rotate_selection_contents_forward(cx: &mut Context) {
5472fn rotate_selection_contents_forward(cx: &mut Context) {
5471 rotate_selection_contents(cx, Direction::Forward)
5473 rotate_selection_contents(cx, Direction::Forward)
5472}
5474}
@@ -5482,7 +5484,39 @@ fn expand_selection(cx: &mut Context) {
5482
5484
5483 if let Some(syntax) = doc.syntax() {
5485 if let Some(syntax) = doc.syntax() {
5484 let text = doc.text().slice(..);
5486 let text = doc.text().slice(..);
5487+
5488+ let current_selection = doc.selection(view.id);
5489+
5490+ // save current selection so it can be restored using shrink_selection
5491+ view.object_selections.push(current_selection.clone());
5492+
5485− let selection = object::expand_selection(syntax, text, doc.selection(view.id));
5493+ let selection = object::expand_selection(syntax, text, current_selection);
5494+ doc.set_selection(view.id, selection);
5495+ }
5496+ };
5497+ motion(cx.editor);
5498+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
5499+}
5500+
5501+fn shrink_selection(cx: &mut Context) {
5502+ let motion = |editor: &mut Editor| {
5503+ let (view, doc) = current!(editor);
5504+ let current_selection = doc.selection(view.id);
5505+ // try to restore previous selection
5506+ if let Some(prev_selection) = view.object_selections.pop() {
5507+ if current_selection.contains(&prev_selection) {
5508+ // allow shrinking the selection only if current selection contains the previous object selection
5509+ doc.set_selection(view.id, prev_selection);
5510+ return;
5511+ } else {
5512+ // clear existing selection as they can't be shrinked to anyway
5513+ view.object_selections.clear();
5514+ }
5515+ }
5516+ // if not previous selection, shrink to first child
5517+ if let Some(syntax) = doc.syntax() {
5518+ let text = doc.text().slice(..);
5519+ let selection = object::shrink_selection(syntax, text, current_selection);
5486 doc.set_selection(view.id, selection);
5520 doc.set_selection(view.id, selection);
5487 }
5521 }
5488 };
5522 };
helix-term/src/keymap.rs+2
@@ -569,11 +569,13 @@ impl Default for Keymaps {
569 "d" => goto_prev_diag,
569 "d" => goto_prev_diag,
570 "D" => goto_first_diag,
570 "D" => goto_first_diag,
571 "space" => add_newline_above,
571 "space" => add_newline_above,
572+ "o" => shrink_selection,
572 },
573 },
573 "]" => { "Right bracket"
574 "]" => { "Right bracket"
574 "d" => goto_next_diag,
575 "d" => goto_next_diag,
575 "D" => goto_last_diag,
576 "D" => goto_last_diag,
576 "space" => add_newline_below,
577 "space" => add_newline_below,
578+ "o" => expand_selection,
577 },
579 },
578
580
579 "/" => search,
581 "/" => search,
helix-view/src/view.rs+3
@@ -80,6 +80,8 @@ pub struct View {
80 // uses two docs because we want to be able to swap between the
80 // uses two docs because we want to be able to swap between the
81 // two last modified docs which we need to manually keep track of
81 // two last modified docs which we need to manually keep track of
82 pub last_modified_docs: [Option<DocumentId>; 2],
82 pub last_modified_docs: [Option<DocumentId>; 2],
83+ /// used to store previous selections of tree-sitter objecs
84+ pub object_selections: Vec<Selection>,
83}
85}
84
86
85impl View {
87impl View {
@@ -92,6 +94,7 @@ impl View {
92 jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
94 jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
93 last_accessed_doc: None,
95 last_accessed_doc: None,
94 last_modified_docs: [None, None],
96 last_modified_docs: [None, None],
97+ object_selections: Vec::new(),
95 }
98 }
96 }
99 }
97
100