1use std::collections::HashMap;
2use std::sync::Arc;
3
4use super::{
5 id::BlockId,
6 inline::InlineText,
7 kind::{BlockKind, BlockKindData},
8 order::OrderIndex,
9 position::BlockPosition,
10 step::DetachedBlock,
11};
12use gpui_kit::base::text::{MarkdownExtensions, MarkdownNode};
13
14#[derive(Clone, Debug, PartialEq)]
16pub(crate) enum AtomData {
17 None,
18 Custom(MarkdownNode),
19}
20
21#[derive(Clone, Debug, PartialEq)]
22pub(crate) enum BlockContent {
23 Children(Vec<BlockId>),
24 Inline(InlineText),
26 Plain(String),
28 Atom(AtomData),
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub(crate) struct BlockEntry {
34 pub(crate) parent: Option<BlockId>,
35 pub(crate) kind: BlockKindData,
36 pub(crate) content: BlockContent,
37}
38
39pub(crate) struct EditorDocument {
41 pub(crate) revision: u64,
42 pub(crate) root: BlockId,
43 pub(crate) blocks: HashMap<BlockId, BlockEntry>,
44 pub(crate) order: OrderIndex,
45 pub(crate) structure_dirty: bool,
47 pub(crate) extensions: Arc<MarkdownExtensions>,
48 pub(crate) block_revisions: HashMap<BlockId, u64>,
52 edit_counter: u64,
53}
54
55impl EditorDocument {
56 pub(crate) fn empty(extensions: Arc<MarkdownExtensions>) -> Self {
58 let root = BlockId::next();
59 let paragraph = BlockId::next();
60 let mut blocks = HashMap::new();
61 blocks.insert(
62 root,
63 BlockEntry {
64 parent: None,
65 kind: BlockKindData::Root,
66 content: BlockContent::Children(vec![paragraph]),
67 },
68 );
69 blocks.insert(
70 paragraph,
71 BlockEntry {
72 parent: Some(root),
73 kind: BlockKindData::Paragraph,
74 content: BlockContent::Inline(InlineText::new()),
75 },
76 );
77 let mut doc = Self {
78 revision: 0,
79 root,
80 blocks,
81 order: OrderIndex::default(),
82 structure_dirty: true,
83 extensions,
84 block_revisions: HashMap::new(),
85 edit_counter: 0,
86 };
87 doc.refresh_order();
88 doc
89 }
90
91 pub(crate) fn touch(&mut self, id: BlockId) {
93 self.edit_counter += 1;
94 let mut next = Some(id);
95 while let Some(id) = next {
96 self.block_revisions.insert(id, self.edit_counter);
97 next = self.parent(id);
98 }
99 }
100
101 pub(crate) fn block_revision(&self, id: BlockId) -> u64 {
102 self.block_revisions.get(&id).copied().unwrap_or(0)
103 }
104
105 pub(crate) fn len(&self) -> usize {
107 self.blocks.len()
108 }
109
110 pub(crate) fn get(&self, id: BlockId) -> Option<&BlockEntry> {
111 self.blocks.get(&id)
112 }
113
114 pub(crate) fn get_mut(&mut self, id: BlockId) -> Option<&mut BlockEntry> {
115 self.blocks.get_mut(&id)
116 }
117
118 pub(crate) fn children(&self, id: BlockId) -> &[BlockId] {
119 match self.blocks.get(&id).map(|entry| &entry.content) {
120 Some(BlockContent::Children(children)) => children,
121 _ => &[],
122 }
123 }
124
125 pub(crate) fn parent(&self, id: BlockId) -> Option<BlockId> {
126 self.blocks.get(&id)?.parent
127 }
128
129 pub(crate) fn index_in_parent(&self, id: BlockId) -> Option<(BlockId, usize)> {
130 let parent = self.parent(id)?;
131 let index = self.children(parent).iter().position(|c| *c == id)?;
132 Some((parent, index))
133 }
134
135 pub(crate) fn kind(&self, id: BlockId) -> Option<&BlockKindData> {
136 self.blocks.get(&id).map(|entry| &entry.kind)
137 }
138
139 pub(crate) fn inline(&self, id: BlockId) -> Option<&InlineText> {
140 match &self.blocks.get(&id)?.content {
141 BlockContent::Inline(inline) => Some(inline),
142 _ => None,
143 }
144 }
145
146 pub(crate) fn is_caret_leaf(&self, id: BlockId) -> bool {
148 self.blocks
149 .get(&id)
150 .is_some_and(|entry| !matches!(entry.content, BlockContent::Children(_)))
151 }
152
153 pub(crate) fn leaf_len(&self, id: BlockId) -> Option<usize> {
156 match &self.blocks.get(&id)?.content {
157 BlockContent::Inline(inline) => Some(inline.len()),
158 BlockContent::Plain(code) => Some(code.len()),
159 BlockContent::Atom(_) => Some(1),
160 BlockContent::Children(_) => None,
161 }
162 }
163
164 pub(crate) fn plain(&self, id: BlockId) -> Option<&str> {
166 match &self.blocks.get(&id)?.content {
167 BlockContent::Plain(code) => Some(code),
168 _ => None,
169 }
170 }
171
172 pub(crate) fn leaf_text(&self, id: BlockId) -> Option<&str> {
174 match &self.blocks.get(&id)?.content {
175 BlockContent::Inline(inline) => Some(inline.text()),
176 BlockContent::Plain(code) => Some(code),
177 _ => None,
178 }
179 }
180
181 pub(crate) fn path(&self, id: BlockId) -> Vec<BlockId> {
183 let mut path = vec![id];
184 let mut current = id;
185 while let Some(parent) = self.parent(current) {
186 path.push(parent);
187 current = parent;
188 }
189 path.reverse();
190 path
191 }
192
193 pub(crate) fn walk_leaves(&self) -> Vec<BlockId> {
196 let mut out = Vec::new();
197 let mut stack = vec![self.root];
198 while let Some(id) = stack.pop() {
199 match self.blocks.get(&id).map(|entry| &entry.content) {
200 Some(BlockContent::Children(children)) => stack.extend(children.iter().rev()),
201 Some(_) => out.push(id),
202 None => {}
203 }
204 }
205 out
206 }
207
208 pub(crate) fn refresh_order(&mut self) {
209 if self.structure_dirty {
210 self.order = OrderIndex::build(self);
211 self.structure_dirty = false;
212 }
213 }
214
215 pub(crate) fn leaves(&self) -> &[BlockId] {
217 debug_assert!(!self.structure_dirty, "order index read while stale");
218 self.order.leaves()
219 }
220
221 pub(crate) fn first_caret(&self) -> BlockPosition {
222 let leaf = self.walk_leaves().first().copied().unwrap_or(self.root);
223 BlockPosition::new(leaf, 0)
224 }
225
226 pub(crate) fn last_caret(&self) -> BlockPosition {
227 let leaf = self.walk_leaves().last().copied().unwrap_or(self.root);
228 BlockPosition::new(leaf, self.leaf_len(leaf).unwrap_or(0))
229 }
230
231 pub(crate) fn attach(&mut self, parent: BlockId, index: usize, block: DetachedBlock) -> bool {
233 let Some(BlockEntry {
234 content: BlockContent::Children(children),
235 ..
236 }) = self.blocks.get_mut(&parent)
237 else {
238 return false;
239 };
240 if index > children.len() {
241 return false;
242 }
243 children.insert(index, block.id);
244 self.attach_entry(Some(parent), block);
245 self.structure_dirty = true;
246 true
247 }
248
249 fn attach_entry(&mut self, parent: Option<BlockId>, block: DetachedBlock) {
250 let content = match block.content {
251 super::step::DetachedContent::Children(children) => {
252 let ids = children.iter().map(|child| child.id).collect();
253 for child in children {
254 self.attach_entry(Some(block.id), child);
255 }
256 BlockContent::Children(ids)
257 }
258 super::step::DetachedContent::Leaf(content) => content,
259 };
260 self.blocks.insert(
261 block.id,
262 BlockEntry {
263 parent,
264 kind: block.kind,
265 content,
266 },
267 );
268 }
269
270 pub(crate) fn detach(&mut self, parent: BlockId, index: usize) -> Option<DetachedBlock> {
272 let Some(BlockEntry {
273 content: BlockContent::Children(children),
274 ..
275 }) = self.blocks.get_mut(&parent)
276 else {
277 return None;
278 };
279 if index >= children.len() {
280 return None;
281 }
282 let id = children.remove(index);
283 self.structure_dirty = true;
284 self.detach_entry(id)
285 }
286
287 fn detach_entry(&mut self, id: BlockId) -> Option<DetachedBlock> {
288 let entry = self.blocks.remove(&id)?;
289 let content = match entry.content {
290 BlockContent::Children(children) => super::step::DetachedContent::Children(
291 children
292 .into_iter()
293 .filter_map(|child| self.detach_entry(child))
294 .collect(),
295 ),
296 other => super::step::DetachedContent::Leaf(other),
297 };
298 Some(DetachedBlock {
299 id,
300 kind: entry.kind,
301 content,
302 })
303 }
304
305 pub(crate) fn check_invariants(&self) -> Result<(), String> {
307 let mut seen = 0;
308 let mut stack = vec![(self.root, None)];
309 while let Some((id, parent)) = stack.pop() {
310 seen += 1;
311 let entry = self.blocks.get(&id).ok_or(format!("missing {id:?}"))?;
312 if entry.parent != parent {
313 return Err(format!("bad parent link on {id:?}"));
314 }
315 match &entry.content {
316 BlockContent::Children(children) => {
317 if !entry.kind.is_container() {
318 return Err(format!("{:?} has children", entry.kind));
319 }
320 if children.is_empty() && entry.kind != BlockKindData::Root {
321 return Err(format!("empty container {:?}", entry.kind));
322 }
323 if let BlockKindData::List { .. } = entry.kind
324 && children
325 .iter()
326 .any(|c| !matches!(self.kind(*c), Some(BlockKindData::ListItem { .. })))
327 {
328 return Err("list holds a non-item".into());
329 }
330 if let BlockKindData::ListItem { .. } = entry.kind
331 && !children.first().is_some_and(|c| self.is_caret_leaf(*c))
332 {
333 return Err("list item does not start with a leaf".into());
334 }
335 stack.extend(children.iter().map(|c| (*c, Some(id))));
336 }
337 BlockContent::Inline(inline) if !inline.is_normalized() => {
338 return Err(format!("inline text of {id:?} not normalized"));
339 }
340 _ => {}
341 }
342 }
343 if seen != self.blocks.len() {
344 return Err("orphaned entries".into());
345 }
346 if self.walk_leaves().is_empty() {
347 return Err("no caret leaf".into());
348 }
349 Ok(())
350 }
351}
352
353#[derive(Clone, Copy)]
355pub struct DocumentRef<'a> {
356 pub(crate) doc: &'a EditorDocument,
357}
358
359impl<'a> DocumentRef<'a> {
360 pub fn root(&self) -> BlockRef<'a> {
361 BlockRef {
362 doc: self.doc,
363 id: self.doc.root,
364 }
365 }
366
367 pub fn block(&self, id: BlockId) -> Option<BlockRef<'a>> {
368 self.doc.get(id).map(|_| BlockRef { doc: self.doc, id })
369 }
370
371 pub fn first_caret(&self) -> BlockPosition {
372 self.doc.first_caret()
373 }
374
375 pub fn last_caret(&self) -> BlockPosition {
376 self.doc.last_caret()
377 }
378
379 pub fn leaves(&self) -> impl Iterator<Item = BlockRef<'a>> + 'a {
382 let doc = self.doc;
383 doc.leaves().iter().map(move |id| BlockRef { doc, id: *id })
384 }
385}
386
387#[derive(Clone, Copy)]
389pub struct BlockRef<'a> {
390 doc: &'a EditorDocument,
391 id: BlockId,
392}
393
394impl<'a> BlockRef<'a> {
395 pub fn id(&self) -> BlockId {
396 self.id
397 }
398
399 pub fn kind(&self) -> BlockKind {
400 self.doc
401 .kind(self.id)
402 .map(BlockKindData::public)
403 .unwrap_or(BlockKind::Unknown)
404 }
405
406 pub fn text(&self) -> Option<&'a str> {
408 match &self.doc.get(self.id)?.content {
409 BlockContent::Inline(inline) => Some(inline.text()),
410 BlockContent::Plain(text) => Some(text),
411 _ => None,
412 }
413 }
414
415 pub fn parent(&self) -> Option<BlockRef<'a>> {
416 self.doc
417 .parent(self.id)
418 .map(|id| BlockRef { doc: self.doc, id })
419 }
420
421 pub fn children(&self) -> impl Iterator<Item = BlockRef<'a>> + 'a {
422 let doc = self.doc;
423 doc.children(self.id)
424 .iter()
425 .map(move |id| BlockRef { doc, id: *id })
426 }
427}