use super::Editor;
use std::path::PathBuf;
use strop_core::id::DocumentId;
impl Editor {
pub(crate) fn collection_undo(&mut self) {
let id = self.current();
let Some(sources) = self
.collections
.get(&id)
.map(|c| c.excerpts.iter().map(|e| e.source).collect::<Vec<_>>())
else {
return;
};
let Some((index, mut receipt)) = self.changes.take_newest_matching(|receipt| {
receipt.producer == "collection edit"
&& receipt
.applied
.iter()
.any(|(document, ..)| sources.contains(document))
}) else {
self.message = "already at oldest change".into();
return;
};
let mut moved = 0;
for (document, ..) in &receipt.applied {
let refusal = match self.docs.get(*document) {
Some(doc) => match doc.buf.check_undo() {
Ok(true) => None,
Ok(false) => Some(format!("{}: no undo remains", doc.label(&self.cwd))),
Err(error) => Some(format!("{}: {error}", doc.label(&self.cwd))),
},
None => Some("source buffer was closed".into()),
};
if let Some(reason) = refusal {
self.changes.restore(index, receipt);
self.message = format!("collection undo refused: {reason}; receipt retained");
return;
}
}
for (member, (document, ..)) in receipt.applied.iter().enumerate() {
match self.docs.get(*document) {
Some(doc)
if doc.buf.history().committed_position()
== receipt.applied_positions.get(member).copied() => {}
Some(_) => {
let name = self
.docs
.get(*document)
.and_then(|d| d.buf.path.clone())
.map(|p| p.display().to_string())
.unwrap_or_else(|| "a source".into());
self.changes.restore(index, receipt);
self.message =
format!("collection undo refused: {name} changed since — resolve it first");
return;
}
None => {
self.changes.restore(index, receipt);
self.message = "collection undo refused: a source buffer was closed".into();
return;
}
}
}
let mut positions = Vec::with_capacity(receipt.applied.len());
for (document, ..) in &receipt.applied {
let undone_ok = matches!(self.doc_mut(*document).buf.undo(), Ok(Some(_)));
if undone_ok {
moved += 1;
}
if let Some(position) = self
.docs
.get(*document)
.and_then(|doc| doc.buf.history().committed_position())
{
positions.push(position);
}
}
receipt.redo_positions = Some(positions);
self.changes.push_undone(receipt);
self.message = format!("undid collection edit across {moved} buffer(s)");
}
pub(crate) fn collection_redo(&mut self) {
let id = self.current();
let Some(sources) = self
.collections
.get(&id)
.map(|c| c.excerpts.iter().map(|e| e.source).collect::<Vec<_>>())
else {
return;
};
let Some(receipt) = self.changes.take_undone_matching(|receipt| {
receipt.producer == "collection edit"
&& receipt
.applied
.iter()
.any(|(document, ..)| sources.contains(document))
}) else {
self.message = "nothing to redo".into();
return;
};
let positions = receipt.redo_positions.as_ref();
for (member, (document, ..)) in receipt.applied.iter().enumerate() {
let refusal = match self.docs.get(*document) {
Some(doc) => match doc
.buf
.check_restore_revision(receipt.applied_positions[member])
{
Ok(true) => None,
Ok(false) => Some(format!(
"{}: redo target is unavailable",
doc.label(&self.cwd)
)),
Err(error) => Some(format!("{}: {error}", doc.label(&self.cwd))),
},
None => Some("source buffer was closed".into()),
};
if let Some(reason) = refusal {
self.changes.push_undone(receipt);
self.message = format!("collection redo refused: {reason}; receipt retained");
return;
}
}
for (member, (document, ..)) in receipt.applied.iter().enumerate() {
let at = self
.docs
.get(*document)
.and_then(|doc| doc.buf.history().committed_position());
if at.is_none() || at != positions.and_then(|p| p.get(member)).copied() {
self.changes.push_undone(receipt);
self.message = "collection redo refused: a source changed since the undo".into();
return;
}
}
let mut moved = 0;
for (member, (document, ..)) in receipt.applied.iter().enumerate() {
if matches!(
self.doc_mut(*document)
.buf
.restore_revision(receipt.applied_positions[member]),
Ok(Some(_))
) {
moved += 1;
}
}
self.changes.push_receipt_back(receipt);
self.message = format!("redid collection edit across {moved} buffer(s)");
}
}
impl Editor {
pub(crate) fn collection_save(&mut self, target: Option<PathBuf>, force: bool, close: bool) {
let id = self.current();
if target.is_some() {
self.message = "a collection has no file of its own — :w saves its sources; exporting the view is unsupported"
.into();
return;
}
let Some(sources) = self.collections.get(&id).map(|c| {
let mut seen: Vec<DocumentId> = c.excerpts.iter().map(|e| e.source).collect();
seen.dedup();
seen
}) else {
return;
};
let mut pending = std::collections::HashSet::new();
let mut refused: Vec<String> = Vec::new();
for source in sources {
let Some(doc) = self.docs.get(source) else {
continue;
};
if !doc.buf.dirty {
continue;
}
let name = doc.label(&self.cwd);
if self.request_save_document(source, None, force, false) {
pending.insert(source);
} else {
refused.push(format!("{name}: {}", self.message));
}
}
let queued = pending.len();
if let Some(collection) = self.collections.get_mut(&id) {
collection.pending_saves = pending;
collection.close_when_saved = close && refused.is_empty() && queued > 0;
}
if queued == 0 && refused.is_empty() {
if close {
self.close_pane_or_buffer(false);
} else {
self.message = "collection: all sources are saved".into();
}
return;
}
if !refused.is_empty() {
self.message = format!(
"collection: saving {queued} source(s); refused: {}",
refused.join(", ")
);
} else {
self.message = format!("collection: saving {queued} source(s)");
}
}
pub(crate) fn collection_save_progress(&mut self, document: DocumentId, saved: bool) {
let mut close: Option<DocumentId> = None;
for (id, collection) in self.collections.iter_mut() {
if !collection.pending_saves.remove(&document) {
continue;
}
if !saved {
collection.pending_saves.clear();
collection.close_when_saved = false;
continue;
}
if collection.pending_saves.is_empty() && collection.close_when_saved {
close = Some(*id);
}
}
if let Some(id) = close {
if self.current() == id {
self.close_pane_or_buffer(false);
} else if let Some(collection) = self.collections.get_mut(&id) {
collection.close_when_saved = false;
self.message = "collection: sources saved".into();
}
}
}
}