From 6a0b35df4e0819bf01d04a488bd58d7ab65e0b10 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 9 Oct 2022 18:04:09 -0400 Subject: [PATCH] Generalize View::object_selections into map This allows using multiple distinct state histories. By default, all history is also cleared any time a view's selection is set, unless explicitly told to save the state. This way, we can have control over when selection history is saved. They are also cleared on any text edit, since an edit could invalidate the previous selection, potentially causing a panic. Additionally, the object selections have been moved into `Document` so that it is easier to manipulate them when changes to the document happen. They have been put into a wrapper struct named `ViewData`, where the intention is that any further fields that we want to add in the future that must be associated with a view, but are more convenient to store in a document, can be added here, instead of further polluting the core `Document` type. --- helix-term/src/commands.rs | 50 ++++++++++++++++++++++++-------------- helix-view/src/document.rs | 43 ++++++++++++++++++++++++++++++-- helix-view/src/view.rs | 3 --- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 116c637879ee3..82b4e3028ac35 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3041,6 +3041,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); } @@ -3050,6 +3051,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); } @@ -3070,6 +3072,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); } @@ -3093,6 +3096,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); } @@ -4451,6 +4455,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); @@ -4458,18 +4464,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))); } @@ -4477,24 +4489,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))); } @@ -4544,8 +4560,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 bd3c465d41dde..eb2e848a1990c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -133,6 +133,7 @@ pub struct Document { pub(crate) id: DocumentId, text: Rope, selections: HashMap, + view_data: HashMap, /// Inlay hints annotations for the document, by view. /// @@ -265,6 +266,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) @@ -602,6 +604,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, @@ -1004,11 +1007,22 @@ impl Document { Ok(()) } - /// 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 @@ -1122,6 +1136,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); @@ -1152,6 +1167,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()); + if emit_lsp_notification { // emit lsp notification for language_server in self.language_servers() { @@ -1546,6 +1567,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() @@ -1720,6 +1749,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 { @@ -1769,6 +1805,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 = @@ -1806,7 +1843,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(), }