Skip to main content

text_document/
text_table.rs

1//! Read-only table and table cell handles.
2
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use frontend::commands::{block_commands, frame_commands, table_cell_commands, table_commands};
8use frontend::common::types::EntityId;
9
10use crate::convert::to_usize;
11use crate::flow::{BlockSnapshot, CellFormat, TableFormat, TableSnapshot};
12use crate::inner::TextDocumentInner;
13use crate::text_block::TextBlock;
14use crate::text_frame::{cell_dto_to_format, table_dto_to_format};
15
16/// A read-only handle to a table in the document.
17///
18/// Obtained from [`FlowElement::Table`](crate::FlowElement::Table) during flow traversal.
19#[derive(Clone)]
20pub struct TextTable {
21    pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
22    pub(crate) table_id: usize,
23}
24
25impl TextTable {
26    /// Stable entity ID. O(1).
27    pub fn id(&self) -> usize {
28        self.table_id
29    }
30
31    /// Number of rows. O(1).
32    pub fn rows(&self) -> usize {
33        let inner = self.doc.lock();
34        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
35            .ok()
36            .flatten()
37            .map(|t| to_usize(t.rows))
38            .unwrap_or(0)
39    }
40
41    /// Number of columns. O(1).
42    pub fn columns(&self) -> usize {
43        let inner = self.doc.lock();
44        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
45            .ok()
46            .flatten()
47            .map(|t| to_usize(t.columns))
48            .unwrap_or(0)
49    }
50
51    /// Cell at grid position. O(c) where c = total cells.
52    pub fn cell(&self, row: usize, col: usize) -> Option<TextTableCell> {
53        let inner = self.doc.lock();
54        let table_dto = table_commands::get_table(&inner.ctx, &(self.table_id as u64))
55            .ok()
56            .flatten()?;
57
58        for &cell_id in &table_dto.cells {
59            if let Some(cell_dto) = table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
60                .ok()
61                .flatten()
62                && cell_dto.row as usize == row
63                && cell_dto.column as usize == col
64            {
65                return Some(TextTableCell {
66                    doc: Arc::clone(&self.doc),
67                    cell_id: cell_dto.id as usize,
68                });
69            }
70        }
71        None
72    }
73
74    /// Column widths. O(1).
75    pub fn column_widths(&self) -> Vec<i32> {
76        let inner = self.doc.lock();
77        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
78            .ok()
79            .flatten()
80            .map(|t| t.column_widths.iter().map(|&v| v as i32).collect())
81            .unwrap_or_default()
82    }
83
84    /// Table-level formatting. O(1).
85    pub fn format(&self) -> TableFormat {
86        let inner = self.doc.lock();
87        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
88            .ok()
89            .flatten()
90            .map(|t| table_dto_to_format(&t))
91            .unwrap_or_default()
92    }
93
94    /// All cells with block snapshots. O(c·k).
95    pub fn snapshot(&self) -> TableSnapshot {
96        let inner = self.doc.lock();
97        crate::text_frame::build_table_snapshot(&inner, self.table_id as u64, inner.highlight_kind)
98            .unwrap_or_else(|| TableSnapshot {
99                table_id: self.table_id,
100                rows: 0,
101                columns: 0,
102                column_widths: Vec::new(),
103                format: TableFormat::default(),
104                cells: Vec::new(),
105            })
106    }
107}
108
109/// A read-only handle to a single cell within a table.
110#[derive(Clone)]
111pub struct TextTableCell {
112    pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
113    pub(crate) cell_id: usize,
114}
115
116impl TextTableCell {
117    /// Stable entity ID. O(1).
118    pub fn id(&self) -> usize {
119        self.cell_id
120    }
121
122    /// Cell row index. O(1).
123    pub fn row(&self) -> usize {
124        let inner = self.doc.lock();
125        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
126            .ok()
127            .flatten()
128            .map(|c| to_usize(c.row))
129            .unwrap_or(0)
130    }
131
132    /// Cell column index. O(1).
133    pub fn column(&self) -> usize {
134        let inner = self.doc.lock();
135        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
136            .ok()
137            .flatten()
138            .map(|c| to_usize(c.column))
139            .unwrap_or(0)
140    }
141
142    /// Row span. O(1).
143    pub fn row_span(&self) -> usize {
144        let inner = self.doc.lock();
145        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
146            .ok()
147            .flatten()
148            .map(|c| to_usize(c.row_span))
149            .unwrap_or(1)
150    }
151
152    /// Column span. O(1).
153    pub fn column_span(&self) -> usize {
154        let inner = self.doc.lock();
155        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
156            .ok()
157            .flatten()
158            .map(|c| to_usize(c.column_span))
159            .unwrap_or(1)
160    }
161
162    /// Cell-level formatting. O(1).
163    pub fn format(&self) -> CellFormat {
164        let inner = self.doc.lock();
165        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
166            .ok()
167            .flatten()
168            .map(|c| cell_dto_to_format(&c))
169            .unwrap_or_default()
170    }
171
172    /// Blocks within this cell's frame. Returns empty `Vec` if `cell_frame` is `None`.
173    pub fn blocks(&self) -> Vec<TextBlock> {
174        let inner = self.doc.lock();
175        let cell_dto = match table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
176            .ok()
177            .flatten()
178        {
179            Some(c) => c,
180            None => return Vec::new(),
181        };
182
183        let cell_frame_id = match cell_dto.cell_frame {
184            Some(id) => id,
185            None => return Vec::new(),
186        };
187
188        let frame_dto = match frame_commands::get_frame(&inner.ctx, &(cell_frame_id as EntityId))
189            .ok()
190            .flatten()
191        {
192            Some(f) => f,
193            None => return Vec::new(),
194        };
195
196        // Get blocks sorted by document_position
197        let mut block_dtos: Vec<_> = frame_dto
198            .blocks
199            .iter()
200            .filter_map(|&id| {
201                block_commands::get_block(&inner.ctx, &{ id })
202                    .ok()
203                    .flatten()
204            })
205            .collect();
206        let store = inner.ctx.db_context.get_store();
207        crate::inner::refresh_block_positions(&mut block_dtos, store);
208        block_dtos.sort_by_key(|b| b.document_position);
209
210        block_dtos
211            .iter()
212            .map(|b| TextBlock {
213                doc: Arc::clone(&self.doc),
214                block_id: b.id as usize,
215            })
216            .collect()
217    }
218
219    /// Snapshot all cell blocks in one lock. Returns empty `Vec` if `cell_frame` is `None`.
220    pub fn snapshot_blocks(&self) -> Vec<BlockSnapshot> {
221        let inner = self.doc.lock();
222        let cell_dto = match table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
223            .ok()
224            .flatten()
225        {
226            Some(c) => c,
227            None => return Vec::new(),
228        };
229
230        match cell_dto.cell_frame {
231            Some(frame_id) => crate::text_block::build_blocks_snapshot_for_frame(
232                &inner,
233                frame_id,
234                inner.highlight_kind,
235            ),
236            None => Vec::new(),
237        }
238    }
239}