mod capability;
pub use capability::{BufferEdit, DocumentEdit};
use strop_core::id::{BufferRevision, DocumentId};
use strop_core::{Change, EditError, Replacement};
#[derive(Debug, Clone)]
pub struct ChangeSet {
pub edits: Vec<Replacement>,
pub undo_open: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ApplyError {
#[error("no such document")]
NoDocument,
#[error(transparent)]
Edit(#[from] EditError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Committed {
pub revision: BufferRevision,
}
impl super::Editor {
pub(crate) fn apply(
&mut self,
document: DocumentId,
base: BufferRevision,
changes: ChangeSet,
) -> Result<Committed, ApplyError> {
let buffer = &self.docs.get(document).ok_or(ApplyError::NoDocument)?.buf;
let prepared = buffer.prepare_replacements(base, changes.edits)?;
if prepared.is_empty() {
return Ok(Committed { revision: base });
}
if self
.pending
.prompt()
.is_some_and(|prompt| prompt.origin().pane.doc == document)
{
self.cancel_pending();
}
let revision = self
.doc_mut(document)
.buf
.apply_prepared(prepared, changes.undo_open)?;
Ok(Committed { revision })
}
pub(crate) fn replace_system(
&mut self,
document: DocumentId,
text: &str,
) -> Result<(), EditError> {
self.doc_mut(document).buf.system_edit().replace_all(text)
}
pub(crate) fn tx_begin(&mut self) {
self.buf_mut().begin_undo_group();
}
pub(crate) fn tx_commit(&mut self) {
self.buf_mut().commit_undo_group();
strop_trace::record_with(strop_trace::EventKind::History, || {
serde_json::json!({
"operation":"commit","buffer":self.buf().trace_id(),"document":self.current(),
"revision":self.buf().revision(),"history_nodes":self.buf().history().depth(),
})
});
}
pub(super) fn sync_document(&mut self, id: DocumentId, map_active: bool) {
self.sync_document_positions(id, map_active, map_position);
}
fn sync_document_positions(
&mut self,
id: DocumentId,
map_active: bool,
position: impl Fn(usize, &Change) -> usize,
) {
if self.collections.contains_key(&id)
&& self.docs.get(id).is_some_and(|doc| {
doc.buf
.changes()
.iter()
.any(|c| c.origin == strop_core::ChangeOrigin::User)
})
{
self.sync_collection_write_back(id, map_active, &position);
return;
}
let change_count = self.docs.get(id).map_or(0, |doc| doc.buf.changes().len());
if change_count == 0 {
return;
}
for index in 0..change_count {
let change = self.doc(id).buf.changes()[index];
self.map_navigation_records(id, |offset| position(offset, &change));
}
let clean_views: std::collections::HashSet<DocumentId> = self
.collections
.iter()
.filter(|(cid, collection)| {
self.docs
.get(**cid)
.is_some_and(|doc| doc.buf.revision() == collection.revision)
})
.map(|(cid, _)| *cid)
.collect();
let collection_updates = self.prepare_collection_updates(id, &clean_views);
let Some(document) = self.docs.get_mut(id) else {
return;
};
if document.buf.changes().is_empty() {
return;
}
let active = self.active_pane;
for change in document.buf.changes() {
let map = |offset| position(offset, change);
for (owner, position) in self.marks.values_mut() {
if *owner == id {
*position = map(*position);
}
}
for (index, pane) in self.panes.iter_mut().enumerate() {
if pane.doc == id && (map_active || index != active) {
pane.sels.map_positions(map);
}
}
for collection in self.collections.values_mut() {
for excerpt in &mut collection.excerpts {
if excerpt.source == id {
let (s, e) = (change.edit.start_byte, change.edit.old_end_byte);
let new_len = change.edit.new_end_byte - change.edit.start_byte;
let delta = new_len as isize - (e - s) as isize;
let shift = |p: usize| (p as isize + delta) as usize;
let insertion = s == e;
excerpt.start = if excerpt.start <= s {
excerpt.start
} else if excerpt.start >= e {
shift(excerpt.start)
} else {
s
};
excerpt.end = if excerpt.end < s {
excerpt.end
} else if excerpt.end > e || (excerpt.end == e && !insertion) {
shift(excerpt.end)
} else {
s + new_len
};
for anchor in &mut excerpt.hit_anchors {
*anchor = map(*anchor);
}
excerpt.matches.retain_mut(|(start, length)| {
let finish = start.saturating_add(*length);
if s < finish && e > *start {
return false;
}
let mapped = map(*start);
*length = map(finish).saturating_sub(mapped);
*start = mapped;
true
});
}
}
}
}
self.analysis.edits(id, document.buf.changes());
document.buf.clear_changes();
self.publish_collection_updates(collection_updates);
}
}
fn map_position(position: usize, change: &Change) -> usize {
let edit = change.edit;
debug_assert!(edit.start_byte <= edit.old_end_byte);
debug_assert!(edit.new_end_byte >= edit.start_byte);
strop_core::editmap::map_position(
position,
edit.start_byte,
edit.old_end_byte,
edit.new_end_byte,
)
}