strop_engine/editor/
transact.rs1mod capability;
3pub use capability::{BufferEdit, DocumentEdit};
4use strop_core::id::{BufferRevision, DocumentId};
5use strop_core::{Change, EditError, Replacement};
6
7#[derive(Debug, Clone)]
9pub struct ChangeSet {
10 pub edits: Vec<Replacement>,
11 pub undo_open: bool,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
15pub enum ApplyError {
16 #[error("no such document")]
17 NoDocument,
18 #[error(transparent)]
19 Edit(#[from] EditError),
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct Committed {
24 pub revision: BufferRevision,
25}
26
27impl super::Editor {
28 pub(crate) fn apply(
29 &mut self,
30 document: DocumentId,
31 base: BufferRevision,
32 changes: ChangeSet,
33 ) -> Result<Committed, ApplyError> {
34 let buffer = &self.docs.get(document).ok_or(ApplyError::NoDocument)?.buf;
35 let prepared = buffer.prepare_replacements(base, changes.edits)?;
36 if prepared.is_empty() {
37 return Ok(Committed { revision: base });
38 }
39 if self
40 .pending
41 .prompt()
42 .is_some_and(|prompt| prompt.origin().pane.doc == document)
43 {
44 self.cancel_pending();
45 }
46 let revision = self
47 .doc_mut(document)
48 .buf
49 .apply_prepared(prepared, changes.undo_open)?;
50 Ok(Committed { revision })
51 }
52
53 pub(crate) fn replace_system(
54 &mut self,
55 document: DocumentId,
56 text: &str,
57 ) -> Result<(), EditError> {
58 self.doc_mut(document).buf.system_edit().replace_all(text)
59 }
60
61 pub(crate) fn tx_begin(&mut self) {
62 self.buf_mut().begin_undo_group();
63 }
64 pub(crate) fn tx_commit(&mut self) {
65 self.buf_mut().commit_undo_group();
66 strop_trace::record_with(strop_trace::EventKind::History, || {
67 serde_json::json!({
68 "operation":"commit","buffer":self.buf().trace_id(),"document":self.current(),
69 "revision":self.buf().revision(),"history_nodes":self.buf().history().depth(),
70 })
71 });
72 }
73
74 pub(super) fn sync_document(&mut self, id: DocumentId, map_active: bool) {
78 self.sync_document_positions(id, map_active, map_position);
79 }
80
81 fn sync_document_positions(
82 &mut self,
83 id: DocumentId,
84 map_active: bool,
85 position: impl Fn(usize, &Change) -> usize,
86 ) {
87 let Some(document) = self.docs.get_mut(id) else {
88 return;
89 };
90 if document.buf.changes().is_empty() {
91 return;
92 }
93 let active = self.active_pane;
94 for change in document.buf.changes() {
95 let map = |offset| position(offset, change);
96 for (owner, position) in self.marks.values_mut() {
97 if *owner == id {
98 *position = map(*position);
99 }
100 }
101 for (owner, position) in self
102 .jumplist_past
103 .iter_mut()
104 .chain(self.jumplist_future.iter_mut())
105 {
106 if *owner == id {
107 *position = map(*position);
108 }
109 }
110 for (index, pane) in self.panes.iter_mut().enumerate() {
111 if pane.doc == id && (map_active || index != active) {
112 pane.sels.map_positions(map);
113 }
114 }
115 for collection in self.collections.values_mut() {
117 for excerpt in &mut collection.excerpts {
118 if excerpt.source == id {
119 excerpt.start = map(excerpt.start);
120 excerpt.end = map(excerpt.end);
121 }
122 }
123 }
124 }
125 self.analysis.edits(id, document.buf.changes());
126 document.buf.clear_changes();
127 }
128}
129
130fn map_position(position: usize, change: &Change) -> usize {
131 let edit = change.edit;
132 debug_assert!(edit.start_byte <= edit.old_end_byte);
133 debug_assert!(edit.new_end_byte >= edit.start_byte);
137 strop_core::editmap::map_position(
138 position,
139 edit.start_byte,
140 edit.old_end_byte,
141 edit.new_end_byte,
142 )
143}