Skip to main content

text_document/
text_list.rs

1//! Read-only list handle.
2
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use frontend::commands::{block_commands, list_commands};
8use frontend::common::types::EntityId;
9
10use crate::ListStyle;
11use crate::inner::TextDocumentInner;
12use crate::text_block::{TextBlock, format_list_marker};
13
14/// A read-only handle to a list in the document.
15///
16/// Created via [`TextBlock::list()`].
17#[derive(Clone)]
18pub struct TextList {
19    pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
20    pub(crate) list_id: usize,
21}
22
23impl TextList {
24    /// Stable entity ID. O(1).
25    pub fn id(&self) -> usize {
26        self.list_id
27    }
28
29    /// List style (Disc, Circle, Square, Decimal, LowerAlpha, etc.). O(1).
30    pub fn style(&self) -> ListStyle {
31        let inner = self.doc.lock();
32        list_commands::get_list(&inner.ctx, &(self.list_id as u64))
33            .ok()
34            .flatten()
35            .map(|l| l.style)
36            .unwrap_or(ListStyle::Disc)
37    }
38
39    /// Indentation level. O(1).
40    pub fn indent(&self) -> u8 {
41        let inner = self.doc.lock();
42        list_commands::get_list(&inner.ctx, &(self.list_id as u64))
43            .ok()
44            .flatten()
45            .map(|l| l.indent as u8)
46            .unwrap_or(0)
47    }
48
49    /// Text before the marker (e.g., "("). O(1).
50    pub fn prefix(&self) -> String {
51        let inner = self.doc.lock();
52        list_commands::get_list(&inner.ctx, &(self.list_id as u64))
53            .ok()
54            .flatten()
55            .map(|l| l.prefix)
56            .unwrap_or_default()
57    }
58
59    /// Text after the marker (e.g., ")"). O(1).
60    pub fn suffix(&self) -> String {
61        let inner = self.doc.lock();
62        list_commands::get_list(&inner.ctx, &(self.list_id as u64))
63            .ok()
64            .flatten()
65            .map(|l| l.suffix)
66            .unwrap_or_default()
67    }
68
69    /// Number of blocks in this list. **O(n)** — scans all blocks.
70    pub fn count(&self) -> usize {
71        let inner = self.doc.lock();
72        let list_entity_id = self.list_id as EntityId;
73        let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
74        all_blocks
75            .iter()
76            .filter(|b| b.list == Some(list_entity_id))
77            .count()
78    }
79
80    /// Block at the given 0-based index within this list. **O(n)**.
81    pub fn item(&self, index: usize) -> Option<TextBlock> {
82        let inner = self.doc.lock();
83        let list_entity_id = self.list_id as EntityId;
84        let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
85        let mut list_blocks: Vec<_> = all_blocks
86            .iter()
87            .filter(|b| b.list == Some(list_entity_id))
88            .collect();
89        list_blocks.sort_by_key(|b| b.document_position);
90
91        list_blocks.get(index).map(|b| TextBlock {
92            doc: Arc::clone(&self.doc),
93            block_id: b.id as usize,
94        })
95    }
96
97    /// Formatted marker for item at index. O(1) (after looking up list properties).
98    pub fn item_marker(&self, index: usize) -> String {
99        let inner = self.doc.lock();
100        match list_commands::get_list(&inner.ctx, &(self.list_id as u64))
101            .ok()
102            .flatten()
103        {
104            Some(list_dto) => format_list_marker(&list_dto, index),
105            None => String::new(),
106        }
107    }
108}