From 41aedc569f50d1cad7d07f04492a5d5877238d7d 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. 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 | 51 ++++++++++++++++++++++++-------------- helix-view/src/document.rs | 36 +++++++++++++++++++++++++-- helix-view/src/view.rs | 3 --- 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e09a1c5bbe3b1..5c3a00a914580 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2945,6 +2945,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); } @@ -2954,6 +2955,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); } @@ -2975,6 +2977,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); } @@ -2999,6 +3002,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); } @@ -4295,6 +4299,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); @@ -4302,18 +4308,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))); } @@ -4321,25 +4333,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) { - // allow shrinking the selection only if current selection contains the previous object 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))); } @@ -4389,8 +4404,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 4d3586f1ae27f..ab4b7c630004a 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -109,6 +109,7 @@ pub struct Document { pub(crate) id: DocumentId, text: Rope, selections: HashMap, + view_data: HashMap, path: Option, encoding: &'static encoding::Encoding, @@ -159,6 +160,7 @@ impl fmt::Debug for Document { .field("id", &self.id) .field("text", &self.text) .field("selections", &self.selections) + .field("view_data", &self.view_data) .field("path", &self.path) .field("encoding", &self.encoding) .field("restore_cursor", &self.restore_cursor) @@ -379,6 +381,7 @@ impl Document { encoding, text, selections: HashMap::default(), + view_data: Default::default(), indent_style: DEFAULT_INDENT, line_ending: DEFAULT_LINE_ENDING, restore_cursor: false, @@ -773,11 +776,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 @@ -1165,6 +1179,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() @@ -1304,6 +1326,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 { @@ -1353,6 +1382,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 = @@ -1390,7 +1420,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 7bfbb2418379f..d2cb85ddb3f85 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -116,8 +116,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. @@ -151,7 +149,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(), }