use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use crate::action::{Action, ActionError};
use crate::buffer::{Buffer, BufferId, FileMeta, Indent, LineEdit};
use crate::changeset::{Bias, ChangeSet};
use crate::display::DisplayMap;
use crate::fold::{self, FoldSet};
use crate::history::Transaction;
use crate::position::{ByteOffset, byte_to_line_col, line_col_to_byte};
use crate::resolve::{Resolved, resolve, resolve_search};
use crate::selection::{Selection, SelectionSet};
use crate::view::{View, ViewId};
pub trait Clock: Send {
fn now(&self) -> Duration;
}
pub struct SystemClock {
origin: Instant,
}
impl Default for SystemClock {
fn default() -> Self {
Self {
origin: Instant::now(),
}
}
}
impl Clock for SystemClock {
fn now(&self) -> Duration {
self.origin.elapsed()
}
}
#[derive(Clone, Default)]
pub struct ManualClock(Arc<AtomicU64>);
impl ManualClock {
pub fn advance(&self, d: Duration) {
self.0.fetch_add(d.as_millis() as u64, Ordering::Relaxed);
}
}
impl Clock for ManualClock {
fn now(&self) -> Duration {
Duration::from_millis(self.0.load(Ordering::Relaxed))
}
}
#[derive(Clone, Debug)]
pub struct Settings {
pub tab_width: usize,
pub use_spaces: bool,
pub auto_close: bool,
pub smart_indent: bool,
pub overtype: bool,
}
impl Settings {
pub fn indent(&self) -> Indent {
Indent {
use_spaces: self.use_spaces,
width: self.tab_width,
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
tab_width: 4,
use_spaces: false,
auto_close: false,
smart_indent: false,
overtype: false,
}
}
}
pub struct Editor {
buffers: BTreeMap<BufferId, Buffer>,
views: BTreeMap<ViewId, View>,
next_id: u64,
pub settings: Settings,
clock: Box<dyn Clock>,
recording: Option<Vec<Action>>,
}
impl Default for Editor {
fn default() -> Self {
Self::with_clock(Box::new(SystemClock::default()))
}
}
impl Editor {
pub fn new() -> Self {
Self::default()
}
pub fn with_clock(clock: Box<dyn Clock>) -> Self {
Self {
buffers: BTreeMap::new(),
views: BTreeMap::new(),
next_id: 1,
settings: Settings::default(),
clock,
recording: None,
}
}
fn fresh_id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn add_buffer(&mut self, text: &str, meta: FileMeta) -> BufferId {
let id = BufferId(self.fresh_id());
let mut buffer = Buffer::new(id, text, meta);
buffer.indent = self.settings.indent();
self.buffers.insert(id, buffer);
id
}
pub fn add_view(&mut self, buffer: BufferId) -> Result<ViewId, ActionError> {
if !self.buffers.contains_key(&buffer) {
return Err(ActionError::UnknownBuffer(buffer));
}
let id = ViewId(self.fresh_id());
self.views.insert(id, View::new(id, buffer));
Ok(id)
}
pub fn remove_view(&mut self, view: ViewId) -> Option<View> {
self.views.remove(&view)
}
pub fn remove_buffer(&mut self, buffer: BufferId) -> Option<Buffer> {
self.views.retain(|_, v| v.buffer() != buffer);
self.buffers.remove(&buffer)
}
pub fn buffer(&self, id: BufferId) -> Option<&Buffer> {
self.buffers.get(&id)
}
pub fn buffer_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
self.buffers.get_mut(&id)
}
pub fn view(&self, id: ViewId) -> Option<&View> {
self.views.get(&id)
}
pub fn view_mut(&mut self, id: ViewId) -> Option<&mut View> {
self.views.get_mut(&id)
}
pub fn buffers(&self) -> impl Iterator<Item = &Buffer> {
self.buffers.values()
}
pub fn buffers_mut(&mut self) -> impl Iterator<Item = &mut Buffer> {
self.buffers.values_mut()
}
pub fn views(&self) -> impl Iterator<Item = &View> {
self.views.values()
}
pub fn views_of(&self, buffer: BufferId) -> impl Iterator<Item = &View> {
self.views
.values()
.filter(move |v| v.buffer() == buffer && !v.is_aux())
}
pub fn add_aux_view(&mut self, buffer: BufferId) -> Result<ViewId, ActionError> {
let id = self.add_view(buffer)?;
if let Some(v) = self.views.get_mut(&id) {
v.set_aux();
}
Ok(id)
}
pub fn start_recording(&mut self) {
self.recording = Some(Vec::new());
}
pub fn is_recording(&self) -> bool {
self.recording.is_some()
}
pub fn stop_recording(&mut self) -> Vec<Action> {
self.recording.take().unwrap_or_default()
}
pub fn recorded(&self) -> Option<&[Action]> {
self.recording.as_deref()
}
pub(crate) fn take_recording(&mut self) -> Option<Vec<Action>> {
self.recording.take()
}
pub(crate) fn restore_recording(&mut self, recording: Option<Vec<Action>>) {
self.recording = recording;
}
pub fn begin_undo_group(&mut self, view: ViewId) -> Result<(), ActionError> {
let (buffer, _) = self.lookup(view)?;
self.buffers
.get_mut(&buffer)
.ok_or(ActionError::UnknownBuffer(buffer))?
.history
.begin_group();
Ok(())
}
pub fn end_undo_group(&mut self, view: ViewId) -> Result<(), ActionError> {
let (buffer, _) = self.lookup(view)?;
self.buffers
.get_mut(&buffer)
.ok_or(ActionError::UnknownBuffer(buffer))?
.history
.end_group();
Ok(())
}
fn lookup(&self, view: ViewId) -> Result<(BufferId, &View), ActionError> {
let v = self
.views
.get(&view)
.ok_or(ActionError::UnknownView(view))?;
if !self.buffers.contains_key(&v.buffer()) {
return Err(ActionError::UnknownBuffer(v.buffer()));
}
Ok((v.buffer(), v))
}
pub fn apply(&mut self, view: ViewId, action: Action) -> Result<(), ActionError> {
let (buffer_id, _) = self.lookup(view)?;
let read_only = self.buffers.get(&buffer_id).is_some_and(|b| b.read_only);
match &action {
Action::Undo | Action::Redo if read_only => return Err(ActionError::ReadOnly),
Action::Undo => self.undo(view, buffer_id),
Action::Redo => self.redo(view, buffer_id),
Action::Fold
| Action::Unfold
| Action::ToggleFold
| Action::FoldAll
| Action::UnfoldAll
| Action::FoldLevel(_)
| Action::UnfoldLevel(_) => self.fold_action(view, buffer_id, &action),
Action::ToggleBookmark
| Action::ClearBookmarks
| Action::InverseBookmarks
| Action::BookmarkMatches(_) => self.bookmark_action(view, buffer_id, &action)?,
_ => {
let marked = match action {
Action::ReplaceBookmarkedLines(_) => self
.buffers
.get(&buffer_id)
.map(|b| b.bookmarks.lines().to_vec())
.unwrap_or_default(),
_ => Vec::new(),
};
let resolved = self.resolve_in(view, buffer_id, &action)??;
self.commit(view, buffer_id, resolved)?;
if let Some(b) = self.buffers.get_mut(&buffer_id) {
let count = b.doc.line_count();
match &action {
Action::RemoveBookmarkedLines => b.bookmarks.clear(),
Action::ReplaceBookmarkedLines(text) => b
.bookmarks
.set(crate::bookmarks::after_replace(&marked, text), count),
_ => {}
}
}
if let Action::ConvertIndentation { to_spaces } = action
&& let Some(b) = self.buffers.get_mut(&buffer_id)
{
b.indent.use_spaces = to_spaces;
b.indent_set = true;
b.indent_mixed = false;
}
}
}
self.sync_folds(buffer_id);
if let Some(rec) = &mut self.recording
&& action.is_recordable()
{
rec.push(action);
}
Ok(())
}
pub fn apply_unrecorded(&mut self, view: ViewId, action: Action) -> Result<(), ActionError> {
let recording = self.recording.take();
let result = self.apply(view, action);
self.recording = recording;
result
}
fn resolve_in(
&self,
view: ViewId,
buffer_id: BufferId,
action: &Action,
) -> Result<Result<Resolved, ActionError>, ActionError> {
let buffer = self
.buffers
.get(&buffer_id)
.ok_or(ActionError::UnknownBuffer(buffer_id))?;
let v = self
.views
.get(&view)
.ok_or(ActionError::UnknownView(view))?;
let sels = &v.selections;
Ok(match action {
Action::Find { .. }
| Action::Replace { .. }
| Action::ReplaceAll { .. }
| Action::SelectAllMatches(_) => resolve_search(action, &buffer.doc, sels),
Action::ToggleLineComment
| Action::CommentLines
| Action::UncommentLines
| Action::ToggleBlockComment => {
use crate::transform::CommentOp;
let syntax = buffer.comments.as_ref();
let op = match action {
Action::CommentLines => CommentOp::Add,
Action::UncommentLines => CommentOp::Remove,
_ => CommentOp::Toggle,
};
let resolved = match (action, syntax) {
(Action::ToggleBlockComment, Some(c)) => {
c.block.as_ref().map(|(open, close)| {
crate::transform::toggle_block_comment(&buffer.doc, sels, open, close)
})
}
(_, Some(c)) => crate::transform::comment_lines(&buffer.doc, sels, c, op),
_ => None,
};
resolved.ok_or(ActionError::NoCommentSyntax)
}
Action::MultiSelectNext { .. } | Action::MultiSelectAll { .. } => {
crate::multiselect::resolve(action, &buffer.doc, sels)
}
Action::NextBookmark
| Action::PrevBookmark
| Action::RemoveBookmarkedLines
| Action::RemoveUnbookmarkedLines
| Action::ReplaceBookmarkedLines(_) => {
crate::bookmarks::resolve(action, &buffer.doc, sels, &buffer.bookmarks)
}
Action::FormatJson { .. }
| Action::MinifyJson
| Action::Encode(_)
| Action::Decode(_) => crate::text_tools::resolve(
action,
&buffer.doc,
sels,
buffer.indent,
buffer.meta.line_ending,
),
_ => {
if self.settings.overtype
&& let Some(r) = crate::overtype::resolve(action, &buffer.doc, sels)
{
return Ok(Ok(r));
}
if self.settings.auto_close
&& let Some(r) = crate::autoclose::resolve(action, &buffer.doc, sels)
{
return Ok(Ok(r));
}
if self.settings.smart_indent
&& let Some(r) = crate::smart_indent::resolve(
action,
&buffer.doc,
sels,
buffer.indent,
buffer.language.as_deref(),
)
{
return Ok(Ok(r));
}
let rows = DisplayMap::new(buffer, v);
Ok(resolve(action, &buffer.doc, sels, buffer.indent, &rows))
}
})
}
fn commit(
&mut self,
view: ViewId,
buffer_id: BufferId,
resolved: Resolved,
) -> Result<(), ActionError> {
match resolved {
Resolved::Selections(sels) => {
if let Some(b) = self.buffers.get_mut(&buffer_id) {
b.history.break_coalescing();
}
self.set_selections(view, sels);
}
Resolved::Edit { .. } if self.buffers.get(&buffer_id).is_some_and(|b| b.read_only) => {
return Err(ActionError::ReadOnly);
}
Resolved::Edit {
changes,
after,
kind,
} => {
let now = self.clock.now();
let before = self.views[&view].selections.clone();
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(ActionError::UnknownBuffer(buffer_id));
};
let inverse = changes.invert(&buffer.doc);
let line_edits = apply_changes(buffer, &changes);
let rev_before = buffer.revision;
let rev_after = buffer.next_revision;
buffer.next_revision += 1;
buffer.revision = rev_after;
buffer.history.push(
Transaction {
changes: changes.clone(),
inverse,
before,
after: after.clone(),
},
kind,
rev_before,
rev_after,
now,
);
self.set_selections(view, after);
self.map_other_views(buffer_id, view, &changes);
self.map_folds(buffer_id, &changes, &line_edits);
}
}
Ok(())
}
pub fn reload_buffer(
&mut self,
buffer: BufferId,
text: &str,
meta: FileMeta,
) -> Result<(), ActionError> {
let view = self
.views_of(buffer)
.next()
.map(View::id)
.ok_or(ActionError::UnknownBuffer(buffer))?;
let b = self
.buffers
.get(&buffer)
.ok_or(ActionError::UnknownBuffer(buffer))?;
if b.doc.to_string() != text {
let new_doc = crate::text::Document::new(text);
let remap = |p: ByteOffset| {
let lc = byte_to_line_col(&b.doc, p);
let mut q = line_col_to_byte(&new_doc, lc);
while !new_doc.is_char_boundary(q) {
q.0 -= 1;
}
q
};
let after = self.views[&view]
.selections
.map(|s| Selection::new(remap(s.anchor), remap(s.head)));
let changes = ChangeSet::new(
b.doc.len(),
vec![crate::changeset::Edit::new(
ByteOffset(0),
b.doc.len(),
text,
)],
);
let read_only = self
.buffers
.get_mut(&buffer)
.map(|b| std::mem::replace(&mut b.read_only, false));
let committed = self.commit(
view,
buffer,
Resolved::Edit {
changes,
after,
kind: crate::history::EditKind::Other,
},
);
if let (Some(b), Some(read_only)) = (self.buffers.get_mut(&buffer), read_only) {
b.read_only = read_only;
}
committed?;
}
if let Some(b) = self.buffers.get_mut(&buffer) {
b.meta = meta;
b.mark_saved();
}
self.sync_folds(buffer);
Ok(())
}
fn undo(&mut self, view: ViewId, buffer_id: BufferId) {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return;
};
let Some(step) = buffer.history.pop_undo() else {
return;
};
let inverses: Vec<ChangeSet> = step.txns.iter().rev().map(|t| t.inverse.clone()).collect();
let line_edits: Vec<Vec<LineEdit>> = inverses
.iter()
.map(|inv| apply_changes(buffer, inv))
.collect();
buffer.revision = step.rev_before;
let sels = step.txns[0].before.clone();
buffer.history.push_redo(step);
self.set_selections(view, sels);
for (inv, edits) in inverses.iter().zip(&line_edits) {
self.map_other_views(buffer_id, view, inv);
self.map_folds(buffer_id, inv, edits);
}
}
fn redo(&mut self, view: ViewId, buffer_id: BufferId) {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return;
};
let Some(step) = buffer.history.pop_redo() else {
return;
};
let changes: Vec<ChangeSet> = step.txns.iter().map(|t| t.changes.clone()).collect();
let line_edits: Vec<Vec<LineEdit>> =
changes.iter().map(|cs| apply_changes(buffer, cs)).collect();
buffer.revision = step.rev_after;
let sels = step.txns[step.txns.len() - 1].after.clone();
buffer.history.push_undo(step);
self.set_selections(view, sels);
for (cs, edits) in changes.iter().zip(&line_edits) {
self.map_other_views(buffer_id, view, cs);
self.map_folds(buffer_id, cs, edits);
}
}
fn set_selections(&mut self, view: ViewId, sels: SelectionSet) {
if let Some(v) = self.views.get_mut(&view) {
v.selections = sels;
}
}
fn map_folds(&mut self, buffer: BufferId, changes: &ChangeSet, line_edits: &[LineEdit]) {
for v in self.views.values_mut() {
if v.buffer() != buffer {
continue;
}
if !v.folds.is_empty() {
v.folds.map(changes);
}
v.folds.filter_line_edits(line_edits);
}
}
pub fn append_unrecorded(
&mut self,
buffer_id: BufferId,
text: &str,
) -> Result<(), ActionError> {
let buffer = self
.buffers
.get_mut(&buffer_id)
.ok_or(ActionError::UnknownBuffer(buffer_id))?;
if text.is_empty() {
return Ok(());
}
let end = buffer.doc.len();
let changes = ChangeSet::new(end, vec![crate::changeset::Edit::new(end, end, text)]);
let clean = !buffer.is_dirty();
let line_edits = apply_changes(buffer, &changes);
let (old, new) = (buffer.revision, buffer.next_revision);
buffer.history.extend_base(text.len(), old, new);
buffer.revision = new;
buffer.next_revision += 1;
if clean {
buffer.mark_saved();
}
for v in self.views.values_mut() {
if v.buffer() == buffer_id {
v.selections = v.selections.map(|s| Selection {
anchor: changes.map_position(s.anchor, Bias::After),
head: changes.map_position(s.head, Bias::After),
goal: None,
});
}
}
self.map_folds(buffer_id, &changes, &line_edits);
self.sync_folds(buffer_id);
Ok(())
}
pub fn set_filter(&mut self, view: ViewId, shown: Option<&[usize]>) {
let Some(v) = self.views.get_mut(&view) else {
return;
};
let Some(b) = self.buffers.get(&v.buffer()) else {
return;
};
let doc = &b.doc;
let count = doc.line_count();
let Some(shown) = shown else {
v.folds.set_filter(None, count);
return;
};
let caret_line = [doc.line_of(v.selections.primary().head)];
let in_doc = shown.partition_point(|&l| l < count);
let shown = if in_doc == 0 {
&caret_line[..]
} else {
&shown[..in_doc]
};
v.folds.set_filter(Some(shown), count);
let folds = &v.folds;
let moved = v.selections.map(|s| {
let line = doc.line_of(s.head);
if !folds.filter_hides(line) {
return s.clone();
}
let after = shown.partition_point(|&l| l < line);
let target = shown
.get(after)
.or_else(|| after.checked_sub(1).and_then(|i| shown.get(i)))
.copied()
.unwrap_or(0)
.min(count - 1);
Selection::caret(doc.line_start(target))
});
v.selections = moved;
}
pub fn filter_appended(&mut self, view: ViewId, from: usize, shown: &[usize]) {
let Some(v) = self.views.get_mut(&view) else {
return;
};
let Some(b) = self.buffers.get(&v.buffer()) else {
return;
};
let doc = &b.doc;
let mut lines = shown.to_vec();
lines.extend(
v.selections
.ranges()
.iter()
.map(|s| doc.line_of(s.head))
.filter(|&l| l >= from),
);
lines.sort_unstable();
lines.dedup();
v.folds.set_filter_from(from, &lines, doc.line_count());
}
pub fn caret_lines(&self, view: ViewId) -> Vec<usize> {
let Some(v) = self.views.get(&view) else {
return Vec::new();
};
let Some(b) = self.buffers.get(&v.buffer()) else {
return Vec::new();
};
let mut lines: Vec<usize> = v
.selections
.ranges()
.iter()
.map(|s| b.doc.line_of(s.head))
.collect();
lines.dedup();
lines
}
fn sync_folds(&mut self, buffer: BufferId) {
let Some(doc) = self.buffers.get(&buffer).map(|b| &b.doc) else {
return;
};
for v in self.views.values_mut() {
if v.buffer() != buffer {
continue;
}
v.folds.relines(doc);
if v.folds.has_filter() {
let lines: Vec<usize> = v
.selections
.ranges()
.iter()
.map(|s| doc.line_of(s.head))
.filter(|&l| v.folds.filter_hides(l))
.collect();
v.folds.unfilter(&lines);
}
if v.folds.is_empty() {
continue;
}
let hidden: Vec<usize> = v
.selections
.ranges()
.iter()
.map(|s| doc.line_of(s.head))
.filter(|&l| v.folds.is_hidden(l))
.collect();
if !hidden.is_empty() {
v.folds
.retain(|header, last| !hidden.iter().any(|&l| header < l && l <= last));
}
}
}
pub fn fold_at(&mut self, view: ViewId, line: usize) -> bool {
let Some(v) = self.views.get_mut(&view) else {
return false;
};
let Some(b) = self.buffers.get(&v.buffer()) else {
return false;
};
let tab_width = b.indent.width;
let Some(last) = fold::region(&b.doc, line, b.fold_strategy, tab_width) else {
return false;
};
v.folds.insert(&b.doc, [(line, last)]);
reveal_carets(&b.doc, v);
true
}
pub fn unfold_at(&mut self, view: ViewId, line: usize) -> bool {
self.views
.get_mut(&view)
.is_some_and(|v| v.folds.remove(line))
}
pub fn toggle_fold_at(&mut self, view: ViewId, line: usize) -> bool {
self.unfold_at(view, line) || self.fold_at(view, line)
}
fn fold_action(&mut self, view: ViewId, buffer: BufferId, action: &Action) {
let (Some(v), Some(b)) = (self.views.get_mut(&view), self.buffers.get(&buffer)) else {
return;
};
let tab_width = b.indent.width;
let (doc, strategy) = (&b.doc, b.fold_strategy);
let caret = doc.line_of(v.selections.primary().head);
let around = || fold::enclosing(doc, caret, strategy, tab_width);
match action {
Action::Fold => {
if let Some(r) = around() {
v.folds.insert(doc, [r]);
}
}
Action::ToggleFold => {
if !v.folds.remove(caret)
&& let Some(r) = around()
{
v.folds.insert(doc, [r]);
}
}
Action::Unfold => {
if !v.folds.remove(caret)
&& let Some((header, last)) = around()
{
v.folds.retain(|h, _| h < header || h > last);
}
}
Action::FoldAll => {
let all = fold::regions(doc, strategy, tab_width);
v.folds.insert(doc, all.iter().map(|r| (r.header, r.last)));
}
Action::UnfoldAll => v.folds = FoldSet::default(),
Action::FoldLevel(n) => {
let all = fold::regions(doc, strategy, tab_width);
let level = all.iter().filter(|r| r.level == *n);
v.folds.insert(doc, level.map(|r| (r.header, r.last)));
}
Action::UnfoldLevel(n) => {
let level: std::collections::HashSet<usize> =
fold::regions(doc, strategy, tab_width)
.into_iter()
.filter(|r| r.level == *n)
.map(|r| r.header)
.collect();
v.folds.retain(|h, _| !level.contains(&h));
}
_ => {}
}
reveal_carets(doc, v);
}
fn bookmark_action(
&mut self,
view: ViewId,
buffer: BufferId,
action: &Action,
) -> Result<(), ActionError> {
let (Some(v), Some(b)) = (self.views.get(&view), self.buffers.get_mut(&buffer)) else {
return Err(ActionError::UnknownView(view));
};
let count = b.doc.line_count();
match action {
Action::ToggleBookmark => {
let lines = crate::bookmarks::caret_lines(&b.doc, &v.selections);
if lines.iter().all(|&l| b.bookmarks.contains(l)) {
for l in lines {
b.bookmarks.remove(l);
}
} else {
for l in lines {
b.bookmarks.insert(l);
}
}
}
Action::ClearBookmarks => b.bookmarks.clear(),
Action::InverseBookmarks => b.bookmarks.invert(count),
Action::BookmarkMatches(query) => {
let searcher = crate::search::Searcher::new(query)
.map_err(|e| ActionError::InvalidPattern(e.0))?;
let matches = searcher.find_in(&b.doc, &v.selections);
if matches.is_empty() {
return Err(ActionError::NotFound);
}
let found = matches.iter().map(|m| b.doc.line_of(m.start));
let all: Vec<usize> = b.bookmarks.lines().iter().copied().chain(found).collect();
b.bookmarks.set(all, count);
}
_ => {}
}
Ok(())
}
fn map_other_views(&mut self, buffer: BufferId, except: ViewId, changes: &ChangeSet) {
for v in self.views.values_mut() {
if v.buffer() != buffer {
continue;
}
v.begin_select = v.begin_select.map(|p| changes.map_position(p, Bias::After));
if v.id() != except {
v.selections = v.selections.map(|s| Selection {
anchor: changes.map_position(s.anchor, Bias::Before),
head: changes.map_position(s.head, Bias::Before),
goal: s.goal,
});
}
}
}
}
fn reveal_carets(doc: &crate::text::Document, v: &mut View) {
let folds = &v.folds;
if folds.is_empty() {
return;
}
let moved = v.selections.map(|s| {
let Some((start, _)) = folds.fold_hidden_range(doc.line_of(s.head)) else {
return s.clone();
};
let head = doc.line_end(start - 1);
if s.is_empty() {
Selection::caret(head)
} else {
Selection::new(s.anchor, head)
}
});
v.selections = moved;
}
fn apply_changes(buffer: &mut Buffer, changes: &ChangeSet) -> Vec<LineEdit> {
let mut edits = Vec::with_capacity(changes.edits().len());
let marked = !buffer.bookmarks.is_empty();
let mut mark_edits = Vec::new();
for e in changes.edits().iter().rev() {
let start = buffer.doc.line_of(e.start);
let new_lines = e.text.bytes().filter(|&b| b == b'\n').count();
let edit = LineEdit {
start,
old_end: buffer.doc.line_of(e.end),
new_end: start + new_lines,
};
if marked {
mark_edits.push(crate::bookmarks::EditLines {
start,
old_end: edit.old_end,
new_lines,
at_line_start: buffer.doc.line_start(start) == e.start,
insertion: e.start == e.end,
});
}
buffer.record_line_edit(edit);
edits.push(edit);
}
changes.apply(&mut buffer.doc);
if marked {
mark_edits.reverse();
let count = buffer.doc.line_count();
buffer.bookmarks.map(&mark_edits, count);
}
edits
}