Unnamed repository; edit this file 'description' to name the repository.
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.
Skyler Hawthorne 5 months ago
parent a938646 · commit d445661
-rw-r--r--helix-term/src/commands.rs49
-rw-r--r--helix-view/src/document.rs35
-rw-r--r--helix-view/src/view.rs3
3 files changed, 61 insertions, 26 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 8edf5944..aafe9619 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3942,6 +3942,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);
view.diagnostics_handler
.immediately_show_diagnostic(doc, view.id);
@@ -3953,6 +3954,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);
view.diagnostics_handler
.immediately_show_diagnostic(doc, view.id);
@@ -4009,6 +4011,7 @@ fn goto_prev_diag(cx: &mut Context) {
view.diagnostics_handler
.immediately_show_diagnostic(doc, view.id);
};
+
cx.editor.apply_motion(motion)
}
@@ -5444,6 +5447,8 @@ fn reverse_selection_contents(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);
@@ -5451,42 +5456,52 @@ 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);
}
}
};
+
cx.editor.apply_motion(motion);
}
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);
}
};
+
cx.editor.apply_motion(motion);
}
@@ -5605,8 +5620,6 @@ fn match_brackets(cx: &mut Context) {
doc.set_selection(view.id, selection);
}
-//
-
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 04b7703c..c0672001 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -1320,15 +1320,27 @@ 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(..)));
+
helix_event::dispatch(SelectionDidChange {
doc: self,
view: view_id,
- })
+ });
+
+ 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
@@ -1520,6 +1532,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());
+
helix_event::dispatch(DocumentDidChange {
doc: self,
view: view_id,
@@ -1962,13 +1980,13 @@ impl Document {
&self.selections
}
- fn view_data(&self, view_id: ViewId) -> &ViewData {
+ pub fn view_data(&self, view_id: ViewId) -> &ViewData {
self.view_data
.get(&view_id)
.expect("This should only be called after ensure_view_init")
}
- fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData {
+ pub fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData {
self.view_data.entry(view_id).or_default()
}
@@ -2286,9 +2304,13 @@ impl Document {
}
}
+/// Stores data needed for views that are tied to this specific Document.
#[derive(Debug, Default)]
pub struct ViewData {
view_position: ViewPosition,
+
+ /// used to store previous selections of tree-sitter objects
+ pub object_selections: HashMap<&'static str, Vec<Selection>>,
}
#[derive(Clone, Debug)]
@@ -2339,6 +2361,7 @@ mod test {
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
);
let view = ViewId::default();
+ doc.ensure_view_init(view);
doc.set_selection(view, Selection::single(0, 0));
let transaction =
@@ -2377,7 +2400,9 @@ mod test {
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
Arc::new(ArcSwap::from_pointee(syntax::Loader::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 aecf09a6..d275befb 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -137,8 +137,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<DocumentId>; 2],
- /// used to store previous selections of tree-sitter objects
- pub object_selections: Vec<Selection>,
/// 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.
@@ -175,7 +173,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(),
diagnostics_handler: DiagnosticsHandler::new(),