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, 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#[derive(Clone, Debug)]
60pub struct Settings {
61    pub tab_width: usize,
62    /// The Tab key inserts spaces instead of a tab character.
63    pub use_spaces: bool,
64}
65
66impl Default for Settings {
67    fn default() -> Self {
68        Self {
69            tab_width: 4,
70            use_spaces: false,
71        }
72    }
73}
74
75pub struct Editor {
76    buffers: BTreeMap<BufferId, Buffer>,
77    views: BTreeMap<ViewId, View>,
78    next_id: u64,
79    pub settings: Settings,
80    clock: Box<dyn Clock>,
81    recording: Option<Vec<Action>>,
82}
83
84impl Default for Editor {
85    fn default() -> Self {
86        Self::with_clock(Box::new(SystemClock::default()))
87    }
88}
89
90impl Editor {
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    pub fn with_clock(clock: Box<dyn Clock>) -> Self {
96        Self {
97            buffers: BTreeMap::new(),
98            views: BTreeMap::new(),
99            next_id: 1,
100            settings: Settings::default(),
101            clock,
102            recording: None,
103        }
104    }
105
106    fn fresh_id(&mut self) -> u64 {
107        let id = self.next_id;
108        self.next_id += 1;
109        id
110    }
111
112    pub fn add_buffer(&mut self, text: &str, meta: FileMeta) -> BufferId {
113        let id = BufferId(self.fresh_id());
114        self.buffers.insert(id, Buffer::new(id, text, meta));
115        id
116    }
117
118    pub fn add_view(&mut self, buffer: BufferId) -> Result<ViewId, ActionError> {
119        if !self.buffers.contains_key(&buffer) {
120            return Err(ActionError::UnknownBuffer(buffer));
121        }
122        let id = ViewId(self.fresh_id());
123        self.views.insert(id, View::new(id, buffer));
124        Ok(id)
125    }
126
127    pub fn remove_view(&mut self, view: ViewId) -> Option<View> {
128        self.views.remove(&view)
129    }
130
131    /// Removes the buffer and every view of it.
132    pub fn remove_buffer(&mut self, buffer: BufferId) -> Option<Buffer> {
133        self.views.retain(|_, v| v.buffer() != buffer);
134        self.buffers.remove(&buffer)
135    }
136
137    pub fn buffer(&self, id: BufferId) -> Option<&Buffer> {
138        self.buffers.get(&id)
139    }
140
141    pub fn buffer_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
142        self.buffers.get_mut(&id)
143    }
144
145    pub fn view(&self, id: ViewId) -> Option<&View> {
146        self.views.get(&id)
147    }
148
149    pub fn view_mut(&mut self, id: ViewId) -> Option<&mut View> {
150        self.views.get_mut(&id)
151    }
152
153    pub fn buffers(&self) -> impl Iterator<Item = &Buffer> {
154        self.buffers.values()
155    }
156
157    pub fn buffers_mut(&mut self) -> impl Iterator<Item = &mut Buffer> {
158        self.buffers.values_mut()
159    }
160
161    pub fn views(&self) -> impl Iterator<Item = &View> {
162        self.views.values()
163    }
164
165    /// The views of `buffer` that are tabs (not auxiliary ones).
166    pub fn views_of(&self, buffer: BufferId) -> impl Iterator<Item = &View> {
167        self.views
168            .values()
169            .filter(move |v| v.buffer() == buffer && !v.is_aux())
170    }
171
172    /// A view that is not a tab of its own (one side of a compare view). It
173    /// follows edits like any view, is left out of
174    /// [`views_of`](Self::views_of), and goes when its buffer is removed.
175    pub fn add_aux_view(&mut self, buffer: BufferId) -> Result<ViewId, ActionError> {
176        let id = self.add_view(buffer)?;
177        if let Some(v) = self.views.get_mut(&id) {
178            v.set_aux();
179        }
180        Ok(id)
181    }
182
183    pub fn start_recording(&mut self) {
184        self.recording = Some(Vec::new());
185    }
186
187    pub fn is_recording(&self) -> bool {
188        self.recording.is_some()
189    }
190
191    pub fn stop_recording(&mut self) -> Vec<Action> {
192        self.recording.take().unwrap_or_default()
193    }
194
195    /// The actions recorded so far, while recording.
196    pub fn recorded(&self) -> Option<&[Action]> {
197        self.recording.as_deref()
198    }
199
200    pub(crate) fn take_recording(&mut self) -> Option<Vec<Action>> {
201        self.recording.take()
202    }
203
204    pub(crate) fn restore_recording(&mut self, recording: Option<Vec<Action>>) {
205        self.recording = recording;
206    }
207
208    /// Everything applied to `view`'s buffer until the matching
209    /// [`Self::end_undo_group`] undoes as one step.
210    pub fn begin_undo_group(&mut self, view: ViewId) -> Result<(), ActionError> {
211        let (buffer, _) = self.lookup(view)?;
212        self.buffers
213            .get_mut(&buffer)
214            .ok_or(ActionError::UnknownBuffer(buffer))?
215            .history
216            .begin_group();
217        Ok(())
218    }
219
220    pub fn end_undo_group(&mut self, view: ViewId) -> Result<(), ActionError> {
221        let (buffer, _) = self.lookup(view)?;
222        self.buffers
223            .get_mut(&buffer)
224            .ok_or(ActionError::UnknownBuffer(buffer))?
225            .history
226            .end_group();
227        Ok(())
228    }
229
230    fn lookup(&self, view: ViewId) -> Result<(BufferId, &View), ActionError> {
231        let v = self
232            .views
233            .get(&view)
234            .ok_or(ActionError::UnknownView(view))?;
235        if !self.buffers.contains_key(&v.buffer()) {
236            return Err(ActionError::UnknownBuffer(v.buffer()));
237        }
238        Ok((v.buffer(), v))
239    }
240
241    /// Resolves `action` against `view` and applies it: edits the rope, pushes
242    /// undo, maps other views' selections, marks dirty lines for the
243    /// highlighter, bumps the revision and records the action if recording.
244    pub fn apply(&mut self, view: ViewId, action: Action) -> Result<(), ActionError> {
245        let (buffer_id, _) = self.lookup(view)?;
246        let read_only = self.buffers.get(&buffer_id).is_some_and(|b| b.read_only);
247        match &action {
248            Action::Undo | Action::Redo if read_only => return Err(ActionError::ReadOnly),
249            Action::Undo => self.undo(view, buffer_id),
250            Action::Redo => self.redo(view, buffer_id),
251            Action::Fold
252            | Action::Unfold
253            | Action::ToggleFold
254            | Action::FoldAll
255            | Action::UnfoldAll
256            | Action::FoldLevel(_)
257            | Action::UnfoldLevel(_) => self.fold_action(view, buffer_id, &action),
258            _ => {
259                let resolved = self.resolve_in(view, buffer_id, &action)??;
260                self.commit(view, buffer_id, resolved)?;
261            }
262        }
263        self.sync_folds(buffer_id);
264        if let Some(rec) = &mut self.recording
265            && action.is_recordable()
266        {
267            rec.push(action);
268        }
269        Ok(())
270    }
271
272    /// Like [`apply`](Self::apply), but never recorded in a macro being
273    /// recorded: for commands that act outside the view the macro plays in
274    /// (Replace All in all open documents).
275    pub fn apply_unrecorded(&mut self, view: ViewId, action: Action) -> Result<(), ActionError> {
276        let recording = self.recording.take();
277        let result = self.apply(view, action);
278        self.recording = recording;
279        result
280    }
281
282    /// Resolves `action` against the view's current state. The outer error
283    /// is a missing view/buffer; the inner one the action's own failure.
284    fn resolve_in(
285        &self,
286        view: ViewId,
287        buffer_id: BufferId,
288        action: &Action,
289    ) -> Result<Result<Resolved, ActionError>, ActionError> {
290        let buffer = self
291            .buffers
292            .get(&buffer_id)
293            .ok_or(ActionError::UnknownBuffer(buffer_id))?;
294        let v = self
295            .views
296            .get(&view)
297            .ok_or(ActionError::UnknownView(view))?;
298        let sels = &v.selections;
299        Ok(match action {
300            Action::Find { .. }
301            | Action::Replace { .. }
302            | Action::ReplaceAll { .. }
303            | Action::SelectAllMatches(_) => resolve_search(action, &buffer.doc, sels),
304            _ => {
305                let rows = DisplayMap::new(buffer, v, self.settings.tab_width);
306                Ok(resolve(action, &buffer.doc, sels, &self.settings, &rows))
307            }
308        })
309    }
310
311    /// Applies a resolved action: edits the rope, pushes undo, maps other
312    /// views' selections, bumps the revision.
313    fn commit(
314        &mut self,
315        view: ViewId,
316        buffer_id: BufferId,
317        resolved: Resolved,
318    ) -> Result<(), ActionError> {
319        match resolved {
320            Resolved::Selections(sels) => {
321                if let Some(b) = self.buffers.get_mut(&buffer_id) {
322                    b.history.break_coalescing();
323                }
324                self.set_selections(view, sels);
325            }
326            Resolved::Edit { .. } if self.buffers.get(&buffer_id).is_some_and(|b| b.read_only) => {
327                return Err(ActionError::ReadOnly);
328            }
329            Resolved::Edit {
330                changes,
331                after,
332                kind,
333            } => {
334                let now = self.clock.now();
335                let before = self.views[&view].selections.clone();
336                let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
337                    return Err(ActionError::UnknownBuffer(buffer_id));
338                };
339                let inverse = changes.invert(&buffer.doc);
340                apply_changes(buffer, &changes);
341                let rev_before = buffer.revision;
342                let rev_after = buffer.next_revision;
343                buffer.next_revision += 1;
344                buffer.revision = rev_after;
345                buffer.history.push(
346                    Transaction {
347                        changes: changes.clone(),
348                        inverse,
349                        before,
350                        after: after.clone(),
351                    },
352                    kind,
353                    rev_before,
354                    rev_after,
355                    now,
356                );
357                self.set_selections(view, after);
358                self.map_other_views(buffer_id, view, &changes);
359                self.map_folds(buffer_id, &changes);
360            }
361        }
362        Ok(())
363    }
364
365    /// Replaces a buffer's text with `text` read from disk (an external
366    /// change), as one undoable step; carets keep their line and column.
367    /// The buffer takes `meta` and is marked saved.
368    pub fn reload_buffer(
369        &mut self,
370        buffer: BufferId,
371        text: &str,
372        meta: FileMeta,
373    ) -> Result<(), ActionError> {
374        let view = self
375            .views_of(buffer)
376            .next()
377            .map(View::id)
378            .ok_or(ActionError::UnknownBuffer(buffer))?;
379        let b = self
380            .buffers
381            .get(&buffer)
382            .ok_or(ActionError::UnknownBuffer(buffer))?;
383        if b.doc.to_string() != text {
384            let new_doc = crate::text::Document::new(text);
385            let remap = |p: ByteOffset| {
386                let lc = byte_to_line_col(&b.doc, p);
387                let mut q = line_col_to_byte(&new_doc, lc);
388                while !new_doc.is_char_boundary(q) {
389                    q.0 -= 1;
390                }
391                q
392            };
393            let after = self.views[&view]
394                .selections
395                .map(|s| Selection::new(remap(s.anchor), remap(s.head)));
396            let changes = ChangeSet::new(
397                b.doc.len(),
398                vec![crate::changeset::Edit::new(
399                    ByteOffset(0),
400                    b.doc.len(),
401                    text,
402                )],
403            );
404            self.commit(
405                view,
406                buffer,
407                Resolved::Edit {
408                    changes,
409                    after,
410                    kind: crate::history::EditKind::Other,
411                },
412            )?;
413        }
414        if let Some(b) = self.buffers.get_mut(&buffer) {
415            b.meta = meta;
416            b.mark_saved();
417        }
418        self.sync_folds(buffer);
419        Ok(())
420    }
421
422    fn undo(&mut self, view: ViewId, buffer_id: BufferId) {
423        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
424            return;
425        };
426        let Some(step) = buffer.history.pop_undo() else {
427            return;
428        };
429        let inverses: Vec<ChangeSet> = step.txns.iter().rev().map(|t| t.inverse.clone()).collect();
430        for inv in &inverses {
431            apply_changes(buffer, inv);
432        }
433        buffer.revision = step.rev_before;
434        let sels = step.txns[0].before.clone();
435        buffer.history.push_redo(step);
436        self.set_selections(view, sels);
437        for inv in &inverses {
438            self.map_other_views(buffer_id, view, inv);
439            self.map_folds(buffer_id, inv);
440        }
441    }
442
443    fn redo(&mut self, view: ViewId, buffer_id: BufferId) {
444        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
445            return;
446        };
447        let Some(step) = buffer.history.pop_redo() else {
448            return;
449        };
450        let changes: Vec<ChangeSet> = step.txns.iter().map(|t| t.changes.clone()).collect();
451        for cs in &changes {
452            apply_changes(buffer, cs);
453        }
454        buffer.revision = step.rev_after;
455        let sels = step.txns[step.txns.len() - 1].after.clone();
456        buffer.history.push_undo(step);
457        self.set_selections(view, sels);
458        for cs in &changes {
459            self.map_other_views(buffer_id, view, cs);
460            self.map_folds(buffer_id, cs);
461        }
462    }
463
464    fn set_selections(&mut self, view: ViewId, sels: SelectionSet) {
465        if let Some(v) = self.views.get_mut(&view) {
466            v.selections = sels;
467        }
468    }
469
470    /// Maps every view's folds through `changes`, dropping the folds it
471    /// touches (PLAN_v0.2.0 3.6). [`sync_folds`](Self::sync_folds) then
472    /// recomputes their lines.
473    fn map_folds(&mut self, buffer: BufferId, changes: &ChangeSet) {
474        for v in self.views.values_mut() {
475            if v.buffer() == buffer && !v.folds.is_empty() {
476                v.folds.map(changes);
477            }
478        }
479    }
480
481    /// After any action: brings the folds' line numbers up to date and
482    /// unfolds every fold hiding a caret, in every view of `buffer`.
483    fn sync_folds(&mut self, buffer: BufferId) {
484        let Some(doc) = self.buffers.get(&buffer).map(|b| &b.doc) else {
485            return;
486        };
487        for v in self.views.values_mut() {
488            if v.buffer() != buffer {
489                continue;
490            }
491            // Also when an edit dropped the last fold: the hidden lines need
492            // rebuilding. Cheap when nothing was mapped.
493            v.folds.relines(doc);
494            if v.folds.is_empty() {
495                continue;
496            }
497            let hidden: Vec<usize> = v
498                .selections
499                .ranges()
500                .iter()
501                .map(|s| doc.line_of(s.head))
502                .filter(|&l| v.folds.is_hidden(l))
503                .collect();
504            if !hidden.is_empty() {
505                v.folds
506                    .retain(|header, last| !hidden.iter().any(|&l| header < l && l <= last));
507            }
508        }
509    }
510
511    /// Folds the region `line` heads in `view`; returns whether it did.
512    /// Carets inside move to the end of the header line.
513    pub fn fold_at(&mut self, view: ViewId, line: usize) -> bool {
514        let tab_width = self.settings.tab_width;
515        let Some(v) = self.views.get_mut(&view) else {
516            return false;
517        };
518        let Some(b) = self.buffers.get(&v.buffer()) else {
519            return false;
520        };
521        let Some(last) = fold::region(&b.doc, line, b.fold_strategy, tab_width) else {
522            return false;
523        };
524        v.folds.insert(&b.doc, [(line, last)]);
525        reveal_carets(&b.doc, v);
526        true
527    }
528
529    /// Unfolds the fold at `line` in `view`; returns whether there was one.
530    pub fn unfold_at(&mut self, view: ViewId, line: usize) -> bool {
531        self.views
532            .get_mut(&view)
533            .is_some_and(|v| v.folds.remove(line))
534    }
535
536    /// Unfolds `line` if it is folded, else folds the region it heads.
537    pub fn toggle_fold_at(&mut self, view: ViewId, line: usize) -> bool {
538        self.unfold_at(view, line) || self.fold_at(view, line)
539    }
540
541    fn fold_action(&mut self, view: ViewId, buffer: BufferId, action: &Action) {
542        let tab_width = self.settings.tab_width;
543        let (Some(v), Some(b)) = (self.views.get_mut(&view), self.buffers.get(&buffer)) else {
544            return;
545        };
546        let (doc, strategy) = (&b.doc, b.fold_strategy);
547        let caret = doc.line_of(v.selections.primary().head);
548        let around = || fold::enclosing(doc, caret, strategy, tab_width);
549        match action {
550            Action::Fold => {
551                if let Some(r) = around() {
552                    v.folds.insert(doc, [r]);
553                }
554            }
555            Action::ToggleFold => {
556                if !v.folds.remove(caret)
557                    && let Some(r) = around()
558                {
559                    v.folds.insert(doc, [r]);
560                }
561            }
562            Action::Unfold => {
563                if !v.folds.remove(caret)
564                    && let Some((header, last)) = around()
565                {
566                    v.folds.retain(|h, _| h < header || h > last);
567                }
568            }
569            Action::FoldAll => {
570                let all = fold::regions(doc, strategy, tab_width);
571                v.folds.insert(doc, all.iter().map(|r| (r.header, r.last)));
572            }
573            Action::UnfoldAll => v.folds = FoldSet::default(),
574            Action::FoldLevel(n) => {
575                let all = fold::regions(doc, strategy, tab_width);
576                let level = all.iter().filter(|r| r.level == *n);
577                v.folds.insert(doc, level.map(|r| (r.header, r.last)));
578            }
579            Action::UnfoldLevel(n) => {
580                let level: std::collections::HashSet<usize> =
581                    fold::regions(doc, strategy, tab_width)
582                        .into_iter()
583                        .filter(|r| r.level == *n)
584                        .map(|r| r.header)
585                        .collect();
586                v.folds.retain(|h, _| !level.contains(&h));
587            }
588            _ => {}
589        }
590        reveal_carets(doc, v);
591    }
592
593    /// Shifts the selections of every other view of `buffer` through `changes`
594    /// so their carets stay put relative to the text.
595    fn map_other_views(&mut self, buffer: BufferId, except: ViewId, changes: &ChangeSet) {
596        for v in self.views.values_mut() {
597            if v.buffer() == buffer && v.id() != except {
598                v.selections = v.selections.map(|s| Selection {
599                    anchor: changes.map_position(s.anchor, Bias::Before),
600                    head: changes.map_position(s.head, Bias::Before),
601                    goal: s.goal,
602                });
603            }
604        }
605    }
606}
607
608/// Moves every selection head that a fold hides to the end of the fold's
609/// header line, so folding does not immediately unfold again.
610fn reveal_carets(doc: &crate::text::Document, v: &mut View) {
611    let folds = &v.folds;
612    if folds.is_empty() {
613        return;
614    }
615    let moved = v.selections.map(|s| {
616        let Some((start, _)) = folds.hidden_range(doc.line_of(s.head)) else {
617            return s.clone();
618        };
619        let head = doc.line_end(start - 1);
620        if s.is_empty() {
621            Selection::caret(head)
622        } else {
623            Selection::new(s.anchor, head)
624        }
625    });
626    v.selections = moved;
627}
628
629fn apply_changes(buffer: &mut Buffer, changes: &ChangeSet) {
630    // `ChangeSet::apply` goes back to front, so each line edit is recorded in
631    // the coordinates of the document at the moment it is applied.
632    for e in changes.edits().iter().rev() {
633        let start = buffer.doc.line_of(e.start);
634        let edit = LineEdit {
635            start,
636            old_end: buffer.doc.line_of(e.end),
637            new_end: start + e.text.bytes().filter(|&b| b == b'\n').count(),
638        };
639        buffer.record_line_edit(edit);
640    }
641    changes.apply(&mut buffer.doc);
642}