From a0e086340e439cdd242daf8b80e1c58d979b622f Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 28 Aug 2022 14:36:41 -0500 Subject: [PATCH] Track document changes in the jumplist Previously, the jumplist was not updated by changes to the document. This meant that the jumplist entries were prone to being incorrect after some edits and that usages of the jumplist were prone to panics if, for example, some edits truncated the document. This change applies `Transaction`s (document edits) to the selections stored in any jumplists. --- helix-term/src/commands.rs | 11 +++++------ helix-term/src/ui/mod.rs | 4 +--- helix-view/src/document.rs | 18 +++++++++++++++++- helix-view/src/editor.rs | 3 +-- helix-view/src/view.rs | 15 +++++++++------ 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 16f7e601231f..f224711473eb 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2346,7 +2346,7 @@ fn jumplist_picker(cx: &mut Context) { view.jumps .get() .iter() - .map(|(doc_id, selection)| new_meta(view, *doc_id, selection.clone())) + .map(|(doc_id, selection)| new_meta(view, *doc_id, selection.borrow().clone())) }) .collect(), (), @@ -2644,9 +2644,8 @@ fn try_restore_indent(doc: &mut Document, view_id: ViewId) { } // Store a jump on the jumplist. -fn push_jump(view: &mut View, doc: &Document) { - let jump = (doc.id(), doc.selection(view.id).clone()); - view.jumps.push(jump); +fn push_jump(view: &mut View, doc: &mut Document) { + view.jumps.push(doc, doc.selection(view.id).clone()); } fn goto_line(cx: &mut Context) { @@ -4048,7 +4047,7 @@ fn jump_forward(cx: &mut Context) { if let Some((id, selection)) = view.jumps.forward(count) { view.doc = *id; - let selection = selection.clone(); + let selection = selection.borrow().clone(); let (view, doc) = current!(cx.editor); // refetch doc doc.set_selection(view.id, selection); @@ -4062,7 +4061,7 @@ fn jump_backward(cx: &mut Context) { if let Some((id, selection)) = view.jumps.backward(view.id, doc, count) { view.doc = *id; - let selection = selection.clone(); + let selection = selection.borrow().clone(); let (view, doc) = current!(cx.editor); // refetch doc doc.set_selection(view.id, selection); diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 560e91558846..64a32396dff5 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -69,7 +69,6 @@ pub fn regex_prompt( fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static, ) { let (view, doc) = current!(cx.editor); - let doc_id = view.doc; let snapshot = doc.selection(view.id).clone(); let offset_snapshot = view.offset; let config = cx.editor.config(); @@ -109,8 +108,7 @@ pub fn regex_prompt( doc.set_selection(view.id, snapshot.clone()); if event == PromptEvent::Validate { - // Equivalent to push_jump to store selection just before jump - view.jumps.push((doc_id, snapshot.clone())); + view.jumps.push(doc, snapshot.clone()); } fun(view, doc, regex, event); diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index c96f222de856..72c2a4a9283d 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -5,11 +5,12 @@ use helix_core::auto_pairs::AutoPairs; use helix_core::Range; use serde::de::{self, Deserialize, Deserializer}; use serde::Serialize; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::fmt::Display; use std::future::Future; use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; @@ -86,6 +87,7 @@ pub struct Document { pub(crate) id: DocumentId, text: Rope, selections: HashMap, + jump_selections: Vec>>, path: Option, encoding: &'static encoding::Encoding, @@ -347,6 +349,7 @@ impl Document { encoding, text, selections: HashMap::default(), + jump_selections: vec![Rc::new(RefCell::new(Selection::point(0)))], indent_style: DEFAULT_INDENT, line_ending: DEFAULT_LINE_ENDING, mode: Mode::Normal, @@ -744,6 +747,14 @@ impl Document { } self.modified_since_accessed = true; + + for selection in &self.jump_selections { + let mut selection = selection.borrow_mut(); + *selection = selection + .clone() + .map(transaction.changes()) + .ensure_invariants(self.text.slice(..)); + } } if !transaction.changes().is_empty() { @@ -1075,6 +1086,11 @@ impl Document { None => global_config, } } + + #[inline] + pub fn push_jump_selection(&mut self, selection: Rc>) { + self.jump_selections.push(selection) + } } impl Default for Document { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 520a425c18ac..70ac6336e540 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -905,8 +905,7 @@ impl Editor { view.remove_document(&id); } } else { - let jump = (view.doc, doc.selection(view.id).clone()); - view.jumps.push(jump); + view.jumps.push(doc, doc.selection(view.id).clone()); // Set last accessed doc if it is a different document if doc.id != id { view.add_to_history(view.doc); diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 3df533dfc6d9..855e9a494c73 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -5,9 +5,9 @@ use crate::{ }; use helix_core::{pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection}; -use std::fmt; +use std::{cell::RefCell, fmt, rc::Rc}; -type Jump = (DocumentId, Selection); +type Jump = (DocumentId, Rc>); #[derive(Debug, Clone)] pub struct JumpList { @@ -23,7 +23,11 @@ impl JumpList { } } - pub fn push(&mut self, jump: Jump) { + pub fn push(&mut self, doc: &mut Document, selection: Selection) { + let selection = Rc::new(RefCell::new(selection)); + let jump = (doc.id, selection.clone()); + doc.push_jump_selection(selection); + self.jumps.truncate(self.current); // don't push duplicates if self.jumps.last() != Some(&jump) { @@ -45,8 +49,7 @@ impl JumpList { pub fn backward(&mut self, view_id: ViewId, doc: &mut Document, count: usize) -> Option<&Jump> { if let Some(current) = self.current.checked_sub(count) { if self.current == self.jumps.len() { - let jump = (doc.id(), doc.selection(view_id).clone()); - self.push(jump); + self.push(doc, doc.selection(view_id).clone()); } self.current = current; self.jumps.get(self.current) @@ -126,7 +129,7 @@ impl View { doc, offset: Position::new(0, 0), area: Rect::default(), // will get calculated upon inserting into tree - jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel + jumps: JumpList::new((doc, Rc::new(RefCell::new(Selection::point(0))))), docs_access_history: Vec::new(), last_modified_docs: [None, None], object_selections: Vec::new(),