Skip to content

Commit

Permalink
Generalize View::object_selections into map
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dead10ck committed Sep 18, 2023
1 parent b50be1e commit 057d00d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 23 deletions.
50 changes: 32 additions & 18 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3277,6 +3277,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);
}

Expand All @@ -3286,6 +3287,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);
}

Expand All @@ -3306,6 +3308,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);
}

Expand All @@ -3329,6 +3332,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);
}

Expand Down Expand Up @@ -4658,49 +4662,61 @@ 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);

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);
}

Expand Down Expand Up @@ -4794,8 +4810,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();
Expand Down
42 changes: 40 additions & 2 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ pub struct Document {
pub(crate) id: DocumentId,
text: Rope,
selections: HashMap<ViewId, Selection>,
view_data: HashMap<ViewId, ViewData>,

/// Inlay hints annotations for the document, by view.
///
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -656,6 +658,7 @@ impl Document {
selections: HashMap::default(),
inlay_hints: HashMap::default(),
inlay_hints_oudated: false,
view_data: Default::default(),
indent_style: DEFAULT_INDENT,
line_ending,
restore_cursor: false,
Expand Down Expand Up @@ -1097,11 +1100,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
Expand Down Expand Up @@ -1259,6 +1273,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() {
Expand Down Expand Up @@ -1653,6 +1673,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<PathBuf> {
self.path
.as_deref()
Expand Down Expand Up @@ -1827,6 +1855,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<Selection>>,
}

#[derive(Clone, Debug)]
pub enum FormatterError {
SpawningFailed {
Expand Down Expand Up @@ -1876,6 +1911,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 =
Expand Down Expand Up @@ -1913,7 +1949,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
Expand Down
3 changes: 0 additions & 3 deletions helix-view/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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.
Expand Down Expand Up @@ -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(),
}
Expand Down

0 comments on commit 057d00d

Please sign in to comment.