Skip to main content

editor/
document.rs

1//! The document every editor on it shares.
2//!
3//! An [`Editor`](crate::Editor) is one view of a [`Document`]: its caret, its
4//! menus, its scroll and its layout are its own, and the text, the form it is
5//! edited in, the comment anchors and the undo stack are the document's. Two
6//! editors on one document show the same text as it is typed.
7//!
8//! Each editor holds a snapshot of the document and edits that, then hands
9//! the result back; the document tells every other editor on it, which takes
10//! the new snapshot and carries its caret through what changed.
11
12use gpui::{Context, EntityId, EventEmitter};
13use markdown::{Annotation, AppExt as _, Cursor, Doc, Part, Selection};
14use std::{ops::Range, rc::Rc};
15
16use crate::{
17    anchor::{Anchor, AnchorId, Delta, SourceRanges},
18    editor::{
19        Mode, ensure_block,
20        mode::{source_of, switch},
21    },
22    history::History,
23};
24
25/// A document, and a count of the changes made to it.
26///
27/// Reads go through `Deref`. [`Tracked::edit`] and [`Tracked::set`] are the
28/// only ways to change it, and both move [`Tracked::revision`], so something
29/// derived from a revision is current while the revision is. Cloning is
30/// cheap: the document is shared until one of the clones is edited.
31#[derive(Clone)]
32pub(crate) struct Tracked {
33    doc: Rc<Doc>,
34    revision: u64,
35}
36
37impl Tracked {
38    fn new(doc: Doc) -> Self {
39        Self {
40            doc: Rc::new(doc),
41            revision: 0,
42        }
43    }
44
45    pub(crate) fn edit(&mut self) -> &mut Doc {
46        self.revision += 1;
47        Rc::make_mut(&mut self.doc)
48    }
49
50    pub(crate) fn set(&mut self, doc: Doc) {
51        *self.edit() = doc;
52    }
53
54    pub(crate) fn revision(&self) -> u64 {
55        self.revision
56    }
57}
58
59impl std::ops::Deref for Tracked {
60    type Target = Doc;
61
62    fn deref(&self) -> &Doc {
63        &self.doc
64    }
65}
66
67/// A markdown document as its editors share it.
68pub struct Document {
69    pub(crate) doc: Tracked,
70    /// Which form the document is in. In [`Mode::Source`] `doc` is one fenced
71    /// block holding the markdown.
72    pub(crate) mode: Mode,
73    /// The dialect this document is read and written in — the app's own marks,
74    /// taken once at construction. One document, one spelling: a document that
75    /// changed dialect between a read and a write would rewrite itself.
76    pub(crate) marks: markdown::Marks,
77    /// Comment ranges, mapped through every edit and snapshotted with the
78    /// document. Here rather than in the app because an undo restores a whole
79    /// document and leaves no delta an app could map its own copy through.
80    pub(crate) anchors: Vec<Anchor>,
81    pub(crate) history: History,
82}
83
84/// A document changed under its editors. Every editor on it but the one that
85/// changed it takes the new snapshot.
86pub struct DocumentEvent {
87    /// The editor the change came from; `None` for a change made on the
88    /// document itself.
89    pub(crate) by: Option<EntityId>,
90    pub(crate) change: Change,
91}
92
93impl DocumentEvent {
94    /// Whether the text changed: not an IME composition still under way, and
95    /// not only the anchors.
96    pub fn edited(&self) -> bool {
97        matches!(self.change, Change::Edited(_) | Change::Replaced)
98    }
99}
100
101/// What a [`DocumentEvent`] did to positions in the document.
102pub(crate) enum Change {
103    /// An edit, through these steps — what a caret elsewhere is carried
104    /// through.
105    Edited(Rc<[Delta]>),
106    /// The document was put back whole — an undo, a switch of form — and
107    /// leaves no step to carry a caret through.
108    Replaced,
109    /// An IME composition moved the text; nothing is committed yet.
110    Composing,
111    /// [`Document::reload`] read the text again from what it was saved as.
112    Reloaded,
113    /// Only the anchors moved.
114    Anchors,
115}
116
117impl EventEmitter<DocumentEvent> for Document {}
118
119impl Document {
120    pub fn new(source: &str, cx: &mut Context<Self>) -> Self {
121        let marks = cx.marks();
122        let mut doc = markdown::parse_with(source, &marks);
123        ensure_block(&mut doc);
124        Self {
125            doc: Tracked::new(doc),
126            mode: Mode::default(),
127            marks,
128            anchors: Vec::new(),
129            history: History::default(),
130        }
131    }
132
133    /// How many undo steps to keep. App-wide configuration would be a gpui
134    /// global alongside [`crate::init`], not a `Theme` field — the theme is
135    /// rebuilt on every light/dark switch, which would quietly reset anything
136    /// behavioural parked in it.
137    pub fn with_undo_limit(mut self, limit: usize) -> Self {
138        self.history = History::with_limit(limit);
139        self
140    }
141
142    /// Read and write this document with marks of its own, rather than the ones
143    /// [`markdown::AppExt::set_marks`] installed. For an app whose documents do
144    /// not all speak the same dialect.
145    pub fn with_marks(mut self, marks: markdown::Marks) -> Self {
146        let source = crate::editor::mode::source(&self.doc, self.mode, &self.marks);
147        self.marks = marks;
148        let mut doc = markdown::parse_with(&source, &self.marks);
149        ensure_block(&mut doc);
150        self.doc.set(doc);
151        self
152    }
153
154    /// Open in [`Mode::Source`] rather than on the document — an app whose
155    /// document is a markdown file first. Nothing is recorded: this is where
156    /// the document starts, not a switch to step back over.
157    pub fn with_mode(mut self, mode: Mode) -> Self {
158        if mode != self.mode {
159            switch(
160                &mut self.doc,
161                &mut self.anchors,
162                &self.marks,
163                mode,
164                Default::default(),
165            );
166            self.mode = mode;
167        }
168        self
169    }
170
171    /// The document as markdown — see [`crate::Editor::source`].
172    pub fn source(&self) -> String {
173        crate::editor::mode::source(&self.doc, self.mode, &self.marks)
174    }
175
176    /// Which form the document is being edited in.
177    pub fn mode(&self) -> Mode {
178        self.mode
179    }
180
181    pub fn doc(&self) -> &Doc {
182        &self.doc
183    }
184
185    /// Read the document again from `source`, in the form it is in. The undo
186    /// stack and the anchors go with the old text.
187    pub fn reload(&mut self, source: &str, cx: &mut Context<Self>) {
188        let mut doc = markdown::parse_with(source, &self.marks);
189        ensure_block(&mut doc);
190        self.doc.set(doc);
191        self.anchors.clear();
192        if self.mode == Mode::Source {
193            switch(
194                &mut self.doc,
195                &mut self.anchors,
196                &self.marks,
197                Mode::Source,
198                Cursor::default(),
199            );
200        }
201        self.history.clear();
202        cx.emit(DocumentEvent {
203            by: None,
204            change: Change::Reloaded,
205        });
206        cx.notify();
207    }
208
209    /// The comment ranges, mapped up to date with the document.
210    ///
211    /// A range that reads [`Anchor::detached`] lost the words it pointed at.
212    /// It is kept rather than dropped, because whether that means "outdated" or
213    /// "resolved" is the app's question.
214    pub fn anchors(&self) -> &[Anchor] {
215        &self.anchors
216    }
217
218    /// Hand over the whole list — the app keeps the threads, this keeps their
219    /// ranges. One entry point rather than add/remove/update, since the app is
220    /// already holding the list that decides all three.
221    ///
222    /// Ranges are positions in [`Self::doc`] as it stands: the source fence in
223    /// [`Mode::Source`], the blocks otherwise.
224    pub fn set_anchors(&mut self, anchors: Vec<Anchor>, cx: &mut Context<Self>) {
225        self.anchors = anchors;
226        cx.emit(DocumentEvent {
227            by: None,
228            change: Change::Anchors,
229        });
230        cx.notify();
231    }
232
233    /// The markdown to save, and where each anchor's ends land in it, in byte
234    /// offsets, in the order of [`Self::anchors`]. A range with an end the
235    /// markdown has no place for comes out `None`.
236    ///
237    /// The string is [`Self::source`] but for whitespace a placed end keeps
238    /// from being trailing; the ranges index this string, so it is the one to
239    /// write beside them.
240    pub fn source_with_anchors(&self) -> (String, SourceRanges) {
241        let ends: Vec<_> = self
242            .anchors
243            .iter()
244            .flat_map(|anchor| {
245                let (start, end) = anchor.range.ordered();
246                [start, end]
247            })
248            .collect();
249        let (source, offsets) = match self.mode {
250            Mode::Blocks => markdown::serialize_at_many(&self.doc, &ends, &self.marks),
251            Mode::Source => {
252                let source = source_of(&self.doc).to_string();
253                let offsets = ends
254                    .iter()
255                    .map(|at| (at.offset <= source.len()).then_some(at.offset))
256                    .collect();
257                (source, offsets)
258            }
259        };
260        let ranges = self
261            .anchors
262            .iter()
263            .enumerate()
264            .map(|(ix, anchor)| {
265                let range = offsets[2 * ix].zip(offsets[2 * ix + 1]);
266                (anchor.id, range.map(|(start, end)| start..end))
267            })
268            .collect();
269        (source, ranges)
270    }
271
272    /// [`Self::set_anchors`] with ranges given in byte offsets into `source`,
273    /// the markdown the document was read from. A range with an end the
274    /// document has no place for comes in detached.
275    pub fn set_source_anchors(
276        &mut self,
277        source: &str,
278        anchors: Vec<(AnchorId, Range<usize>, Annotation)>,
279        cx: &mut Context<Self>,
280    ) {
281        let offsets: Vec<usize> = anchors
282            .iter()
283            .flat_map(|(_, range, _)| [range.start, range.end])
284            .collect();
285        let ends: Vec<Option<Cursor>> = match self.mode {
286            Mode::Blocks => markdown::parse_at_many(source, &offsets, &self.marks).1,
287            Mode::Source => offsets
288                .iter()
289                .map(|offset| {
290                    (*offset <= source.len()).then(|| Cursor::new(0, Part::Code, *offset))
291                })
292                .collect(),
293        };
294        self.anchors = anchors
295            .into_iter()
296            .enumerate()
297            .map(|(ix, (id, _, state))| {
298                let range = match (ends[2 * ix], ends[2 * ix + 1]) {
299                    (Some(start), Some(end)) => Selection::new(start, end),
300                    (surviving, other) => Selection::at(surviving.or(other).unwrap_or_default()),
301                };
302                Anchor {
303                    id,
304                    range: range.clamp(&self.doc),
305                    state,
306                }
307            })
308            .collect();
309        cx.emit(DocumentEvent {
310            by: None,
311            change: Change::Anchors,
312        });
313        cx.notify();
314    }
315}