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.

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 Mar 9, 2023
1 parent 6a5a36d commit 41aedc5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 24 deletions.
51 changes: 32 additions & 19 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -4295,51 +4299,62 @@ 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);

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

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

path: Option<PathBuf>,
encoding: &'static encoding::Encoding,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<PathBuf> {
self.path
.as_deref()
Expand Down Expand Up @@ -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<Selection>>,
}

#[derive(Clone, Debug)]
pub enum FormatterError {
SpawningFailed {
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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
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 @@ -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<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 @@ -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(),
}
Expand Down

0 comments on commit 41aedc5

Please sign in to comment.