aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src
diff options
context:
space:
mode:
authorPascal Kuthe2023-03-02 20:56:55 +0000
committerBlaž Hrastnik2023-03-09 04:01:02 +0000
commite8898fd9a8ac8120827fb2d6f4752b3cb2431a62 (patch)
treebc6abc2aa663c7c31fa006bf14d3a59cc9269191 /helix-view/src
parent2588fa3710921683c16a84ffd91103a0823a033b (diff)
store multiple snapshots on the document at once
Fixing autocomplete required moving the document savepoint before the asynchronous completion request. However, this in turn causes new bugs: If the completion popup is open, the savepoint is restored when the popup closes (or another entry is selected). However, at that point a new completion request might already have been created which would have replaced the new savepoint (therefore leading to incorrectly applied complies). This commit fixes that bug by allowing in arbitrary number of savepoints to be tracked on the document. The savepoints are reference counted and therefore remain valid as long as any reference to them remains. Weak reference are stored on the document and any reference that can not be upgraded anymore (hence no strong reference remain) are automatically discarded.
Diffstat (limited to 'helix-view/src')
-rw-r--r--helix-view/src/document.rs69
1 files changed, 54 insertions, 15 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 13ffe794..db12fb92 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -9,6 +9,7 @@ use helix_core::text_annotations::TextAnnotations;
use helix_core::Range;
use helix_vcs::{DiffHandle, DiffProviderRegistry};
+use ::parking_lot::Mutex;
use serde::de::{self, Deserialize, Deserializer};
use serde::Serialize;
use std::borrow::Cow;
@@ -18,7 +19,7 @@ use std::fmt::Display;
use std::future::Future;
use std::path::{Path, PathBuf};
use std::str::FromStr;
-use std::sync::Arc;
+use std::sync::{Arc, Weak};
use std::time::SystemTime;
use helix_core::{
@@ -105,6 +106,13 @@ pub struct DocumentSavedEvent {
pub type DocumentSavedEventResult = Result<DocumentSavedEvent, anyhow::Error>;
pub type DocumentSavedEventFuture = BoxFuture<'static, DocumentSavedEventResult>;
+#[derive(Debug)]
+pub struct SavePoint {
+ /// The view this savepoint is associated with
+ pub view: ViewId,
+ revert: Mutex<Transaction>,
+}
+
pub struct Document {
pub(crate) id: DocumentId,
text: Rope,
@@ -136,7 +144,7 @@ pub struct Document {
pub history: Cell<History>,
pub config: Arc<dyn DynAccess<Config>>,
- pub savepoint: Option<Transaction>,
+ savepoints: Vec<Weak<SavePoint>>,
// Last time we wrote to the file. This will carry the time the file was last opened if there
// were no saves.
@@ -389,7 +397,7 @@ impl Document {
diagnostics: Vec::new(),
version: 0,
history: Cell::new(History::default()),
- savepoint: None,
+ savepoints: Vec::new(),
last_saved_time: SystemTime::now(),
last_saved_revision: 0,
modified_since_accessed: false,
@@ -846,11 +854,18 @@ impl Document {
}
// generate revert to savepoint
- if self.savepoint.is_some() {
- take_with(&mut self.savepoint, |prev_revert| {
- let revert = transaction.invert(&old_doc);
- Some(revert.compose(prev_revert.unwrap()))
- });
+ if !self.savepoints.is_empty() {
+ let revert = transaction.invert(&old_doc);
+ self.savepoints
+ .retain_mut(|save_point| match save_point.upgrade() {
+ Some(savepoint) => {
+ let mut revert_to_savepoint = savepoint.revert.lock();
+ *revert_to_savepoint =
+ revert.clone().compose(mem::take(&mut revert_to_savepoint));
+ true
+ }
+ None => false,
+ })
}
// update tree-sitter syntax tree
@@ -940,15 +955,39 @@ impl Document {
self.undo_redo_impl(view, false)
}
- pub fn savepoint(&mut self) {
- self.savepoint =
- Some(Transaction::new(self.text()).with_selection(self.selection(view.id).clone()));
+ /// Creates a reference counted snapshot (called savpepoint) of the document.
+ ///
+ /// The snapshot will remain valid (and updated) idenfinitly as long as ereferences to it exist.
+ /// Restoring the snapshot will restore the selection and the contents of the document to
+ /// the state it had when this function was called.
+ pub fn savepoint(&mut self, view: &View) -> Arc<SavePoint> {
+ let revert = Transaction::new(self.text()).with_selection(self.selection(view.id).clone());
+ let savepoint = Arc::new(SavePoint {
+ view: view.id,
+ revert: Mutex::new(revert),
+ });
+ self.savepoints.push(Arc::downgrade(&savepoint));
+ savepoint
}
- pub fn restore(&mut self, view: &mut View) {
- if let Some(revert) = self.savepoint.take() {
- self.apply(&revert, view.id);
- }
+ pub fn restore(&mut self, view: &mut View, savepoint: &SavePoint) {
+ assert_eq!(
+ savepoint.view, view.id,
+ "Savepoint must not be used with a different view!"
+ );
+ // search and remove savepoint using a ptr comparison
+ // this avoids a deadlock as we need to lock the mutex
+ let savepoint_idx = self
+ .savepoints
+ .iter()
+ .position(|savepoint_ref| savepoint_ref.as_ptr() == savepoint as *const _)
+ .expect("Savepoint must belong to this document");
+
+ let savepoint_ref = self.savepoints.remove(savepoint_idx);
+ let mut revert = savepoint.revert.lock();
+ self.apply(&revert, view.id);
+ *revert = Transaction::new(self.text()).with_selection(self.selection(view.id).clone());
+ self.savepoints.push(savepoint_ref)
}
fn earlier_later_impl(&mut self, view: &mut View, uk: UndoKind, earlier: bool) -> bool {