diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 0f53fdc94c370..38b7684566fa2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2959,6 +2959,7 @@ fn goto_first_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.start, diag.range.end), None => return, }; + doc.set_selection(view.id, selection); } @@ -2968,6 +2969,7 @@ fn goto_last_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.start, diag.range.end), None => return, }; + doc.set_selection(view.id, selection); } @@ -2989,6 +2991,7 @@ fn goto_next_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.start, diag.range.end), None => return, }; + doc.set_selection(view.id, selection); } @@ -3013,6 +3016,7 @@ fn goto_prev_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.end, diag.range.start), None => return, }; + doc.set_selection(view.id, selection); } @@ -4351,6 +4355,8 @@ fn rotate_selection_contents_backward(cx: &mut Context) { // tree sitter node selection +const EXPAND_KEY: &str = "expand"; + fn expand_selection(cx: &mut Context) { let motion = |editor: &mut Editor| { let (view, doc) = current!(editor); @@ -4358,18 +4364,24 @@ fn expand_selection(cx: &mut Context) { if let Some(syntax) = doc.syntax() { let text = doc.text().slice(..); - let current_selection = doc.selection(view.id); + let current_selection = doc.selection(view.id).clone(); let selection = object::expand_selection(syntax, text, current_selection.clone()); // check if selection is different from the last one - if *current_selection != selection { - // save current selection so it can be restored using shrink_selection - view.object_selections.push(current_selection.clone()); + if current_selection != selection { + let prev_selections = doc + .view_data_mut(view.id) + .object_selections + .entry(EXPAND_KEY) + .or_default(); - doc.set_selection(view.id, selection); + // save current selection so it can be restored using shrink_selection + prev_selections.push(current_selection); + doc.set_selection_clear(view.id, selection, false); } } }; + motion(cx.editor); cx.editor.last_motion = Some(Motion(Box::new(motion))); } @@ -4377,24 +4389,28 @@ fn expand_selection(cx: &mut Context) { fn shrink_selection(cx: &mut Context) { let motion = |editor: &mut Editor| { let (view, doc) = current!(editor); - let current_selection = doc.selection(view.id); + let current_selection = doc.selection(view.id).clone(); + let prev_expansions = doc + .view_data_mut(view.id) + .object_selections + .entry(EXPAND_KEY) + .or_default(); + // try to restore previous selection - if let Some(prev_selection) = view.object_selections.pop() { - if current_selection.contains(&prev_selection) { - doc.set_selection(view.id, prev_selection); - return; - } else { - // clear existing selection as they can't be shrunk to anyway - view.object_selections.clear(); - } + if let Some(prev_selection) = prev_expansions.pop() { + // allow shrinking the selection only if current selection contains the previous object selection + doc.set_selection_clear(view.id, prev_selection, false); + return; } + // if not previous selection, shrink to first child if let Some(syntax) = doc.syntax() { let text = doc.text().slice(..); - let selection = object::shrink_selection(syntax, text, current_selection.clone()); - doc.set_selection(view.id, selection); + let selection = object::shrink_selection(syntax, text, current_selection); + doc.set_selection_clear(view.id, selection, false); } }; + motion(cx.editor); cx.editor.last_motion = Some(Motion(Box::new(motion))); } @@ -4444,8 +4460,6 @@ fn match_brackets(cx: &mut Context) { } } -// - fn jump_forward(cx: &mut Context) { let count = cx.count(); let config = cx.editor.config(); diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index eca6002653f54..28e1e47395e49 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -119,6 +119,7 @@ pub struct Document { pub(crate) id: DocumentId, text: Rope, selections: HashMap, + view_data: HashMap, /// Inlay hints annotations for the document, by view. /// @@ -247,6 +248,7 @@ impl fmt::Debug for Document { .field("selections", &self.selections) .field("inlay_hints_oudated", &self.inlay_hints_oudated) .field("text_annotations", &self.inlay_hints) + .field("view_data", &self.view_data) .field("path", &self.path) .field("encoding", &self.encoding) .field("restore_cursor", &self.restore_cursor) @@ -478,6 +480,7 @@ impl Document { selections: HashMap::default(), inlay_hints: HashMap::default(), inlay_hints_oudated: false, + view_data: Default::default(), indent_style: DEFAULT_INDENT, line_ending: DEFAULT_LINE_ENDING, restore_cursor: false, @@ -875,11 +878,22 @@ impl Document { self.language_server = language_server; } - /// Select text within the [`Document`]. - pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) { + /// Select text within the [`Document`], optionally clearing the previous selection state. + pub fn set_selection_clear(&mut self, view_id: ViewId, selection: Selection, clear_prev: bool) { // TODO: use a transaction? self.selections .insert(view_id, selection.ensure_invariants(self.text().slice(..))); + + if clear_prev { + self.view_data + .entry(view_id) + .and_modify(|view_data| view_data.object_selections.clear()); + } + } + + /// Select text within the [`Document`]. + pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) { + self.set_selection_clear(view_id, selection, true); } /// Find the origin selection of the text in a document, i.e. where @@ -981,6 +995,7 @@ impl Document { diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After); diagnostic.line = self.text.char_to_line(diagnostic.range.start); } + self.diagnostics .sort_unstable_by_key(|diagnostic| diagnostic.range); @@ -1011,6 +1026,12 @@ impl Document { apply_inlay_hint_changes(padding_after_inlay_hints); } + // clear out all associated view object selections, as they are no + // longer valid + self.view_data + .values_mut() + .for_each(|view_data| view_data.object_selections.clear()); + // emit lsp notification if let Some(language_server) = self.language_server() { let notify = language_server.text_document_did_change( @@ -1341,6 +1362,14 @@ impl Document { &self.selections } + pub fn view_data(&mut self, view_id: ViewId) -> &ViewData { + self.view_data.entry(view_id).or_default() + } + + pub fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData { + self.view_data.entry(view_id).or_default() + } + pub fn relative_path(&self) -> Option { self.path .as_deref() @@ -1498,6 +1527,13 @@ impl Document { } } +/// Stores data needed for views that are tied to this specific Document. +#[derive(Debug, Default)] +pub struct ViewData { + /// used to store previous selections of tree-sitter objects + pub object_selections: HashMap<&'static str, Vec>, +} + #[derive(Clone, Debug)] pub enum FormatterError { SpawningFailed { @@ -1547,6 +1583,7 @@ mod test { Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); let view = ViewId::default(); + doc.ensure_view_init(view); doc.set_selection(view, Selection::single(0, 0)); let transaction = @@ -1584,7 +1621,9 @@ mod test { None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + let view = ViewId::default(); + doc.ensure_view_init(view); doc.set_selection(view, Selection::single(5, 5)); // insert diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index ee6fc1275debb..7d7aeeaa72666 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -122,8 +122,6 @@ pub struct View { // uses two docs because we want to be able to swap between the // two last modified docs which we need to manually keep track of pub last_modified_docs: [Option; 2], - /// used to store previous selections of tree-sitter objects - pub object_selections: Vec, /// all gutter-related configuration settings, used primarily for gutter rendering pub gutters: GutterConfig, /// A mapping between documents and the last history revision the view was updated at. @@ -157,7 +155,6 @@ impl View { jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel docs_access_history: Vec::new(), last_modified_docs: [None, None], - object_selections: Vec::new(), gutters, doc_revisions: HashMap::new(), }