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.
pull/6198/head
Skyler Hawthorne 2 years ago
parent 96cb28f4bf
commit da261724d1

@ -3662,6 +3662,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);
@ -3673,6 +3674,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);
@ -3731,6 +3733,7 @@ fn goto_prev_diag(cx: &mut Context) {
view.diagnostics_handler
.immediately_show_diagnostic(doc, view.id);
};
cx.editor.apply_motion(motion)
}
@ -5029,6 +5032,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);
@ -5036,42 +5041,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);
}
@ -5190,8 +5205,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();

@ -1213,15 +1213,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
@ -1408,6 +1420,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,
@ -1834,13 +1852,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()
}
@ -2134,9 +2152,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)]
@ -2188,6 +2210,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 =
@ -2225,7 +2248,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

@ -138,8 +138,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.
@ -176,7 +174,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(),

Loading…
Cancel
Save