1use super::{
6 command::CommandError,
7 document::{BlockContent, EditorDocument},
8 editor::BlockEditorModel,
9 id::BlockId,
10 inline::InlineText,
11 kind::BlockKindData,
12 position::{BlockPosition, PositionError},
13 selection::BlockSelection,
14 step::DetachedBlock,
15 transaction::EditOrigin,
16};
17use gpui_kit::base::text::editor_host::ColumnAlign;
18
19pub(crate) fn grid(doc: &EditorDocument, table: BlockId) -> Vec<Vec<BlockId>> {
21 doc.children(table)
22 .iter()
23 .map(|row| doc.children(*row).to_vec())
24 .collect()
25}
26
27pub(crate) fn table_of(doc: &EditorDocument, cell: BlockId) -> Option<BlockId> {
28 if doc.kind(cell) != Some(&BlockKindData::TableCell) {
29 return None;
30 }
31 doc.path(cell)
32 .into_iter()
33 .rev()
34 .find(|id| matches!(doc.kind(*id), Some(BlockKindData::Table { .. })))
35}
36
37pub(crate) fn cell_coords(
39 doc: &EditorDocument,
40 table: BlockId,
41 cell: BlockId,
42) -> Option<(usize, usize)> {
43 grid(doc, table)
44 .iter()
45 .enumerate()
46 .find_map(|(r, row)| row.iter().position(|c| *c == cell).map(|c| (r, c)))
47}
48
49fn empty_cell() -> DetachedBlock {
50 DetachedBlock::leaf(
51 BlockKindData::TableCell,
52 BlockContent::Inline(InlineText::new()),
53 )
54}
55
56fn empty_row(cols: usize) -> DetachedBlock {
57 DetachedBlock::container(
58 BlockKindData::TableRow,
59 (0..cols).map(|_| empty_cell()).collect(),
60 )
61}
62
63fn aligns(doc: &EditorDocument, table: BlockId) -> Vec<ColumnAlign> {
64 match doc.kind(table) {
65 Some(BlockKindData::Table { aligns }) => aligns.clone(),
66 _ => Vec::new(),
67 }
68}
69
70#[derive(Clone, Copy, PartialEq, Debug)]
73pub enum TableEdit {
74 AddRowAbove,
76 AddRowBelow,
78 RemoveRow,
80 AddColumnLeft,
82 AddColumnRight,
84 RemoveColumn,
86 SetColumnAlign(ColumnAlign),
88}
89
90#[derive(Clone, Copy, PartialEq, Eq, Debug)]
92pub enum TableLine {
93 Row(usize),
94 Column(usize),
95}
96
97impl BlockEditorModel {
98 pub(crate) fn current_cell(&self) -> Option<(BlockId, BlockId)> {
100 let cell = match self.selection() {
101 BlockSelection::Text { head, .. } => head.block,
102 BlockSelection::Cells { head, .. } => *head,
103 BlockSelection::Blocks { .. } => return None,
104 };
105 Some((table_of(&self.doc, cell)?, cell))
106 }
107
108 pub fn selected_cells(&self) -> Option<Vec<Vec<BlockId>>> {
110 let BlockSelection::Cells { anchor, head } = self.selection() else {
111 return None;
112 };
113 let table = table_of(&self.doc, *anchor)?;
114 let (ar, ac) = cell_coords(&self.doc, table, *anchor)?;
115 let (hr, hc) = cell_coords(&self.doc, table, *head)?;
116 let grid = grid(&self.doc, table);
117 Some(
118 (ar.min(hr)..=ar.max(hr))
119 .map(|r| {
120 (ac.min(hc)..=ac.max(hc))
121 .filter_map(|c| grid[r].get(c).copied())
122 .collect()
123 })
124 .collect(),
125 )
126 }
127
128 pub(crate) fn clear_cells(&mut self, origin: EditOrigin) -> Result<bool, CommandError> {
131 let Some(cells) = self.selected_cells() else {
132 return Ok(false);
133 };
134 let first = cells.first().and_then(|r| r.first()).copied();
135 self.transact(origin, |tx| {
136 for cell in cells.iter().flatten() {
137 let len = tx.doc.leaf_len(*cell).unwrap_or(0);
138 if len > 0 {
139 tx.replace_text(*cell, 0..len, InlineText::new())?;
140 }
141 }
142 Ok(first.map(|c| BlockSelection::caret(BlockPosition::new(c, 0))))
143 })?;
144 if let Some(first) = first {
145 let _ = self.set_selection(BlockSelection::caret(BlockPosition::new(first, 0)));
147 }
148 Ok(true)
149 }
150
151 pub(crate) fn cells_markdown(&self) -> Option<String> {
153 let cells = self.selected_cells()?;
154 let cell_text = |id: &BlockId| {
155 let inline = self.doc.inline(*id).cloned().unwrap_or_default();
156 super::serialize::inline_markdown(&inline)
157 };
158 let mut out = String::new();
159 for (r, row) in cells.iter().enumerate() {
160 let line: Vec<String> = row.iter().map(cell_text).collect();
161 out.push_str(&format!("| {} |\n", line.join(" | ")));
162 if r == 0 {
163 let sep: Vec<&str> = row.iter().map(|_| "---").collect();
164 out.push_str(&format!("| {} |\n", sep.join(" | ")));
165 }
166 }
167 Some(out.trim_end().to_string())
168 }
169
170 pub(crate) fn tab_cell(&mut self, back: bool) -> Result<bool, CommandError> {
174 let Some((table, cell)) = self.current_cell() else {
175 return Ok(false);
176 };
177 let cells: Vec<BlockId> = grid(&self.doc, table).into_iter().flatten().collect();
178 let Some(ix) = cells.iter().position(|c| *c == cell) else {
179 return Ok(false);
180 };
181 let target = if back {
182 match ix.checked_sub(1) {
183 Some(i) => cells[i],
184 None => return Ok(true),
185 }
186 } else if let Some(next) = cells.get(ix + 1) {
187 *next
188 } else {
189 let cols = self
190 .doc
191 .children(*self.doc.children(table).last().unwrap_or(&table))
192 .len();
193 let row = empty_row(cols.max(1));
194 let first = match &row.content {
195 super::step::DetachedContent::Children(c) => c[0].id,
196 _ => unreachable!(),
197 };
198 let rows = self.doc.children(table).len();
199 self.transact(EditOrigin::Command("table_add_row"), |tx| {
200 tx.insert(table, rows, vec![row])?;
201 Ok(Some(BlockSelection::caret(BlockPosition::new(first, 0))))
202 })?;
203 return Ok(true);
204 };
205 let len = self.doc.leaf_len(target).unwrap_or(0);
206 self.set_selection(BlockSelection::Text {
207 anchor: BlockPosition::new(target, 0),
208 head: BlockPosition::new(target, len),
209 })?;
210 Ok(true)
211 }
212
213 pub(crate) fn insert_table(&mut self, rows: usize, cols: usize) -> Result<bool, CommandError> {
216 let pos = self
217 .caret()
218 .ok_or(CommandError::Unsupported("block selection"))?;
219 if table_of(&self.doc, pos.block).is_some() {
220 return Err(CommandError::Unsupported("table inside a table"));
221 }
222 let (rows, cols) = (rows.max(1), cols.max(1));
223 let table = DetachedBlock::container(
224 BlockKindData::Table {
225 aligns: vec![ColumnAlign::default(); cols],
226 },
227 (0..rows).map(|_| empty_row(cols)).collect(),
228 );
229 let mut ids = Vec::new();
230 table.ids(&mut ids);
231 let first = ids[2];
232 let empty = self.doc.leaf_len(pos.block) == Some(0)
233 && self.doc.kind(pos.block) == Some(&BlockKindData::Paragraph);
234 self.transact(EditOrigin::Command("insert_table"), |tx| {
235 let (parent, index) = tx
236 .doc
237 .index_in_parent(pos.block)
238 .ok_or(PositionError::UnknownBlock(pos.block))?;
239 tx.insert(parent, index + 1, vec![table])?;
240 if empty {
242 tx.remove_block(pos.block)?;
243 }
244 Ok(Some(BlockSelection::caret(BlockPosition::new(first, 0))))
245 })?;
246 Ok(true)
247 }
248
249 pub(crate) fn table_span(
252 &self,
253 ) -> Option<(
254 BlockId,
255 std::ops::RangeInclusive<usize>,
256 std::ops::RangeInclusive<usize>,
257 )> {
258 let (table, head) = self.current_cell()?;
259 let (hr, hc) = cell_coords(&self.doc, table, head)?;
260 let (ar, ac) = match self.selection() {
261 BlockSelection::Cells { anchor, .. } => cell_coords(&self.doc, table, *anchor)?,
262 BlockSelection::Text { anchor, .. } if anchor.block == head => (hr, hc),
263 BlockSelection::Text { anchor, .. } => {
264 cell_coords(&self.doc, table, anchor.block).unwrap_or((hr, hc))
265 }
266 BlockSelection::Blocks { .. } => return None,
267 };
268 Some((table, ar.min(hr)..=ar.max(hr), ac.min(hc)..=ac.max(hc)))
269 }
270
271 pub fn whole_columns_selected(&self) -> bool {
274 matches!(self.selection(), BlockSelection::Cells { .. })
275 && self.table_span().is_some_and(|(table, rows, _)| {
276 *rows.start() == 0 && *rows.end() + 1 == self.doc.children(table).len()
277 })
278 }
279
280 pub fn column_align_at_caret(&self) -> Option<ColumnAlign> {
282 let (table, _, cols) = self.table_span()?;
283 Some(
284 aligns(&self.doc, table)
285 .get(*cols.start())
286 .copied()
287 .unwrap_or_default(),
288 )
289 }
290
291 pub fn select_table_line(
293 &mut self,
294 table: BlockId,
295 line: TableLine,
296 ) -> Result<bool, CommandError> {
297 let grid = grid(&self.doc, table);
298 let (anchor, head) = match line {
299 TableLine::Row(r) => {
300 let Some(row) = grid.get(r) else {
301 return Ok(false);
302 };
303 match (row.first(), row.last()) {
304 (Some(a), Some(h)) => (*a, *h),
305 _ => return Ok(false),
306 }
307 }
308 TableLine::Column(c) => {
309 let (Some(a), Some(h)) = (
310 grid.first().and_then(|r| r.get(c)),
311 grid.last().and_then(|r| r.get(c)),
312 ) else {
313 return Ok(false);
314 };
315 (*a, *h)
316 }
317 };
318 self.set_selection(BlockSelection::Cells { anchor, head })?;
319 Ok(true)
320 }
321
322 pub(crate) fn edit_table(&mut self, edit: TableEdit) -> Result<bool, CommandError> {
326 let Some((table, rsel, csel)) = self.table_span() else {
327 return Err(CommandError::Unsupported("caret is not in a table"));
328 };
329 let (r0, r1, c0, c1) = (*rsel.start(), *rsel.end(), *csel.start(), *csel.end());
330 let grid = grid(&self.doc, table);
331 let rows = grid.len();
332 let cols = grid.iter().map(Vec::len).max().unwrap_or(0);
333 let row_ids = self.doc.children(table).to_vec();
334 let mut aligns = aligns(&self.doc, table);
335 aligns.resize(cols, ColumnAlign::default());
336 let remove_table = matches!(edit, TableEdit::RemoveRow if r0 == 0 && r1 + 1 >= rows)
337 || matches!(edit, TableEdit::RemoveColumn if c0 == 0 && c1 + 1 >= cols);
338 if remove_table {
339 self.set_selection(BlockSelection::Blocks {
340 anchor: table,
341 head: table,
342 })?;
343 return self.run(super::command::EditorCommand::DeleteSelection);
344 }
345 let r = r0;
346 let c = c0;
347 let cell_at = |row: usize, col: usize| -> BlockId {
348 let row = &grid[row.min(rows - 1)];
349 row[col.min(row.len().saturating_sub(1))]
350 };
351 self.transact(EditOrigin::Command("table_edit"), |tx| {
352 let caret_cell = match edit {
353 TableEdit::AddRowAbove | TableEdit::AddRowBelow => {
354 let at = if edit == TableEdit::AddRowBelow {
355 r1 + 1
356 } else {
357 r0
358 };
359 let row = empty_row(cols);
360 let mut ids = Vec::new();
361 row.ids(&mut ids);
362 tx.insert(table, at, vec![row])?;
363 ids[1 + c.min(cols - 1)]
364 }
365 TableEdit::RemoveRow => {
366 tx.remove(table, r0..r1 + 1)?;
367 if r1 + 1 < rows {
368 cell_at(r1 + 1, c)
369 } else {
370 cell_at(r0 - 1, c)
371 }
372 }
373 TableEdit::AddColumnLeft | TableEdit::AddColumnRight => {
374 let at = if edit == TableEdit::AddColumnRight {
375 c1 + 1
376 } else {
377 c0
378 };
379 let mut caret_cell = cell_at(r, c);
380 for (ri, row) in row_ids.iter().enumerate() {
381 let new = empty_cell();
382 if ri == r {
383 caret_cell = new.id;
384 }
385 let at = at.min(tx.doc.children(*row).len());
386 tx.insert(*row, at, vec![new])?;
387 }
388 aligns.insert(at.min(aligns.len()), ColumnAlign::default());
389 tx.set_kind(
390 table,
391 BlockKindData::Table {
392 aligns: aligns.clone(),
393 },
394 )?;
395 caret_cell
396 }
397 TableEdit::RemoveColumn => {
398 for row in &row_ids {
399 let len = tx.doc.children(*row).len();
400 if c0 < len {
401 tx.remove(*row, c0..(c1 + 1).min(len))?;
402 }
403 }
404 aligns.drain(c0..(c1 + 1).min(aligns.len()));
405 tx.set_kind(
406 table,
407 BlockKindData::Table {
408 aligns: aligns.clone(),
409 },
410 )?;
411 if c1 + 1 < cols {
412 cell_at(r, c1 + 1)
413 } else {
414 cell_at(r, c0 - 1)
415 }
416 }
417 TableEdit::SetColumnAlign(align) => {
418 for a in &mut aligns[c0..=c1.min(cols - 1)] {
419 *a = align;
420 }
421 tx.set_kind(
422 table,
423 BlockKindData::Table {
424 aligns: aligns.clone(),
425 },
426 )?;
427 return Ok(None);
429 }
430 };
431 Ok(Some(BlockSelection::caret(BlockPosition::new(
432 caret_cell, 0,
433 ))))
434 })?;
435 Ok(true)
436 }
437
438 pub(crate) fn table_enter(&mut self) -> Result<bool, CommandError> {
445 let Some((table, cell)) = self.current_cell() else {
446 return Ok(false);
447 };
448 let Some((r, c)) = cell_coords(&self.doc, table, cell) else {
449 return Ok(false);
450 };
451 let grid = grid(&self.doc, table);
452 let row_empty = grid[r]
453 .iter()
454 .all(|id| self.doc.leaf_len(*id).unwrap_or(0) == 0);
455 if r > 0 && row_empty {
456 let para = empty_paragraph();
457 let target = para.id;
458 self.transact(EditOrigin::Command("table_exit_row"), |tx| {
459 tx.remove(table, r..r + 1)?;
460 let (parent, index) = tx
461 .doc
462 .index_in_parent(table)
463 .ok_or(PositionError::UnknownBlock(table))?;
464 tx.insert(parent, index + 1, vec![para])?;
465 Ok(Some(BlockSelection::caret(BlockPosition::new(target, 0))))
466 })?;
467 return Ok(true);
468 }
469 if r + 1 == grid.len() {
470 let cols = grid.iter().map(Vec::len).max().unwrap_or(1).max(1);
471 let row = empty_row(cols);
472 let mut ids = Vec::new();
473 row.ids(&mut ids);
474 let first = ids[1];
475 let rows = grid.len();
476 self.transact(EditOrigin::Command("table_add_row"), |tx| {
477 tx.insert(table, rows, vec![row])?;
478 Ok(Some(BlockSelection::caret(BlockPosition::new(first, 0))))
479 })?;
480 return Ok(true);
481 }
482 let next = &grid[r + 1];
483 let target = next[c.min(next.len().saturating_sub(1))];
484 self.set_selection(BlockSelection::caret(BlockPosition::new(target, 0)))?;
485 Ok(true)
486 }
487
488 pub(crate) fn cell_soft_break(&mut self) -> Result<bool, CommandError> {
490 let Some((_, cell)) = self.current_cell() else {
491 return Ok(false);
492 };
493 let BlockSelection::Text { anchor, head } = self.selection().clone() else {
494 return Ok(false);
495 };
496 if anchor.block != cell || head.block != cell {
497 return Ok(false);
498 }
499 let (lo, hi) = (
500 anchor.offset.min(head.offset),
501 anchor.offset.max(head.offset),
502 );
503 let marks = self
504 .doc
505 .inline(cell)
506 .and_then(|i| i.mark_before(lo).cloned())
507 .unwrap_or_default();
508 self.transact(EditOrigin::Command("cell_soft_break"), |tx| {
509 tx.replace_text(cell, lo..hi, InlineText::plain("\n", marks))?;
510 Ok(Some(BlockSelection::caret(BlockPosition::new(
511 cell,
512 lo + 1,
513 ))))
514 })?;
515 Ok(true)
516 }
517
518 pub(crate) fn exit_table(&mut self) -> Result<bool, CommandError> {
522 let Some((table, _)) = self.current_cell() else {
523 return Ok(false);
524 };
525 let Some((parent, index)) = self.doc.index_in_parent(table) else {
526 return Ok(false);
527 };
528 if let Some(next) = self.doc.children(parent).get(index + 1).copied()
529 && self.doc.inline(next).is_some()
530 {
531 self.set_selection(BlockSelection::caret(BlockPosition::new(next, 0)))?;
532 return Ok(true);
533 }
534 let para = empty_paragraph();
535 let target = para.id;
536 self.transact(EditOrigin::Command("table_exit"), |tx| {
537 tx.insert(parent, index + 1, vec![para])?;
538 Ok(Some(BlockSelection::caret(BlockPosition::new(target, 0))))
539 })?;
540 Ok(true)
541 }
542
543 pub(crate) fn in_last_table_row(&self) -> bool {
545 self.current_cell().is_some_and(|(table, cell)| {
546 cell_coords(&self.doc, table, cell)
547 .is_some_and(|(r, _)| r + 1 == self.doc.children(table).len())
548 })
549 }
550
551 pub(crate) fn paragraph_to_table(&mut self, block: BlockId) -> Result<bool, CommandError> {
556 if self.doc.kind(block) != Some(&BlockKindData::Paragraph)
557 || table_of(&self.doc, block).is_some()
558 {
559 return Ok(false);
560 }
561 let Some(inline) = self.doc.inline(block).cloned() else {
562 return Ok(false);
563 };
564 let Some(ranges) = table_row_cells(inline.text()) else {
565 return Ok(false);
566 };
567 let cols = ranges.len();
568 let header = DetachedBlock::container(
569 BlockKindData::TableRow,
570 ranges
571 .into_iter()
572 .map(|r| {
573 DetachedBlock::leaf(
574 BlockKindData::TableCell,
575 BlockContent::Inline(inline.slice(r)),
576 )
577 })
578 .collect(),
579 );
580 let body = empty_row(cols);
581 let mut ids = Vec::new();
582 body.ids(&mut ids);
583 let first = ids[1];
584 let table = DetachedBlock::container(
585 BlockKindData::Table {
586 aligns: vec![ColumnAlign::None; cols],
587 },
588 vec![header, body],
589 );
590 self.transact(EditOrigin::InputRule("table"), |tx| {
591 let (parent, index) = tx
592 .doc
593 .index_in_parent(block)
594 .ok_or(PositionError::UnknownBlock(block))?;
595 tx.remove_block(block)?;
596 tx.insert(parent, index, vec![table])?;
597 Ok(Some(BlockSelection::caret(BlockPosition::new(first, 0))))
598 })?;
599 Ok(true)
600 }
601}
602
603fn empty_paragraph() -> DetachedBlock {
604 DetachedBlock::leaf(
605 BlockKindData::Paragraph,
606 BlockContent::Inline(InlineText::new()),
607 )
608}
609
610pub(crate) fn table_row_cells(text: &str) -> Option<Vec<std::ops::Range<usize>>> {
613 let start = text.len() - text.trim_start().len();
614 let end = text.trim_end().len();
615 let line = &text[start..end];
616 if line.len() < 2 || !line.starts_with('|') || !line.ends_with('|') || line.contains('\n') {
617 return None;
618 }
619 let mut bars = Vec::new();
620 let bytes = text.as_bytes();
621 for i in start..end {
622 if bytes[i] == b'|' && (i == 0 || bytes[i - 1] != b'\\') {
623 bars.push(i);
624 }
625 }
626 if bars.len() < 2 {
627 return None;
628 }
629 Some(
630 bars.windows(2)
631 .map(|w| {
632 let (a, b) = (w[0] + 1, w[1]);
633 let cell = &text[a..b];
634 let lead = cell.len() - cell.trim_start().len();
635 a + lead..a + cell.trim_end().len()
636 })
637 .map(|r| if r.start > r.end { r.start..r.start } else { r })
638 .collect(),
639 )
640}