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::{AppExt as _, Block, BlockKind, Cursor, Doc, Part, Selection, Synced, Text};
14use std::{cell::RefCell, rc::Rc};
15
16use crate::{
17 anchor::{Anchor, Delta, SourceAnchor, SourceRanges},
18 editor::{
19 Mode,
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: Rc<Doc>) -> Self {
39 Self { doc, revision: 0 }
40 }
41
42 pub(crate) fn edit(&mut self) -> &mut Doc {
43 self.revision += 1;
44 Rc::make_mut(&mut self.doc)
45 }
46
47 pub(crate) fn set(&mut self, doc: Doc) {
48 *self.edit() = doc;
49 }
50
51 /// [`Self::set`] to a document held elsewhere, shared until edited.
52 pub(crate) fn share(&mut self, doc: Rc<Doc>) {
53 self.revision += 1;
54 self.doc = doc;
55 }
56
57 pub(crate) fn shared(&self) -> Rc<Doc> {
58 self.doc.clone()
59 }
60
61 /// Give an empty document a block to hold a caret, and say whether one
62 /// was needed.
63 ///
64 /// A [`Doc`] with no blocks is a legitimate document — it is what
65 /// `parse("")` returns — but it is not something that can be edited:
66 /// nothing paints, so there is no caret and no placeholder, and neither a
67 /// click nor a hit test has a target to find.
68 pub(crate) fn ensure_block(&mut self) -> bool {
69 if !self.doc.blocks.is_empty() {
70 return false;
71 }
72 self.edit()
73 .blocks
74 .push(Block::new(BlockKind::Paragraph(Text::default())));
75 true
76 }
77
78 pub(crate) fn revision(&self) -> u64 {
79 self.revision
80 }
81}
82
83impl std::ops::Deref for Tracked {
84 type Target = Doc;
85
86 fn deref(&self) -> &Doc {
87 &self.doc
88 }
89}
90
91/// A markdown document as its editors share it.
92pub struct Document {
93 pub(crate) doc: Tracked,
94 /// Which form the document is in. In [`Mode::Source`] `doc` is one fenced
95 /// block holding the markdown.
96 pub(crate) mode: Mode,
97 /// The document as markdown and as blocks. In [`Mode::Blocks`] it is kept
98 /// up with `doc` as each change is handed back; in [`Mode::Source`] it
99 /// stands where the last switch left it, and catches up with the source at
100 /// the next. Every editor on the document holds it too.
101 pub(crate) synced: Rc<RefCell<Synced>>,
102 /// The dialect this document is read and written in — the app's own marks,
103 /// taken once at construction. One document, one spelling: a document that
104 /// changed dialect between a read and a write would rewrite itself.
105 pub(crate) marks: markdown::Marks,
106 /// Comment ranges, mapped through every edit and snapshotted with the
107 /// document. Here rather than in the app because an undo restores a whole
108 /// document and leaves no delta an app could map its own copy through.
109 pub(crate) anchors: Vec<Anchor>,
110 pub(crate) history: History,
111}
112
113/// A document changed under its editors. Every editor on it but the one that
114/// changed it takes the new snapshot.
115pub struct DocumentEvent {
116 /// The editor the change came from; `None` for a change made on the
117 /// document itself.
118 pub(crate) by: Option<EntityId>,
119 pub(crate) change: Change,
120}
121
122impl DocumentEvent {
123 /// Whether the text changed: not an IME composition still under way, and
124 /// not only the anchors.
125 pub fn edited(&self) -> bool {
126 matches!(self.change, Change::Edited(_) | Change::Replaced)
127 }
128}
129
130/// What a [`DocumentEvent`] did to positions in the document.
131pub(crate) enum Change {
132 /// An edit, through these steps — what a caret elsewhere is carried
133 /// through.
134 Edited(Rc<[Delta]>),
135 /// The document was put back whole — an undo, a switch of form — and
136 /// leaves no step to carry a caret through.
137 Replaced,
138 /// An IME composition moved the text; nothing is committed yet.
139 Composing,
140 /// [`Document::reload`] read the text again from what it was saved as.
141 Reloaded,
142 /// Only the anchors moved.
143 Anchors,
144}
145
146impl EventEmitter<DocumentEvent> for Document {}
147
148impl Document {
149 pub fn new(source: &str, cx: &mut Context<Self>) -> Self {
150 let marks = cx.marks();
151 let synced = Synced::new(source, &marks);
152 let mut doc = Tracked::new(synced.doc().clone());
153 doc.ensure_block();
154 Self {
155 doc,
156 mode: Mode::default(),
157 synced: Rc::new(RefCell::new(synced)),
158 marks,
159 anchors: Vec::new(),
160 history: History::default(),
161 }
162 }
163
164 /// How many undo steps to keep; [`crate::DEFAULT_UNDO_LIMIT`] until set. A
165 /// run of typing coalesces into one step.
166 pub fn with_undo_limit(mut self, limit: usize) -> Self {
167 self.history = History::with_limit(limit);
168 self
169 }
170
171 /// Read and write this document with marks of its own, rather than the ones
172 /// [`markdown::AppExt::set_marks`] installed. For an app whose documents do
173 /// not all speak the same dialect.
174 pub fn with_marks(mut self, marks: markdown::Marks) -> Self {
175 let source = self.source();
176 self.marks = marks;
177 let synced = Synced::new(source, &self.marks);
178 self.doc.share(synced.doc().clone());
179 self.doc.ensure_block();
180 *self.synced.borrow_mut() = synced;
181 self
182 }
183
184 /// Open in [`Mode::Source`] rather than on the document — an app whose
185 /// document is a markdown file first. Nothing is recorded: this is where
186 /// the document starts, not a switch to step back over.
187 pub fn with_mode(mut self, mode: Mode) -> Self {
188 if mode != self.mode {
189 switch(
190 &mut self.doc,
191 &mut self.synced.borrow_mut(),
192 &mut self.anchors,
193 mode,
194 Default::default(),
195 );
196 self.mode = mode;
197 }
198 self
199 }
200
201 /// The document as markdown — see [`crate::Editor::source`].
202 pub fn source(&self) -> String {
203 crate::editor::mode::source(&self.doc, self.mode, &self.synced)
204 }
205
206 /// Which form the document is being edited in.
207 pub fn mode(&self) -> Mode {
208 self.mode
209 }
210
211 pub fn doc(&self) -> &Doc {
212 &self.doc
213 }
214
215 /// Read the document again from `source`, in the form it is in. The undo
216 /// stack and the anchors go with the old text.
217 pub fn reload(&mut self, source: &str, cx: &mut Context<Self>) {
218 let synced = Synced::new(source, &self.marks);
219 self.doc.share(synced.doc().clone());
220 self.doc.ensure_block();
221 *self.synced.borrow_mut() = synced;
222 self.anchors.clear();
223 if self.mode == Mode::Source {
224 switch(
225 &mut self.doc,
226 &mut self.synced.borrow_mut(),
227 &mut self.anchors,
228 Mode::Source,
229 Cursor::default(),
230 );
231 }
232 self.history.clear();
233 cx.emit(DocumentEvent {
234 by: None,
235 change: Change::Reloaded,
236 });
237 cx.notify();
238 }
239
240 /// The comment ranges, mapped up to date with the document.
241 ///
242 /// A range that reads [`Anchor::detached`] lost the words it pointed at.
243 /// It is kept rather than dropped, because whether that means "outdated" or
244 /// "resolved" is the app's question.
245 pub fn anchors(&self) -> &[Anchor] {
246 &self.anchors
247 }
248
249 /// Hand over the whole list — the app keeps the threads, this keeps their
250 /// ranges. One entry point rather than add/remove/update, since the app is
251 /// already holding the list that decides all three.
252 ///
253 /// Ranges are positions in [`Self::doc`] as it stands: the source fence in
254 /// [`Mode::Source`], the blocks otherwise.
255 pub fn set_anchors(&mut self, anchors: Vec<Anchor>, cx: &mut Context<Self>) {
256 self.anchors = anchors;
257 cx.emit(DocumentEvent {
258 by: None,
259 change: Change::Anchors,
260 });
261 cx.notify();
262 }
263
264 /// The markdown to save, and where each anchor's ends land in it, in byte
265 /// offsets, in the order of [`Self::anchors`]. A range with an end the
266 /// markdown has no place for comes out `None`.
267 ///
268 /// The string is [`Self::source`] but for whitespace a placed end keeps
269 /// from being trailing; the ranges index this string, so it is the one to
270 /// write beside them.
271 pub fn source_with_anchors(&self) -> (String, SourceRanges) {
272 let ends: Vec<_> = self
273 .anchors
274 .iter()
275 .flat_map(|anchor| {
276 let (start, end) = anchor.range.ordered();
277 [start, end]
278 })
279 .collect();
280 let (source, offsets) = match self.mode {
281 Mode::Blocks => {
282 let mut synced = self.synced.borrow_mut();
283 synced.set_doc(self.doc.shared());
284 let offsets = synced.offsets(&ends);
285 (synced.text().to_owned(), offsets)
286 }
287 Mode::Source => {
288 let source = source_of(&self.doc).to_string();
289 let offsets = ends
290 .iter()
291 .map(|at| (at.offset <= source.len()).then_some(at.offset))
292 .collect();
293 (source, offsets)
294 }
295 };
296 let ranges = self
297 .anchors
298 .iter()
299 .enumerate()
300 .map(|(ix, anchor)| {
301 let range = offsets[2 * ix].zip(offsets[2 * ix + 1]);
302 (anchor.id, range.map(|(start, end)| start..end))
303 })
304 .collect();
305 (source, ranges)
306 }
307
308 /// [`Self::set_anchors`] with ranges given in byte offsets into `source`,
309 /// the markdown the document was read from. A range with an end the
310 /// document has no place for comes in detached.
311 pub fn set_source_anchors(
312 &mut self,
313 source: &str,
314 anchors: Vec<SourceAnchor>,
315 cx: &mut Context<Self>,
316 ) {
317 let offsets: Vec<usize> = anchors
318 .iter()
319 .flat_map(|anchor| [anchor.range.start, anchor.range.end])
320 .collect();
321 let ends: Vec<Option<Cursor>> = match self.mode {
322 Mode::Blocks => markdown::parse_at_many(source, &offsets, &self.marks).1,
323 Mode::Source => offsets
324 .iter()
325 .map(|offset| {
326 (*offset <= source.len()).then(|| Cursor::new(0, Part::Code, *offset))
327 })
328 .collect(),
329 };
330 self.anchors = anchors
331 .into_iter()
332 .enumerate()
333 .map(|(ix, anchor)| {
334 let range = match (ends[2 * ix], ends[2 * ix + 1]) {
335 (Some(start), Some(end)) => Selection::new(start, end),
336 (surviving, other) => Selection::at(surviving.or(other).unwrap_or_default()),
337 };
338 Anchor {
339 id: anchor.id,
340 range: range.clamp(&self.doc),
341 state: anchor.state,
342 actionable: anchor.actionable,
343 }
344 })
345 .collect();
346 cx.emit(DocumentEvent {
347 by: None,
348 change: Change::Anchors,
349 });
350 cx.notify();
351 }
352}