Skip to main content

ochrepad_core/
editor.rs

1//! `Editor`: owns buffers and views. [`Editor::apply`] is the only path that
2//! mutates a document (PLAN 3.5).
3
4use std::collections::BTreeMap;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::{Duration, Instant};
8
9use crate::action::{Action, ActionError};
10use crate::buffer::{Buffer, BufferId, FileMeta, Indent, LineEdit};
11use crate::changeset::{Bias, ChangeSet};
12use crate::display::DisplayMap;
13use crate::fold::{self, FoldSet};
14use crate::history::Transaction;
15use crate::position::{ByteOffset, byte_to_line_col, line_col_to_byte};
16use crate::resolve::{Resolved, resolve, resolve_search};
17use crate::selection::{Selection, SelectionSet};
18use crate::view::{View, ViewId};
19
20/// Monotonic time source for undo coalescing; replaceable in tests.
21pub trait Clock: Send {
22    fn now(&self) -> Duration;
23}
24
25pub struct SystemClock {
26    origin: Instant,
27}
28
29impl Default for SystemClock {
30    fn default() -> Self {
31        Self {
32            origin: Instant::now(),
33        }
34    }
35}
36
37impl Clock for SystemClock {
38    fn now(&self) -> Duration {
39        self.origin.elapsed()
40    }
41}
42
43/// A clock that only moves when told to. Clones share the same time.
44#[derive(Clone, Default)]
45pub struct ManualClock(Arc<AtomicU64>);
46
47impl ManualClock {
48    pub fn advance(&self, d: Duration) {
49        self.0.fetch_add(d.as_millis() as u64, Ordering::Relaxed);
50    }
51}
52
53impl Clock for ManualClock {
54    fn now(&self) -> Duration {
55        Duration::from_millis(self.0.load(Ordering::Relaxed))
56    }
57}
58
59/// Defaults for new buffers (each buffer has its own [`Indent`]).
60#[derive(Clone, Debug)]
61pub struct Settings {
62    pub tab_width: usize,
63    /// The Tab key inserts spaces instead of a tab character.
64    pub use_spaces: bool,
65    /// Typing an opening bracket or quote inserts its partner (Notepad++'s
66    /// Auto-Insert; see `autoclose`).
67    pub auto_close: bool,
68    /// Enter after an opening bracket indents, and a closing bracket lines
69    /// up with its partner (see `smart_indent`).
70    pub smart_indent: bool,
71    /// Typing replaces the character after the caret (the Insert key; see
72    /// `overtype`).
73    pub overtype: bool,
74}
75
76impl Settings {
77    /// The indentation a new buffer starts with.
78    pub fn indent(&self) -> Indent {
79        Indent {
80            use_spaces: self.use_spaces,
81            width: self.tab_width,
82        }
83    }
84}
85
86impl Default for Settings {
87    fn default() -> Self {
88        Self {
89            tab_width: 4,
90            use_spaces: false,
91            auto_close: false,
92            smart_indent: false,
93            overtype: false,
94        }
95    }
96}
97
98pub struct Editor {
99    buffers: BTreeMap<BufferId, Buffer>,
100    views: BTreeMap<ViewId, View>,
101    next_id: u64,
102    pub settings: Settings,
103    clock: Box<dyn Clock>,
104    recording: Option<Vec<Action>>,
105}
106
107impl Default for Editor {
108    fn default() -> Self {
109        Self::with_clock(Box::new(SystemClock::default()))
110    }
111}
112
113impl Editor {
114    pub fn new() -> Self {
115        Self::default()
116    }
117
118    pub fn with_clock(clock: Box<dyn Clock>) -> Self {
119        Self {
120            buffers: BTreeMap::new(),
121            views: BTreeMap::new(),
122            next_id: 1,
123            settings: Settings::default(),
124            clock,
125            recording: None,
126        }
127    }
128
129    fn fresh_id(&mut self) -> u64 {
130        let id = self.next_id;
131        self.next_id += 1;
132        id
133    }
134
135    pub fn add_buffer(&mut self, text: &str, meta: FileMeta) -> BufferId {
136        let id = BufferId(self.fresh_id());
137        let mut buffer = Buffer::new(id, text, meta);
138        buffer.indent = self.settings.indent();
139        self.buffers.insert(id, buffer);
140        id
141    }
142
143    pub fn add_view(&mut self, buffer: BufferId) -> Result<ViewId, ActionError> {
144        if !self.buffers.contains_key(&buffer) {
145            return Err(ActionError::UnknownBuffer(buffer));
146        }
147        let id = ViewId(self.fresh_id());
148        self.views.insert(id, View::new(id, buffer));
149        Ok(id)
150    }
151
152    pub fn remove_view(&mut self, view: ViewId) -> Option<View> {
153        self.views.remove(&view)
154    }
155
156    /// Removes the buffer and every view of it.
157    pub fn remove_buffer(&mut self, buffer: BufferId) -> Option<Buffer> {
158        self.views.retain(|_, v| v.buffer() != buffer);
159        self.buffers.remove(&buffer)
160    }
161
162    pub fn buffer(&self, id: BufferId) -> Option<&Buffer> {
163        self.buffers.get(&id)
164    }
165
166    pub fn buffer_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
167        self.buffers.get_mut(&id)
168    }
169
170    pub fn view(&self, id: ViewId) -> Option<&View> {
171        self.views.get(&id)
172    }
173
174    pub fn view_mut(&mut self, id: ViewId) -> Option<&mut View> {
175        self.views.get_mut(&id)
176    }
177
178    pub fn buffers(&self) -> impl Iterator<Item = &Buffer> {
179        self.buffers.values()
180    }
181
182    pub fn buffers_mut(&mut self) -> impl Iterator<Item = &mut Buffer> {
183        self.buffers.values_mut()
184    }
185
186    pub fn views(&self) -> impl Iterator<Item = &View> {
187        self.views.values()
188    }
189
190    /// The views of `buffer` that are tabs (not auxiliary ones).
191    pub fn views_of(&self, buffer: BufferId) -> impl Iterator<Item = &View> {
192        self.views
193            .values()
194            .filter(move |v| v.buffer() == buffer && !v.is_aux())
195    }
196
197    /// A view that is not a tab of its own (one side of a compare view). It
198    /// follows edits like any view, is left out of
199    /// [`views_of`](Self::views_of), and goes when its buffer is removed.
200    pub fn add_aux_view(&mut self, buffer: BufferId) -> Result<ViewId, ActionError> {
201        let id = self.add_view(buffer)?;
202        if let Some(v) = self.views.get_mut(&id) {
203            v.set_aux();
204        }
205        Ok(id)
206    }
207
208    pub fn start_recording(&mut self) {
209        self.recording = Some(Vec::new());
210    }
211
212    pub fn is_recording(&self) -> bool {
213        self.recording.is_some()
214    }
215
216    pub fn stop_recording(&mut self) -> Vec<Action> {
217        self.recording.take().unwrap_or_default()
218    }
219
220    /// The actions recorded so far, while recording.
221    pub fn recorded(&self) -> Option<&[Action]> {
222        self.recording.as_deref()
223    }
224
225    pub(crate) fn take_recording(&mut self) -> Option<Vec<Action>> {
226        self.recording.take()
227    }
228
229    pub(crate) fn restore_recording(&mut self, recording: Option<Vec<Action>>) {
230        self.recording = recording;
231    }
232
233    /// Everything applied to `view`'s buffer until the matching
234    /// [`Self::end_undo_group`] undoes as one step.
235    pub fn begin_undo_group(&mut self, view: ViewId) -> Result<(), ActionError> {
236        let (buffer, _) = self.lookup(view)?;
237        self.buffers
238            .get_mut(&buffer)
239            .ok_or(ActionError::UnknownBuffer(buffer))?
240            .history
241            .begin_group();
242        Ok(())
243    }
244
245    pub fn end_undo_group(&mut self, view: ViewId) -> Result<(), ActionError> {
246        let (buffer, _) = self.lookup(view)?;
247        self.buffers
248            .get_mut(&buffer)
249            .ok_or(ActionError::UnknownBuffer(buffer))?
250            .history
251            .end_group();
252        Ok(())
253    }
254
255    fn lookup(&self, view: ViewId) -> Result<(BufferId, &View), ActionError> {
256        let v = self
257            .views
258            .get(&view)
259            .ok_or(ActionError::UnknownView(view))?;
260        if !self.buffers.contains_key(&v.buffer()) {
261            return Err(ActionError::UnknownBuffer(v.buffer()));
262        }
263        Ok((v.buffer(), v))
264    }
265
266    /// Resolves `action` against `view` and applies it: edits the rope, pushes
267    /// undo, maps other views' selections, marks dirty lines for the
268    /// highlighter, bumps the revision and records the action if recording.
269    pub fn apply(&mut self, view: ViewId, action: Action) -> Result<(), ActionError> {
270        let (buffer_id, _) = self.lookup(view)?;
271        let read_only = self.buffers.get(&buffer_id).is_some_and(|b| b.read_only);
272        match &action {
273            Action::Undo | Action::Redo if read_only => return Err(ActionError::ReadOnly),
274            Action::Undo => self.undo(view, buffer_id),
275            Action::Redo => self.redo(view, buffer_id),
276            Action::Fold
277            | Action::Unfold
278            | Action::ToggleFold
279            | Action::FoldAll
280            | Action::UnfoldAll
281            | Action::FoldLevel(_)
282            | Action::UnfoldLevel(_) => self.fold_action(view, buffer_id, &action),
283            Action::ToggleBookmark
284            | Action::ClearBookmarks
285            | Action::InverseBookmarks
286            | Action::BookmarkMatches(_) => self.bookmark_action(view, buffer_id, &action)?,
287            _ => {
288                let marked = match action {
289                    Action::ReplaceBookmarkedLines(_) => self
290                        .buffers
291                        .get(&buffer_id)
292                        .map(|b| b.bookmarks.lines().to_vec())
293                        .unwrap_or_default(),
294                    _ => Vec::new(),
295                };
296                let resolved = self.resolve_in(view, buffer_id, &action)??;
297                self.commit(view, buffer_id, resolved)?;
298                if let Some(b) = self.buffers.get_mut(&buffer_id) {
299                    let count = b.doc.line_count();
300                    match &action {
301                        // Their lines are gone (the marks would otherwise
302                        // land on the lines after them).
303                        Action::RemoveBookmarkedLines => b.bookmarks.clear(),
304                        Action::ReplaceBookmarkedLines(text) => b
305                            .bookmarks
306                            .set(crate::bookmarks::after_replace(&marked, text), count),
307                        _ => {}
308                    }
309                }
310                if let Action::ConvertIndentation { to_spaces } = action
311                    && let Some(b) = self.buffers.get_mut(&buffer_id)
312                {
313                    b.indent.use_spaces = to_spaces;
314                    b.indent_set = true;
315                    b.indent_mixed = false;
316                }
317            }
318        }
319        self.sync_folds(buffer_id);
320        if let Some(rec) = &mut self.recording
321            && action.is_recordable()
322        {
323            rec.push(action);
324        }
325        Ok(())
326    }
327
328    /// Like [`apply`](Self::apply), but never recorded in a macro being
329    /// recorded: for commands that act outside the view the macro plays in
330    /// (Replace All in all open documents).
331    pub fn apply_unrecorded(&mut self, view: ViewId, action: Action) -> Result<(), ActionError> {
332        let recording = self.recording.take();
333        let result = self.apply(view, action);
334        self.recording = recording;
335        result
336    }
337
338    /// Resolves `action` against the view's current state. The outer error
339    /// is a missing view/buffer; the inner one the action's own failure.
340    fn resolve_in(
341        &self,
342        view: ViewId,
343        buffer_id: BufferId,
344        action: &Action,
345    ) -> Result<Result<Resolved, ActionError>, ActionError> {
346        let buffer = self
347            .buffers
348            .get(&buffer_id)
349            .ok_or(ActionError::UnknownBuffer(buffer_id))?;
350        let v = self
351            .views
352            .get(&view)
353            .ok_or(ActionError::UnknownView(view))?;
354        let sels = &v.selections;
355        Ok(match action {
356            Action::Find { .. }
357            | Action::Replace { .. }
358            | Action::ReplaceAll { .. }
359            | Action::SelectAllMatches(_) => resolve_search(action, &buffer.doc, sels),
360            Action::ToggleLineComment
361            | Action::CommentLines
362            | Action::UncommentLines
363            | Action::ToggleBlockComment => {
364                use crate::transform::CommentOp;
365                let syntax = buffer.comments.as_ref();
366                let op = match action {
367                    Action::CommentLines => CommentOp::Add,
368                    Action::UncommentLines => CommentOp::Remove,
369                    _ => CommentOp::Toggle,
370                };
371                let resolved = match (action, syntax) {
372                    (Action::ToggleBlockComment, Some(c)) => {
373                        c.block.as_ref().map(|(open, close)| {
374                            crate::transform::toggle_block_comment(&buffer.doc, sels, open, close)
375                        })
376                    }
377                    (_, Some(c)) => crate::transform::comment_lines(&buffer.doc, sels, c, op),
378                    _ => None,
379                };
380                resolved.ok_or(ActionError::NoCommentSyntax)
381            }
382            Action::MultiSelectNext { .. } | Action::MultiSelectAll { .. } => {
383                crate::multiselect::resolve(action, &buffer.doc, sels)
384            }
385            Action::NextBookmark
386            | Action::PrevBookmark
387            | Action::RemoveBookmarkedLines
388            | Action::RemoveUnbookmarkedLines
389            | Action::ReplaceBookmarkedLines(_) => {
390                crate::bookmarks::resolve(action, &buffer.doc, sels, &buffer.bookmarks)
391            }
392            Action::FormatJson { .. }
393            | Action::MinifyJson
394            | Action::Encode(_)
395            | Action::Decode(_) => crate::text_tools::resolve(
396                action,
397                &buffer.doc,
398                sels,
399                buffer.indent,
400                buffer.meta.line_ending,
401            ),
402            _ => {
403                if self.settings.overtype
404                    && let Some(r) = crate::overtype::resolve(action, &buffer.doc, sels)
405                {
406                    return Ok(Ok(r));
407                }
408                if self.settings.auto_close
409                    && let Some(r) = crate::autoclose::resolve(action, &buffer.doc, sels)
410                {
411                    return Ok(Ok(r));
412                }
413                if self.settings.smart_indent
414                    && let Some(r) = crate::smart_indent::resolve(
415                        action,
416                        &buffer.doc,
417                        sels,
418                        buffer.indent,
419                        buffer.language.as_deref(),
420                    )
421                {
422                    return Ok(Ok(r));
423                }
424                let rows = DisplayMap::new(buffer, v);
425                Ok(resolve(action, &buffer.doc, sels, buffer.indent, &rows))
426            }
427        })
428    }
429
430    /// Applies a resolved action: edits the rope, pushes undo, maps other
431    /// views' selections, bumps the revision.
432    fn commit(
433        &mut self,
434        view: ViewId,
435        buffer_id: BufferId,
436        resolved: Resolved,
437    ) -> Result<(), ActionError> {
438        match resolved {
439            Resolved::Selections(sels) => {
440                if let Some(b) = self.buffers.get_mut(&buffer_id) {
441                    b.history.break_coalescing();
442                }
443                self.set_selections(view, sels);
444            }
445            Resolved::Edit { .. } if self.buffers.get(&buffer_id).is_some_and(|b| b.read_only) => {
446                return Err(ActionError::ReadOnly);
447            }
448            Resolved::Edit {
449                changes,
450                after,
451                kind,
452            } => {
453                let now = self.clock.now();
454                let before = self.views[&view].selections.clone();
455                let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
456                    return Err(ActionError::UnknownBuffer(buffer_id));
457                };
458                let inverse = changes.invert(&buffer.doc);
459                let line_edits = apply_changes(buffer, &changes);
460                let rev_before = buffer.revision;
461                let rev_after = buffer.next_revision;
462                buffer.next_revision += 1;
463                buffer.revision = rev_after;
464                buffer.history.push(
465                    Transaction {
466                        changes: changes.clone(),
467                        inverse,
468                        before,
469                        after: after.clone(),
470                    },
471                    kind,
472                    rev_before,
473                    rev_after,
474                    now,
475                );
476                self.set_selections(view, after);
477                self.map_other_views(buffer_id, view, &changes);
478                self.map_folds(buffer_id, &changes, &line_edits);
479            }
480        }
481        Ok(())
482    }
483
484    /// Replaces a buffer's text with `text` read from disk (an external
485    /// change), as one undoable step; carets keep their line and column.
486    /// The buffer takes `meta` and is marked saved. It mirrors the file, so
487    /// it goes through even when the buffer refuses edits (Read-Only).
488    pub fn reload_buffer(
489        &mut self,
490        buffer: BufferId,
491        text: &str,
492        meta: FileMeta,
493    ) -> Result<(), ActionError> {
494        let view = self
495            .views_of(buffer)
496            .next()
497            .map(View::id)
498            .ok_or(ActionError::UnknownBuffer(buffer))?;
499        let b = self
500            .buffers
501            .get(&buffer)
502            .ok_or(ActionError::UnknownBuffer(buffer))?;
503        if b.doc.to_string() != text {
504            let new_doc = crate::text::Document::new(text);
505            let remap = |p: ByteOffset| {
506                let lc = byte_to_line_col(&b.doc, p);
507                let mut q = line_col_to_byte(&new_doc, lc);
508                while !new_doc.is_char_boundary(q) {
509                    q.0 -= 1;
510                }
511                q
512            };
513            let after = self.views[&view]
514                .selections
515                .map(|s| Selection::new(remap(s.anchor), remap(s.head)));
516            let changes = ChangeSet::new(
517                b.doc.len(),
518                vec![crate::changeset::Edit::new(
519                    ByteOffset(0),
520                    b.doc.len(),
521                    text,
522                )],
523            );
524            let read_only = self
525                .buffers
526                .get_mut(&buffer)
527                .map(|b| std::mem::replace(&mut b.read_only, false));
528            let committed = self.commit(
529                view,
530                buffer,
531                Resolved::Edit {
532                    changes,
533                    after,
534                    kind: crate::history::EditKind::Other,
535                },
536            );
537            if let (Some(b), Some(read_only)) = (self.buffers.get_mut(&buffer), read_only) {
538                b.read_only = read_only;
539            }
540            committed?;
541        }
542        if let Some(b) = self.buffers.get_mut(&buffer) {
543            b.meta = meta;
544            b.mark_saved();
545        }
546        self.sync_folds(buffer);
547        Ok(())
548    }
549
550    fn undo(&mut self, view: ViewId, buffer_id: BufferId) {
551        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
552            return;
553        };
554        let Some(step) = buffer.history.pop_undo() else {
555            return;
556        };
557        let inverses: Vec<ChangeSet> = step.txns.iter().rev().map(|t| t.inverse.clone()).collect();
558        let line_edits: Vec<Vec<LineEdit>> = inverses
559            .iter()
560            .map(|inv| apply_changes(buffer, inv))
561            .collect();
562        buffer.revision = step.rev_before;
563        let sels = step.txns[0].before.clone();
564        buffer.history.push_redo(step);
565        self.set_selections(view, sels);
566        for (inv, edits) in inverses.iter().zip(&line_edits) {
567            self.map_other_views(buffer_id, view, inv);
568            self.map_folds(buffer_id, inv, edits);
569        }
570    }
571
572    fn redo(&mut self, view: ViewId, buffer_id: BufferId) {
573        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
574            return;
575        };
576        let Some(step) = buffer.history.pop_redo() else {
577            return;
578        };
579        let changes: Vec<ChangeSet> = step.txns.iter().map(|t| t.changes.clone()).collect();
580        let line_edits: Vec<Vec<LineEdit>> =
581            changes.iter().map(|cs| apply_changes(buffer, cs)).collect();
582        buffer.revision = step.rev_after;
583        let sels = step.txns[step.txns.len() - 1].after.clone();
584        buffer.history.push_undo(step);
585        self.set_selections(view, sels);
586        for (cs, edits) in changes.iter().zip(&line_edits) {
587            self.map_other_views(buffer_id, view, cs);
588            self.map_folds(buffer_id, cs, edits);
589        }
590    }
591
592    fn set_selections(&mut self, view: ViewId, sels: SelectionSet) {
593        if let Some(v) = self.views.get_mut(&view) {
594            v.selections = sels;
595        }
596    }
597
598    /// Maps every view's folds through `changes`, dropping the folds it
599    /// touches (PLAN_v0.2.0 3.6). [`sync_folds`](Self::sync_folds) then
600    /// recomputes their lines. A filter moves by `line_edits` (the same
601    /// change by line), showing the lines it touched.
602    fn map_folds(&mut self, buffer: BufferId, changes: &ChangeSet, line_edits: &[LineEdit]) {
603        for v in self.views.values_mut() {
604            if v.buffer() != buffer {
605                continue;
606            }
607            if !v.folds.is_empty() {
608                v.folds.map(changes);
609            }
610            v.folds.filter_line_edits(line_edits);
611        }
612    }
613
614    /// Appends `text` that the file on disk gained (follow mode, PLAN_v0.3.0
615    /// P6). It is not an undo step: undoing earlier steps keeps it. A clean
616    /// buffer stays clean. Carets at the end move along to the new end.
617    pub fn append_unrecorded(
618        &mut self,
619        buffer_id: BufferId,
620        text: &str,
621    ) -> Result<(), ActionError> {
622        let buffer = self
623            .buffers
624            .get_mut(&buffer_id)
625            .ok_or(ActionError::UnknownBuffer(buffer_id))?;
626        if text.is_empty() {
627            return Ok(());
628        }
629        let end = buffer.doc.len();
630        let changes = ChangeSet::new(end, vec![crate::changeset::Edit::new(end, end, text)]);
631        let clean = !buffer.is_dirty();
632        let line_edits = apply_changes(buffer, &changes);
633        let (old, new) = (buffer.revision, buffer.next_revision);
634        buffer.history.extend_base(text.len(), old, new);
635        buffer.revision = new;
636        buffer.next_revision += 1;
637        if clean {
638            buffer.mark_saved();
639        }
640        for v in self.views.values_mut() {
641            if v.buffer() == buffer_id {
642                v.selections = v.selections.map(|s| Selection {
643                    anchor: changes.map_position(s.anchor, Bias::After),
644                    head: changes.map_position(s.head, Bias::After),
645                    goal: None,
646                });
647            }
648        }
649        self.map_folds(buffer_id, &changes, &line_edits);
650        self.sync_folds(buffer_id);
651        Ok(())
652    }
653
654    /// Shows only the lines `shown` (sorted line numbers) of `view`, or
655    /// every line again with `None`: the filter view (PLAN_v0.3.0 P6).
656    /// Carets on lines it hides move to the start of the nearest shown
657    /// line. With no line to show, the primary caret's line stays.
658    pub fn set_filter(&mut self, view: ViewId, shown: Option<&[usize]>) {
659        let Some(v) = self.views.get_mut(&view) else {
660            return;
661        };
662        let Some(b) = self.buffers.get(&v.buffer()) else {
663            return;
664        };
665        let doc = &b.doc;
666        let count = doc.line_count();
667        let Some(shown) = shown else {
668            v.folds.set_filter(None, count);
669            return;
670        };
671        let caret_line = [doc.line_of(v.selections.primary().head)];
672        let in_doc = shown.partition_point(|&l| l < count);
673        let shown = if in_doc == 0 {
674            &caret_line[..]
675        } else {
676            &shown[..in_doc]
677        };
678        v.folds.set_filter(Some(shown), count);
679        let folds = &v.folds;
680        let moved = v.selections.map(|s| {
681            let line = doc.line_of(s.head);
682            if !folds.filter_hides(line) {
683                return s.clone();
684            }
685            let after = shown.partition_point(|&l| l < line);
686            let target = shown
687                .get(after)
688                .or_else(|| after.checked_sub(1).and_then(|i| shown.get(i)))
689                .copied()
690                .unwrap_or(0)
691                .min(count - 1);
692            Selection::caret(doc.line_start(target))
693        });
694        v.selections = moved;
695    }
696
697    /// After text was appended to a filtered `view` (follow mode): hides
698    /// the lines from `from` on but `shown` (sorted) and the lines holding
699    /// a caret, leaving the lines before `from` as they were.
700    pub fn filter_appended(&mut self, view: ViewId, from: usize, shown: &[usize]) {
701        let Some(v) = self.views.get_mut(&view) else {
702            return;
703        };
704        let Some(b) = self.buffers.get(&v.buffer()) else {
705            return;
706        };
707        let doc = &b.doc;
708        let mut lines = shown.to_vec();
709        lines.extend(
710            v.selections
711                .ranges()
712                .iter()
713                .map(|s| doc.line_of(s.head))
714                .filter(|&l| l >= from),
715        );
716        lines.sort_unstable();
717        lines.dedup();
718        v.folds.set_filter_from(from, &lines, doc.line_count());
719    }
720
721    /// The lines of `view` holding a selection's head, in order.
722    pub fn caret_lines(&self, view: ViewId) -> Vec<usize> {
723        let Some(v) = self.views.get(&view) else {
724            return Vec::new();
725        };
726        let Some(b) = self.buffers.get(&v.buffer()) else {
727            return Vec::new();
728        };
729        let mut lines: Vec<usize> = v
730            .selections
731            .ranges()
732            .iter()
733            .map(|s| b.doc.line_of(s.head))
734            .collect();
735        lines.dedup();
736        lines
737    }
738
739    /// After any action: brings the folds' line numbers up to date and
740    /// unfolds every fold hiding a caret, in every view of `buffer`.
741    fn sync_folds(&mut self, buffer: BufferId) {
742        let Some(doc) = self.buffers.get(&buffer).map(|b| &b.doc) else {
743            return;
744        };
745        for v in self.views.values_mut() {
746            if v.buffer() != buffer {
747                continue;
748            }
749            // Also when an edit dropped the last fold: the hidden lines need
750            // rebuilding. Cheap when nothing was mapped.
751            v.folds.relines(doc);
752            // A caret the filter hides (Go to Line, a match on a hidden
753            // line, an edit elsewhere) brings its line back.
754            if v.folds.has_filter() {
755                let lines: Vec<usize> = v
756                    .selections
757                    .ranges()
758                    .iter()
759                    .map(|s| doc.line_of(s.head))
760                    .filter(|&l| v.folds.filter_hides(l))
761                    .collect();
762                v.folds.unfilter(&lines);
763            }
764            if v.folds.is_empty() {
765                continue;
766            }
767            let hidden: Vec<usize> = v
768                .selections
769                .ranges()
770                .iter()
771                .map(|s| doc.line_of(s.head))
772                .filter(|&l| v.folds.is_hidden(l))
773                .collect();
774            if !hidden.is_empty() {
775                v.folds
776                    .retain(|header, last| !hidden.iter().any(|&l| header < l && l <= last));
777            }
778        }
779    }
780
781    /// Folds the region `line` heads in `view`; returns whether it did.
782    /// Carets inside move to the end of the header line.
783    pub fn fold_at(&mut self, view: ViewId, line: usize) -> bool {
784        let Some(v) = self.views.get_mut(&view) else {
785            return false;
786        };
787        let Some(b) = self.buffers.get(&v.buffer()) else {
788            return false;
789        };
790        let tab_width = b.indent.width;
791        let Some(last) = fold::region(&b.doc, line, b.fold_strategy, tab_width) else {
792            return false;
793        };
794        v.folds.insert(&b.doc, [(line, last)]);
795        reveal_carets(&b.doc, v);
796        true
797    }
798
799    /// Unfolds the fold at `line` in `view`; returns whether there was one.
800    pub fn unfold_at(&mut self, view: ViewId, line: usize) -> bool {
801        self.views
802            .get_mut(&view)
803            .is_some_and(|v| v.folds.remove(line))
804    }
805
806    /// Unfolds `line` if it is folded, else folds the region it heads.
807    pub fn toggle_fold_at(&mut self, view: ViewId, line: usize) -> bool {
808        self.unfold_at(view, line) || self.fold_at(view, line)
809    }
810
811    fn fold_action(&mut self, view: ViewId, buffer: BufferId, action: &Action) {
812        let (Some(v), Some(b)) = (self.views.get_mut(&view), self.buffers.get(&buffer)) else {
813            return;
814        };
815        let tab_width = b.indent.width;
816        let (doc, strategy) = (&b.doc, b.fold_strategy);
817        let caret = doc.line_of(v.selections.primary().head);
818        let around = || fold::enclosing(doc, caret, strategy, tab_width);
819        match action {
820            Action::Fold => {
821                if let Some(r) = around() {
822                    v.folds.insert(doc, [r]);
823                }
824            }
825            Action::ToggleFold => {
826                if !v.folds.remove(caret)
827                    && let Some(r) = around()
828                {
829                    v.folds.insert(doc, [r]);
830                }
831            }
832            Action::Unfold => {
833                if !v.folds.remove(caret)
834                    && let Some((header, last)) = around()
835                {
836                    v.folds.retain(|h, _| h < header || h > last);
837                }
838            }
839            Action::FoldAll => {
840                let all = fold::regions(doc, strategy, tab_width);
841                v.folds.insert(doc, all.iter().map(|r| (r.header, r.last)));
842            }
843            Action::UnfoldAll => v.folds = FoldSet::default(),
844            Action::FoldLevel(n) => {
845                let all = fold::regions(doc, strategy, tab_width);
846                let level = all.iter().filter(|r| r.level == *n);
847                v.folds.insert(doc, level.map(|r| (r.header, r.last)));
848            }
849            Action::UnfoldLevel(n) => {
850                let level: std::collections::HashSet<usize> =
851                    fold::regions(doc, strategy, tab_width)
852                        .into_iter()
853                        .filter(|r| r.level == *n)
854                        .map(|r| r.header)
855                        .collect();
856                v.folds.retain(|h, _| !level.contains(&h));
857            }
858            _ => {}
859        }
860        reveal_carets(doc, v);
861    }
862
863    /// The bookmark actions that change only the bookmarks.
864    fn bookmark_action(
865        &mut self,
866        view: ViewId,
867        buffer: BufferId,
868        action: &Action,
869    ) -> Result<(), ActionError> {
870        let (Some(v), Some(b)) = (self.views.get(&view), self.buffers.get_mut(&buffer)) else {
871            return Err(ActionError::UnknownView(view));
872        };
873        let count = b.doc.line_count();
874        match action {
875            Action::ToggleBookmark => {
876                let lines = crate::bookmarks::caret_lines(&b.doc, &v.selections);
877                if lines.iter().all(|&l| b.bookmarks.contains(l)) {
878                    for l in lines {
879                        b.bookmarks.remove(l);
880                    }
881                } else {
882                    for l in lines {
883                        b.bookmarks.insert(l);
884                    }
885                }
886            }
887            Action::ClearBookmarks => b.bookmarks.clear(),
888            Action::InverseBookmarks => b.bookmarks.invert(count),
889            Action::BookmarkMatches(query) => {
890                let searcher = crate::search::Searcher::new(query)
891                    .map_err(|e| ActionError::InvalidPattern(e.0))?;
892                let matches = searcher.find_in(&b.doc, &v.selections);
893                if matches.is_empty() {
894                    return Err(ActionError::NotFound);
895                }
896                let found = matches.iter().map(|m| b.doc.line_of(m.start));
897                let all: Vec<usize> = b.bookmarks.lines().iter().copied().chain(found).collect();
898                b.bookmarks.set(all, count);
899            }
900            _ => {}
901        }
902        Ok(())
903    }
904
905    /// Shifts the selections of every other view of `buffer` through `changes`
906    /// so their carets stay put relative to the text.
907    /// Maps the selections of `buffer`'s views other than `except` through
908    /// `changes`, and every view's Begin/End Select start.
909    fn map_other_views(&mut self, buffer: BufferId, except: ViewId, changes: &ChangeSet) {
910        for v in self.views.values_mut() {
911            if v.buffer() != buffer {
912                continue;
913            }
914            // As in Notepad++, text typed where it began goes before it.
915            v.begin_select = v.begin_select.map(|p| changes.map_position(p, Bias::After));
916            if v.id() != except {
917                v.selections = v.selections.map(|s| Selection {
918                    anchor: changes.map_position(s.anchor, Bias::Before),
919                    head: changes.map_position(s.head, Bias::Before),
920                    goal: s.goal,
921                });
922            }
923        }
924    }
925}
926
927/// Moves every selection head that a fold hides to the end of the fold's
928/// header line, so folding does not immediately unfold again.
929fn reveal_carets(doc: &crate::text::Document, v: &mut View) {
930    let folds = &v.folds;
931    if folds.is_empty() {
932        return;
933    }
934    let moved = v.selections.map(|s| {
935        let Some((start, _)) = folds.fold_hidden_range(doc.line_of(s.head)) else {
936            return s.clone();
937        };
938        let head = doc.line_end(start - 1);
939        if s.is_empty() {
940            Selection::caret(head)
941        } else {
942            Selection::new(s.anchor, head)
943        }
944    });
945    v.selections = moved;
946}
947
948/// Applies `changes` to the buffer's text and returns how the lines changed,
949/// in the order the edits were applied.
950fn apply_changes(buffer: &mut Buffer, changes: &ChangeSet) -> Vec<LineEdit> {
951    // `ChangeSet::apply` goes back to front, so each line edit is recorded in
952    // the coordinates of the document at the moment it is applied.
953    let mut edits = Vec::with_capacity(changes.edits().len());
954    let marked = !buffer.bookmarks.is_empty();
955    let mut mark_edits = Vec::new();
956    for e in changes.edits().iter().rev() {
957        let start = buffer.doc.line_of(e.start);
958        let new_lines = e.text.bytes().filter(|&b| b == b'\n').count();
959        let edit = LineEdit {
960            start,
961            old_end: buffer.doc.line_of(e.end),
962            new_end: start + new_lines,
963        };
964        if marked {
965            mark_edits.push(crate::bookmarks::EditLines {
966                start,
967                old_end: edit.old_end,
968                new_lines,
969                at_line_start: buffer.doc.line_start(start) == e.start,
970                insertion: e.start == e.end,
971            });
972        }
973        buffer.record_line_edit(edit);
974        edits.push(edit);
975    }
976    changes.apply(&mut buffer.doc);
977    if marked {
978        mark_edits.reverse();
979        let count = buffer.doc.line_count();
980        buffer.bookmarks.map(&mark_edits, count);
981    }
982    edits
983}