Skip to main content

gpui_base/text/
node.rs

1use std::{
2    borrow::Cow,
3    collections::HashMap,
4    ops::Range,
5    sync::{Arc, Mutex, OnceLock, Weak},
6};
7
8use gpui::{
9    AnyElement, App, Axis, Bounds, DefiniteLength, Div, Element, ElementId, FontStyle, FontWeight,
10    GlobalElementId, HighlightStyle, Hsla, Image, ImageFormat, ImageSource, InspectorElementId,
11    InteractiveElement as _, IntoElement, IsZero as _, LayoutId, ObjectFit, Overflow,
12    ParentElement, Pixels, Point, Refineable as _, Rems, ScrollHandle, SharedString, SharedUri,
13    StatefulInteractiveElement, StyleRefinement, Styled, StyledImage as _, TextStyleRefinement,
14    WhiteSpace, Window, div, img, prelude::FluentBuilder as _, px, relative, rems,
15};
16use markdown::mdast;
17
18use crate::{
19    GlobalState, ScrollableMask, Scrollbar, StyledExt, h_flex,
20    scrollable_mask::horizontal_scroll_area,
21    text::{
22        CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, MarkdownExtensions,
23        MarkdownNode, TableActionsFn,
24        document::{NodeRenderOptions, PrevBlock, flow_position},
25        inline::{
26            Inline, InlineHighlight, InlineState, code_insets, combine_highlights, fade_highlights,
27            point_in_text_selection, text_runs, text_size_ranges,
28        },
29        inline_flow::{InlineFlow, InlineFlowItem, slice_ranges},
30        range_highlight::{RangeHighlightFrame, RevealAt, RevealRequest},
31        stream_fade::{StreamFadeFrame, TextLeafKey},
32        text_view::handle_link_click,
33    },
34    theme::ActiveTheme as _,
35};
36
37use super::{
38    SelectionFormat, TextViewStyle,
39    utils::{data_url_image, list_item_prefix, ordered_list_ordinal},
40};
41
42const CHECK_SVG_LIGHT: &[u8] = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none"><path d="m3.25 8.25 3 3 6.5-7" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>"#;
43const CHECK_SVG_DARK: &[u8] = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none"><path d="m3.25 8.25 3 3 6.5-7" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>"#;
44
45/// The block-level nodes.
46///
47/// This is the document tree TextView renders. Together with
48/// [`editor_host`](super::editor_host) it is also the hand-over type for
49/// external editors: they build the tree, render it with
50/// [`BlockNode::render`] and read geometry back per leaf.
51#[derive(Debug, Clone, PartialEq)]
52#[non_exhaustive]
53pub enum BlockNode {
54    /// Something like a Div container in HTML.
55    Root {
56        children: Vec<BlockNode>,
57        span: Option<Span>,
58    },
59    Paragraph(Paragraph),
60    Heading {
61        level: u8,
62        children: Paragraph,
63        span: Option<Span>,
64    },
65    Blockquote {
66        children: Vec<BlockNode>,
67        span: Option<Span>,
68    },
69    List {
70        /// Only contains ListItem, others will be ignored
71        children: Vec<BlockNode>,
72        ordered: bool,
73        /// The first ordinal for an ordered list. HTML lists leave this unset.
74        start: Option<u32>,
75        span: Option<Span>,
76    },
77    ListItem {
78        children: Vec<BlockNode>,
79        spread: bool,
80        /// Whether the list item is checked, if None, it's not a checkbox
81        checked: Option<bool>,
82        span: Option<Span>,
83    },
84    CodeBlock(CodeBlock),
85    /// A custom Markdown node produced by [`MarkdownExtensions`].
86    Custom {
87        node: MarkdownNode,
88        state: BlockState,
89    },
90    Table(Table),
91    Break {
92        html: bool,
93        span: Option<Span>,
94    },
95    HorizontalRule {
96        span: Option<Span>,
97    },
98    /// Use for to_markdown get raw definition
99    Definition {
100        identifier: SharedString,
101        url: SharedString,
102        title: Option<SharedString>,
103        span: Option<Span>,
104    },
105    Unknown,
106}
107
108/// Retained block interaction state, shared with its rendered element.
109///
110/// Opaque: build one with `BlockState::default()` for a
111/// [`BlockNode::Custom`].
112#[derive(Debug, Clone, Default)]
113pub struct BlockState {
114    // None is unobserved, allowing virtualized copy to include enclosed blocks.
115    selection: Arc<Mutex<Option<BlockSelection>>>,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum BlockSelection {
120    Empty,
121    Full,
122}
123
124impl BlockState {
125    fn selection(&self) -> Option<BlockSelection> {
126        self.selection.lock().ok().and_then(|selection| *selection)
127    }
128
129    fn set_selection(&self, value: Option<BlockSelection>) {
130        if let Ok(mut selection) = self.selection.lock() {
131            *selection = value;
132        }
133    }
134
135    fn is_selected(&self) -> bool {
136        self.selection() == Some(BlockSelection::Full)
137    }
138
139    fn has_selection_observation(&self) -> bool {
140        self.selection().is_some()
141    }
142
143    fn clear_selection(&self) {
144        self.set_selection(None);
145    }
146}
147
148impl PartialEq for BlockState {
149    fn eq(&self, _: &Self) -> bool {
150        // Runtime selection does not change parsed document identity.
151        true
152    }
153}
154
155pub(super) struct CustomBlockSelectionProjection {
156    state: BlockState,
157    bounds: Bounds<Pixels>,
158}
159
160impl CustomBlockSelectionProjection {
161    pub(super) fn project(&self, start: Point<Pixels>, end: Point<Pixels>) {
162        self.state
163            .set_selection(custom_block_selection(self.bounds, start, end));
164    }
165
166    pub(super) fn deselect(&self) {
167        self.state.set_selection(None);
168    }
169}
170
171fn custom_block_selection(
172    bounds: Bounds<Pixels>,
173    start: Point<Pixels>,
174    end: Point<Pixels>,
175) -> Option<BlockSelection> {
176    if bounds.size.width <= Pixels::ZERO
177        || bounds.size.height <= Pixels::ZERO
178        || bounds.bottom() <= start.y.min(end.y)
179        || bounds.top() > start.y.max(end.y)
180    {
181        return None;
182    }
183    Some(if custom_block_is_selected(bounds, start, end) {
184        BlockSelection::Full
185    } else {
186        BlockSelection::Empty
187    })
188}
189
190/// A table cell, laid out as its content. Text painted inside it knows its
191/// cell, so a drag that starts in the cell selects only in that cell.
192struct TableCellElement(AnyElement);
193
194impl IntoElement for TableCellElement {
195    type Element = Self;
196
197    fn into_element(self) -> Self {
198        self
199    }
200}
201
202impl Element for TableCellElement {
203    type RequestLayoutState = ();
204    type PrepaintState = ();
205
206    fn id(&self) -> Option<ElementId> {
207        None
208    }
209
210    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
211        None
212    }
213
214    fn request_layout(
215        &mut self,
216        _: Option<&GlobalElementId>,
217        _: Option<&InspectorElementId>,
218        window: &mut Window,
219        cx: &mut App,
220    ) -> (LayoutId, ()) {
221        (self.0.request_layout(window, cx), ())
222    }
223
224    fn prepaint(
225        &mut self,
226        _: Option<&GlobalElementId>,
227        _: Option<&InspectorElementId>,
228        bounds: Bounds<Pixels>,
229        _: &mut (),
230        window: &mut Window,
231        cx: &mut App,
232    ) {
233        // Prepaint runs for the whole window before any text paints, so the
234        // cell that holds the anchor is known to all text, in every view.
235        if let Some(view) = GlobalState::global(cx).text_view_state() {
236            view.read(cx)
237                .selection_adapter
238                .record_table_cell(bounds, cx);
239        }
240        GlobalState::global(cx).push_table_cell(bounds);
241        self.0.prepaint(window, cx);
242        GlobalState::global(cx).pop_table_cell();
243    }
244
245    fn paint(
246        &mut self,
247        _: Option<&GlobalElementId>,
248        _: Option<&InspectorElementId>,
249        bounds: Bounds<Pixels>,
250        _: &mut (),
251        _: &mut (),
252        window: &mut Window,
253        cx: &mut App,
254    ) {
255        GlobalState::global(cx).push_table_cell(bounds);
256        self.0.paint(window, cx);
257        GlobalState::global(cx).pop_table_cell();
258    }
259}
260
261/// Observe the plugin's own bounds without changing its layout or listeners.
262struct CustomBlockElement {
263    content: AnyElement,
264    state: BlockState,
265}
266
267impl IntoElement for CustomBlockElement {
268    type Element = Self;
269
270    fn into_element(self) -> Self {
271        self
272    }
273}
274
275impl Element for CustomBlockElement {
276    type RequestLayoutState = ();
277    type PrepaintState = ();
278
279    fn id(&self) -> Option<ElementId> {
280        None
281    }
282
283    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
284        None
285    }
286
287    fn request_layout(
288        &mut self,
289        _: Option<&GlobalElementId>,
290        _: Option<&InspectorElementId>,
291        window: &mut Window,
292        cx: &mut App,
293    ) -> (LayoutId, ()) {
294        (self.content.request_layout(window, cx), ())
295    }
296
297    fn prepaint(
298        &mut self,
299        _: Option<&GlobalElementId>,
300        _: Option<&InspectorElementId>,
301        _: Bounds<Pixels>,
302        _: &mut (),
303        window: &mut Window,
304        cx: &mut App,
305    ) {
306        self.content.prepaint(window, cx);
307    }
308
309    fn paint(
310        &mut self,
311        _: Option<&GlobalElementId>,
312        _: Option<&InspectorElementId>,
313        bounds: Bounds<Pixels>,
314        _: &mut (),
315        _: &mut (),
316        window: &mut Window,
317        cx: &mut App,
318    ) {
319        let view = GlobalState::global(cx).text_view_state();
320        let selection = view.as_ref().and_then(|view| {
321            let state = view.read(cx);
322            if !state.is_selectable() {
323                return None;
324            }
325            if state.is_all_selected() {
326                return Some(BlockSelection::Full);
327            }
328            if state.preserve_inline_selection {
329                return self.state.selection();
330            }
331            let (start, end) = state.painted_selection_points(cx)?;
332            custom_block_selection(bounds, start, end)
333        });
334        self.state.set_selection(selection);
335        if let Some(view) = &view {
336            if view.read(cx).is_selectable() {
337                view.read(cx).selection_adapter.register_copy_projection(
338                    super::selection_adapter::CopyProjection::CustomBlock(
339                        CustomBlockSelectionProjection {
340                            state: self.state.clone(),
341                            bounds,
342                        },
343                    ),
344                    cx,
345                );
346            }
347            let visible = bounds.intersect(&window.content_mask().bounds);
348            if view.read(cx).is_selectable()
349                && visible.size.width > Pixels::ZERO
350                && visible.size.height > Pixels::ZERO
351            {
352                view.read(cx)
353                    .selection_adapter
354                    .register_inline(vec![visible]);
355            }
356        }
357        self.content.paint(window, cx);
358        if selection == Some(BlockSelection::Full) {
359            let color = view.as_ref().unwrap().read(cx).text_view_style.selection();
360            window.paint_quad(gpui::fill(bounds, color));
361        }
362    }
363}
364
365/// A block is atomic: the selection takes it whole once it crosses the block's
366/// edge, and never from a gesture that stays inside it. A press and release
367/// within one block is a click on its content — often an interactive card —
368/// and the pointer commonly travels a few pixels during a quick click, so
369/// counting that travel as a drag would paint the whole block as selected.
370fn custom_block_is_selected(
371    bounds: Bounds<Pixels>,
372    start: Point<Pixels>,
373    end: Point<Pixels>,
374) -> bool {
375    let (start_inside, end_inside) = (bounds.contains(&start), bounds.contains(&end));
376    start != end
377        && !(start_inside && end_inside)
378        && (start_inside
379            || end_inside
380            || point_in_text_selection(
381                bounds.origin,
382                bounds.size.width,
383                start,
384                end,
385                bounds.size.height,
386            ))
387}
388
389/// How many styles a selection takes in, which decides whether
390/// [`SelectionFormat::SourceAcrossStyles`] copies the rendered text or the
391/// source.
392#[derive(Clone, Copy, Debug, PartialEq, Eq)]
393pub(super) enum SelectionStyles {
394    None,
395    One,
396    Mixed,
397}
398
399impl SelectionStyles {
400    /// Combine two separately selected parts. They are two styles even when
401    /// their marks match, because they sit in different blocks or cells.
402    fn merge(&mut self, other: Self) {
403        *self = match (*self, other) {
404            (Self::None, other) | (other, Self::None) => other,
405            _ => Self::Mixed,
406        };
407    }
408}
409
410#[derive(Clone, Copy)]
411enum BlockTextKind {
412    All,
413    Selected,
414    /// Like `Selected`, but reconstructs Markdown source for the selection
415    /// instead of the rendered plain text.
416    SelectedSource,
417}
418
419impl BlockNode {
420    pub(super) fn is_list_item(&self) -> bool {
421        matches!(self, Self::ListItem { .. })
422    }
423
424    /// Combine all children, omitting the empt parent nodes.
425    pub(super) fn compact(self) -> BlockNode {
426        match self {
427            Self::Root { mut children, .. } if children.len() == 1 => children.remove(0).compact(),
428            _ => self,
429        }
430    }
431
432    /// Get the span of the node.
433    pub fn span(&self) -> Option<Span> {
434        match self {
435            BlockNode::Root { span, .. } => *span,
436            BlockNode::Paragraph(paragraph) => paragraph.span,
437            BlockNode::Heading { span, .. } => *span,
438            BlockNode::Blockquote { span, .. } => *span,
439            BlockNode::List { span, .. } => *span,
440            BlockNode::ListItem { span, .. } => *span,
441            BlockNode::CodeBlock(code_block) => code_block.span,
442            BlockNode::Custom { node, .. } => node.span,
443            BlockNode::Table(table) => table.span,
444            BlockNode::Break { span, .. } => *span,
445            BlockNode::HorizontalRule { span, .. } => *span,
446            BlockNode::Definition { span, .. } => *span,
447            BlockNode::Unknown { .. } => None,
448        }
449    }
450
451    pub(super) fn text(&self) -> String {
452        self.text_by_kind(BlockTextKind::All)
453    }
454
455    /// The selected text within this block, in `format`.
456    ///
457    /// [`SelectionFormat::Source`] reconstructs the Markdown source of the
458    /// selection instead of the rendered text.
459    pub(super) fn selected_text(&self, format: SelectionFormat) -> String {
460        self.text_by_kind(match format {
461            SelectionFormat::Plain => BlockTextKind::Selected,
462            SelectionFormat::Source | SelectionFormat::SourceAcrossStyles => {
463                BlockTextKind::SelectedSource
464            }
465        })
466    }
467
468    /// How many styles the selection within this block takes in.
469    pub(super) fn selection_styles(&self) -> SelectionStyles {
470        let mut styles = SelectionStyles::None;
471        match self {
472            BlockNode::Root { children, .. }
473            | BlockNode::Blockquote { children, .. }
474            | BlockNode::List { children, .. }
475            | BlockNode::ListItem { children, .. } => {
476                for child in children {
477                    styles.merge(child.selection_styles());
478                }
479            }
480            BlockNode::Paragraph(paragraph) => styles = paragraph.selection_styles(),
481            BlockNode::Heading { children, .. } => styles = children.selection_styles(),
482            BlockNode::Table(table) => {
483                for row in &table.children {
484                    for cell in &row.children {
485                        styles.merge(cell.children.selection_styles());
486                    }
487                }
488            }
489            BlockNode::CodeBlock(code_block) => {
490                if code_block.has_selection() {
491                    styles = SelectionStyles::One;
492                }
493            }
494            // A plugin's text stands in for content its source spells out.
495            BlockNode::Custom { state, .. } => {
496                if state.is_selected() {
497                    styles = SelectionStyles::Mixed;
498                }
499            }
500            BlockNode::Definition { .. }
501            | BlockNode::Break { .. }
502            | BlockNode::HorizontalRule { .. }
503            | BlockNode::Unknown => {}
504        }
505        styles
506    }
507
508    pub(super) fn selected_source_range(&self) -> SourceRangeSelection {
509        let mut selected = SourceRangeSelection::Unselected;
510        match self {
511            BlockNode::Root { children, .. }
512            | BlockNode::Blockquote { children, .. }
513            | BlockNode::List { children, .. }
514            | BlockNode::ListItem { children, .. } => {
515                for child in children {
516                    selected.merge(child.selected_source_range());
517                }
518            }
519            BlockNode::Paragraph(paragraph) => selected = paragraph.selected_source_range(),
520            BlockNode::Heading { children, .. } => selected = children.selected_source_range(),
521            BlockNode::Table(table) => {
522                for row in &table.children {
523                    for cell in &row.children {
524                        selected.merge(cell.children.selected_source_range());
525                    }
526                }
527            }
528            BlockNode::CodeBlock(code_block) => selected = code_block.selected_source_range(),
529            BlockNode::Custom { node, state } => {
530                if state.is_selected() {
531                    selected = node
532                        .source_range()
533                        .map_or(SourceRangeSelection::Unmapped, SourceRangeSelection::Mapped);
534                }
535            }
536            BlockNode::Definition { .. }
537            | BlockNode::Break { .. }
538            | BlockNode::HorizontalRule { .. }
539            | BlockNode::Unknown => {}
540        }
541        selected
542    }
543
544    fn text_by_kind(&self, kind: BlockTextKind) -> String {
545        let mut text = String::new();
546        match self {
547            BlockNode::Root { children, .. } => {
548                let block_text = Self::children_text(children, kind);
549                if !block_text.is_empty() {
550                    text.push_str(&block_text);
551                    text.push('\n');
552                }
553            }
554            BlockNode::Paragraph(paragraph) => {
555                let block_text = match kind {
556                    BlockTextKind::All => paragraph.text(),
557                    BlockTextKind::Selected => paragraph.selected_text(),
558                    BlockTextKind::SelectedSource => paragraph.selected_source(),
559                };
560                if !block_text.is_empty() {
561                    text.push_str(&block_text);
562                    text.push('\n');
563                }
564            }
565            BlockNode::Heading {
566                level, children, ..
567            } => {
568                let block_text = match kind {
569                    BlockTextKind::All => children.text(),
570                    BlockTextKind::Selected => children.selected_text(),
571                    BlockTextKind::SelectedSource => children.selected_source(),
572                };
573                if !block_text.is_empty() {
574                    // In source mode, prefix the heading marker so a selected
575                    // heading round-trips as Markdown (e.g. `## Title`).
576                    if matches!(kind, BlockTextKind::SelectedSource) {
577                        text.push_str(&"#".repeat(*level as usize));
578                        text.push(' ');
579                    }
580                    text.push_str(&block_text);
581                    text.push('\n');
582                }
583            }
584            BlockNode::List {
585                children,
586                ordered,
587                start,
588                ..
589            } => {
590                if matches!(kind, BlockTextKind::SelectedSource) {
591                    // Reconstruct the list source, indenting nested lists and
592                    // restoring list markers and task-list checkboxes.
593                    text.push_str(&list_selected_source(children, *ordered, *start, ""));
594                } else {
595                    text.push_str(&Self::children_text(children, kind));
596                }
597            }
598            BlockNode::ListItem { children, .. } => {
599                text.push_str(&Self::children_text(children, kind));
600            }
601            BlockNode::Blockquote { children, .. } => {
602                let block_text = Self::children_text(children, kind);
603
604                if !block_text.is_empty() {
605                    if matches!(kind, BlockTextKind::SelectedSource) {
606                        // Prefix every line with `> ` so a selected blockquote
607                        // round-trips as Markdown.
608                        let quoted = block_text
609                            .trim_end_matches('\n')
610                            .lines()
611                            .map(|line| {
612                                if line.is_empty() {
613                                    ">".to_string()
614                                } else {
615                                    format!("> {}", line)
616                                }
617                            })
618                            .collect::<Vec<_>>()
619                            .join("\n");
620                        text.push_str(&quoted);
621                    } else {
622                        text.push_str(&block_text);
623                    }
624                    text.push('\n');
625                }
626            }
627            BlockNode::Table(table) => {
628                if matches!(kind, BlockTextKind::SelectedSource) {
629                    let block_text = table_selected_source(table);
630                    if !block_text.is_empty() {
631                        text.push_str(&block_text);
632                        text.push('\n');
633                    }
634                } else {
635                    let mut block_text = String::new();
636                    for row in table.children.iter() {
637                        let mut row_texts = vec![];
638                        for cell in row.children.iter() {
639                            match kind {
640                                BlockTextKind::All => row_texts.push(cell.children.text()),
641                                // Source is handled above; only Selected reaches
642                                // here. A cell with nothing selected adds nothing,
643                                // so a selection in one cell copies only its text.
644                                _ => {
645                                    let selected = cell.children.selected_text();
646                                    if !selected.is_empty() {
647                                        row_texts.push(selected);
648                                    }
649                                }
650                            }
651                        }
652                        if !row_texts.is_empty() {
653                            block_text.push_str(&row_texts.join(" "));
654                            block_text.push('\n');
655                        }
656                    }
657
658                    if !block_text.is_empty() {
659                        text.push_str(&block_text);
660                        text.push('\n');
661                    }
662                }
663            }
664            BlockNode::CodeBlock(code_block) => {
665                let block_text = match kind {
666                    BlockTextKind::All => code_block.text(),
667                    BlockTextKind::Selected => code_block.selected_text(),
668                    BlockTextKind::SelectedSource => code_block.selected_source(),
669                };
670                if !block_text.is_empty() {
671                    text.push_str(&block_text);
672                    text.push('\n');
673                }
674            }
675            BlockNode::Custom { node, state } => {
676                if matches!(kind, BlockTextKind::All) || state.is_selected() {
677                    let content = match kind {
678                        BlockTextKind::SelectedSource => Cow::Owned(node.to_markdown()),
679                        _ => Cow::Borrowed(node.as_text()),
680                    };
681                    if !content.is_empty() {
682                        text.push_str(&content);
683                        text.push('\n');
684                    }
685                }
686            }
687            BlockNode::Definition { .. }
688            | BlockNode::Break { .. }
689            | BlockNode::HorizontalRule { .. }
690            | BlockNode::Unknown { .. } => {}
691        }
692
693        text
694    }
695
696    fn children_text(children: &[BlockNode], kind: BlockTextKind) -> String {
697        let mut text = String::new();
698        for child in children.iter() {
699            text.push_str(&child.text_by_kind(kind));
700        }
701
702        text
703    }
704
705    /// Synchronously clear the selection stored in every inline state.
706    ///
707    /// Mirrors the [`selected_text`](Self::selected_text) traversal so the
708    /// selection can be cleared without relying on a repaint.
709    /// Whether this block carries a selection, even an empty one.
710    ///
711    /// A block only learns its selection when it is painted, so this doubles as
712    /// "this block was on screen while the selection was made". An empty
713    /// selection is the caret left by the press that started the drag, which is
714    /// why it counts (see [`ParsedDocument::selected_text`]).
715    pub(super) fn has_selection(&self) -> bool {
716        match self {
717            BlockNode::Root { children, .. }
718            | BlockNode::Blockquote { children, .. }
719            | BlockNode::List { children, .. }
720            | BlockNode::ListItem { children, .. } => {
721                children.iter().any(|child| child.has_selection())
722            }
723            BlockNode::Paragraph(paragraph) => paragraph.has_selection(),
724            BlockNode::Heading { children, .. } => children.has_selection(),
725            BlockNode::Table(table) => table.children.iter().any(|row| {
726                row.children
727                    .iter()
728                    .any(|cell| cell.children.has_selection())
729            }),
730            BlockNode::CodeBlock(code_block) => code_block.has_selection(),
731            BlockNode::Custom { state, .. } => {
732                // An observed empty endpoint must suppress virtualized copy fallback.
733                state.has_selection_observation()
734            }
735            BlockNode::Definition { .. }
736            | BlockNode::Break { .. }
737            | BlockNode::HorizontalRule { .. }
738            | BlockNode::Unknown { .. } => false,
739        }
740    }
741
742    pub(super) fn clear_selection(&self) {
743        match self {
744            BlockNode::Root { children, .. }
745            | BlockNode::Blockquote { children, .. }
746            | BlockNode::List { children, .. }
747            | BlockNode::ListItem { children, .. } => {
748                for child in children.iter() {
749                    child.clear_selection();
750                }
751            }
752            BlockNode::Paragraph(paragraph) => paragraph.clear_selection(),
753            BlockNode::Heading { children, .. } => children.clear_selection(),
754            BlockNode::Table(table) => {
755                for row in table.children.iter() {
756                    for cell in row.children.iter() {
757                        cell.children.clear_selection();
758                    }
759                }
760            }
761            BlockNode::CodeBlock(code_block) => code_block.clear_selection(),
762            BlockNode::Custom { state, .. } => {
763                state.clear_selection();
764            }
765            BlockNode::Definition { .. }
766            | BlockNode::Break { .. }
767            | BlockNode::HorizontalRule { .. }
768            | BlockNode::Unknown { .. } => {}
769        }
770    }
771}
772
773#[allow(unused)]
774#[derive(Debug, Default, Clone, PartialEq)]
775pub struct LinkMark {
776    pub url: SharedString,
777    /// Optional identifier for footnotes.
778    pub identifier: Option<SharedString>,
779    pub title: Option<SharedString>,
780}
781
782#[derive(Debug, Default, Clone, PartialEq)]
783pub struct TextMark {
784    pub bold: bool,
785    pub italic: bool,
786    pub strikethrough: bool,
787    pub underline: bool,
788    pub code: bool,
789    /// Highlight (`<mark>`) the text with this background color.
790    ///
791    /// `None` means the text is not highlighted.
792    pub highlight: Option<Hsla>,
793    pub link: Option<LinkMark>,
794}
795
796impl TextMark {
797    pub fn bold(mut self) -> Self {
798        self.bold = true;
799        self
800    }
801
802    pub fn italic(mut self) -> Self {
803        self.italic = true;
804        self
805    }
806
807    pub fn strikethrough(mut self) -> Self {
808        self.strikethrough = true;
809        self
810    }
811
812    pub fn underline(mut self) -> Self {
813        self.underline = true;
814        self
815    }
816
817    pub fn code(mut self) -> Self {
818        self.code = true;
819        self
820    }
821
822    /// Mark the text as highlighted (`<mark>`) with the given background color.
823    pub fn highlight(mut self, color: Hsla) -> Self {
824        self.highlight = Some(color);
825        self
826    }
827
828    pub fn link(mut self, link: impl Into<LinkMark>) -> Self {
829        self.link = Some(link.into());
830        self
831    }
832
833    pub fn merge(&mut self, other: TextMark) {
834        self.bold |= other.bold;
835        self.italic |= other.italic;
836        self.strikethrough |= other.strikethrough;
837        self.underline |= other.underline;
838        self.code |= other.code;
839        if other.highlight.is_some() {
840            self.highlight = other.highlight;
841        }
842        if let Some(link) = other.link {
843            self.link = Some(link);
844        }
845    }
846}
847
848/// The bytes
849#[derive(Debug, Default, Copy, Clone, PartialEq)]
850pub struct Span {
851    pub start: usize,
852    pub end: usize,
853}
854
855#[allow(unused)]
856#[derive(Default, Clone)]
857pub struct ImageNode {
858    pub url: SharedUri,
859    pub link: Option<LinkMark>,
860    pub title: Option<SharedString>,
861    pub alt: Option<SharedString>,
862    pub width: Option<DefiniteLength>,
863    pub height: Option<DefiniteLength>,
864    pub(crate) span: Option<Span>,
865    /// The image a `data:` URL carries, decoded on first render and kept for
866    /// the node's lifetime so it is not decoded again every frame.
867    pub(super) embedded: OnceLock<Option<Arc<Image>>>,
868}
869
870impl ImageNode {
871    pub fn title(&self) -> String {
872        self.title
873            .clone()
874            .unwrap_or_else(|| self.alt.clone().unwrap_or_default())
875            .to_string()
876    }
877
878    /// The [`ImageSource`] to render, without granting implicit filesystem
879    /// access.
880    ///
881    /// A `data:` URL carries its image inline, so it is decoded here rather
882    /// than handed to GPUI's resource loader, which only fetches over HTTP.
883    /// Every other document-provided value remains URI-backed, including
884    /// `file://` and scheme-less strings.
885    pub(super) fn source(&self) -> ImageSource {
886        match self.embedded.get_or_init(|| data_url_image(&self.url)) {
887            Some(image) => ImageSource::Image(image.clone()),
888            None => self.url.clone().into(),
889        }
890    }
891}
892
893impl std::fmt::Debug for ImageNode {
894    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
895        f.debug_struct("ImageNode")
896            .field("url", &self.url)
897            .field("link", &self.link)
898            .field("title", &self.title)
899            .field("alt", &self.alt)
900            .field("width", &self.width)
901            .field("height", &self.height)
902            .field("span", &self.span)
903            .finish()
904    }
905}
906
907impl PartialEq for ImageNode {
908    fn eq(&self, other: &Self) -> bool {
909        self.url == other.url
910            && self.link == other.link
911            && self.title == other.title
912            && self.alt == other.alt
913            && self.width == other.width
914            && self.height == other.height
915            && self.span == other.span
916    }
917}
918
919#[derive(Debug, Clone, PartialEq)]
920pub(crate) struct SourceSegment {
921    pub(crate) rendered: Range<usize>,
922    pub(crate) source: Range<usize>,
923    /// Whether each rendered character came from a source character of the
924    /// same length, so that part of the segment maps to part of its source.
925    /// A decoded entity or an escape maps only as a whole, even an entity
926    /// whose characters take as many bytes as its source, like `&acE;`.
927    pub(crate) linear: bool,
928}
929
930pub(crate) enum SourceRangeSelection {
931    Unselected,
932    Mapped(Range<usize>),
933    Unmapped,
934}
935
936impl SourceRangeSelection {
937    pub(crate) fn merge(&mut self, other: Self) {
938        match (&mut *self, other) {
939            (_, Self::Unselected) => {}
940            (_, Self::Unmapped) => *self = Self::Unmapped,
941            (Self::Unselected, mapped @ Self::Mapped(_)) => *self = mapped,
942            (Self::Mapped(selected), Self::Mapped(range)) => {
943                selected.start = selected.start.min(range.start);
944                selected.end = selected.end.max(range.end);
945            }
946            (Self::Unmapped, Self::Mapped(_)) => {}
947        }
948    }
949
950    pub(crate) fn into_range(self) -> Option<Range<usize>> {
951        match self {
952            Self::Mapped(range) => Some(range),
953            Self::Unselected | Self::Unmapped => None,
954        }
955    }
956}
957
958fn source_range_for_segments(
959    segments: &[SourceSegment],
960    selection: Range<usize>,
961) -> Option<Range<usize>> {
962    fn mapped_source_start(segment: &SourceSegment, rendered_start: usize) -> usize {
963        if segment.linear {
964            segment.source.start + rendered_start.saturating_sub(segment.rendered.start)
965        } else {
966            segment.source.start
967        }
968    }
969
970    fn mapped_source_end(segment: &SourceSegment, rendered_end: usize) -> usize {
971        if segment.linear {
972            segment.source.start
973                + rendered_end
974                    .min(segment.rendered.end)
975                    .saturating_sub(segment.rendered.start)
976        } else {
977            segment.source.end
978        }
979    }
980
981    if selection.start >= selection.end {
982        return None;
983    }
984
985    let mut overlapping = segments.iter().filter(|segment| {
986        segment.rendered.start < selection.end && segment.rendered.end > selection.start
987    });
988    let first = overlapping.next()?;
989    if first.rendered.start > selection.start {
990        return None;
991    }
992    let mut rendered_end = first.rendered.end;
993    let source_start = mapped_source_start(first, selection.start);
994    let mut source_end = mapped_source_end(first, selection.end);
995    for segment in overlapping {
996        if segment.rendered.start > rendered_end {
997            return None;
998        }
999        rendered_end = rendered_end.max(segment.rendered.end);
1000        source_end = mapped_source_end(segment, selection.end);
1001    }
1002    (rendered_end >= selection.end).then_some(source_start..source_end)
1003}
1004
1005/// One run of inline content in a [`Paragraph`]: marked text, an image or a
1006/// custom inline node.
1007#[derive(Default, Clone, Debug)]
1008pub struct InlineNode {
1009    /// The text content.
1010    pub(crate) text: SharedString,
1011    pub(crate) image: Option<ImageNode>,
1012    pub(crate) custom: Option<MarkdownNode>,
1013    custom_selection: Arc<Mutex<bool>>,
1014    /// The text styles, each tuple contains the range of the text and the style.
1015    pub(crate) marks: Vec<(Range<usize>, TextMark)>,
1016    /// Rendered UTF-8 byte spans paired with their exact Markdown source spans.
1017    pub(crate) source_segments: Vec<SourceSegment>,
1018
1019    pub(super) state: Arc<Mutex<InlineState>>,
1020}
1021
1022impl PartialEq for InlineNode {
1023    fn eq(&self, other: &Self) -> bool {
1024        self.text == other.text
1025            && self.image == other.image
1026            && self.custom == other.custom
1027            && self.marks == other.marks
1028            && self.source_segments == other.source_segments
1029    }
1030}
1031
1032fn set_leaf_key(state: &mut Arc<Mutex<InlineState>>, key: u64) {
1033    let text = state
1034        .lock()
1035        .map(|state| state.text.clone())
1036        .unwrap_or_default();
1037    // Rekeying creates a new rendered identity. Do not mutate the state of
1038    // another AST clone (or carry its transient selection into this instance).
1039    *state = Arc::new(Mutex::new(InlineState {
1040        text,
1041        editor_key: Some(key),
1042        cleanup_senders: Vec::new(),
1043        ..Default::default()
1044    }));
1045}
1046
1047/// Wrap `text` with the Markdown syntax implied by `mark`.
1048///
1049/// This mirrors the per-mark formatting in [`Paragraph::to_markdown`] but
1050/// operates on an already-sliced run, so it can reconstruct the Markdown
1051/// source for a *partial* text selection. Applied inside-out (innermost markup
1052/// first) so nested emphasis like `**_x_**` round-trips.
1053pub(crate) fn wrap_with_mark(text: &str, mark: &TextMark) -> String {
1054    if text.is_empty() {
1055        return String::new();
1056    }
1057
1058    let mut out = text.to_string();
1059    if mark.code {
1060        out = format!("`{}`", out);
1061    }
1062    if mark.italic {
1063        out = format!("*{}*", out);
1064    }
1065    if mark.bold {
1066        out = format!("**{}**", out);
1067    }
1068    if mark.strikethrough {
1069        out = format!("~~{}~~", out);
1070    }
1071    if mark.underline {
1072        // Markdown has no underline syntax, and `__` reads as bold to most
1073        // parsers, so fall back to the inline HTML `<u>` parses from.
1074        out = format!("<u>{}</u>", out);
1075    }
1076    if mark.highlight.is_some() {
1077        out = format!("=={}==", out);
1078    }
1079    if let Some(link) = &mark.link {
1080        out = match &link.title {
1081            Some(title) => format!("[{}]({} \"{}\")", out, link.url, title),
1082            None => format!("[{}]({})", out, link.url),
1083        };
1084    }
1085    out
1086}
1087
1088/// Keep syntax and marks separate until all selected runs and atomic objects
1089/// have been collected. Wrapping every run independently creates adjacent `*`
1090/// delimiters that can turn italic formulas into bold ones when pasted.
1091#[derive(Default)]
1092struct MarkdownSource {
1093    pieces: Vec<(String, Vec<TextMark>)>,
1094    /// Whether an inline object or image was emitted, whose rendered text
1095    /// stands in for source that spells out more.
1096    has_objects: bool,
1097}
1098
1099impl MarkdownSource {
1100    fn push_str(&mut self, source: &str) {
1101        self.push_marked(source, &TextMark::default());
1102    }
1103
1104    fn push_marked(&mut self, source: &str, mark: &TextMark) {
1105        if source.is_empty() {
1106            return;
1107        }
1108        // Outer-to-inner order for equally extensive marks. When a mark spans
1109        // more neighboring pieces, serialize it outside the shorter marks.
1110        let mut layers = Vec::new();
1111        if let Some(link) = &mark.link {
1112            layers.push(TextMark {
1113                link: Some(link.clone()),
1114                ..Default::default()
1115            });
1116        }
1117        if let Some(highlight) = mark.highlight {
1118            layers.push(TextMark {
1119                highlight: Some(highlight),
1120                ..Default::default()
1121            });
1122        }
1123        if mark.underline {
1124            layers.push(TextMark::default().underline());
1125        }
1126        if mark.strikethrough {
1127            layers.push(TextMark::default().strikethrough());
1128        }
1129        if mark.bold {
1130            layers.push(TextMark::default().bold());
1131        }
1132        if mark.italic {
1133            layers.push(TextMark::default().italic());
1134        }
1135        if mark.code {
1136            layers.push(TextMark::default().code());
1137        }
1138        self.pieces.push((source.to_string(), layers));
1139    }
1140
1141    fn push_text(
1142        &mut self,
1143        text: &str,
1144        marks: &[(Range<usize>, TextMark)],
1145        selection: Range<usize>,
1146    ) {
1147        let start = selection.start.min(text.len());
1148        let end = selection.end.min(text.len());
1149        if start >= end {
1150            return;
1151        }
1152        let mut cursor = start;
1153        for (range, mark) in marks {
1154            let lo = range.start.max(start);
1155            let hi = range.end.min(end);
1156            if lo >= hi {
1157                continue;
1158            }
1159            if cursor < lo {
1160                self.push_str(&text[cursor..lo]);
1161            }
1162            self.push_marked(&text[lo..hi], mark);
1163            cursor = hi;
1164        }
1165        if cursor < end {
1166            self.push_str(&text[cursor..end]);
1167        }
1168    }
1169
1170    fn push_images(&mut self, images: &[String]) {
1171        if images.is_empty() {
1172            return;
1173        }
1174        self.has_objects = true;
1175        self.push_str(&images.join(""));
1176    }
1177
1178    fn push_object(&mut self, node: &InlineNode) {
1179        self.has_objects = true;
1180        let mut mark = TextMark::default();
1181        for (_, layer) in &node.marks {
1182            mark.merge(layer.clone());
1183        }
1184        self.push_marked(&node.custom.as_ref().unwrap().to_markdown(), &mark);
1185    }
1186
1187    fn is_empty(&self) -> bool {
1188        self.pieces.is_empty()
1189    }
1190
1191    fn styles(&self) -> SelectionStyles {
1192        if self.is_empty() {
1193            SelectionStyles::None
1194        } else if !self.has_objects && self.pieces.windows(2).all(|pair| pair[0].1 == pair[1].1) {
1195            SelectionStyles::One
1196        } else {
1197            SelectionStyles::Mixed
1198        }
1199    }
1200
1201    fn finish(mut self) -> String {
1202        fn write(pieces: &mut [(String, Vec<TextMark>)]) -> String {
1203            let mut out = String::new();
1204            let mut offset = 0;
1205            while offset < pieces.len() {
1206                let Some((mark, count)) = pieces[offset]
1207                    .1
1208                    .iter()
1209                    .map(|mark| {
1210                        let count = pieces[offset..]
1211                            .iter()
1212                            .take_while(|(_, layers)| layers.contains(mark))
1213                            .count();
1214                        (mark.clone(), count)
1215                    })
1216                    .reduce(|best, candidate| {
1217                        if candidate.1 > best.1 {
1218                            candidate
1219                        } else {
1220                            best
1221                        }
1222                    })
1223                else {
1224                    out.push_str(&pieces[offset].0);
1225                    offset += 1;
1226                    continue;
1227                };
1228                let group = &mut pieces[offset..offset + count];
1229                for (_, layers) in group.iter_mut() {
1230                    layers.retain(|layer| layer != &mark);
1231                }
1232                let content = write(group);
1233                out.push_str(&wrap_with_mark(&content, &mark));
1234                offset += count;
1235            }
1236            out
1237        }
1238        write(&mut self.pieces)
1239    }
1240}
1241
1242/// How a selection covers one rendered run, so the caller can tell whether it
1243/// continues into an adjacent inline image.
1244#[derive(Default)]
1245struct RunSelection {
1246    emitted: bool,
1247    at_start: bool,
1248    at_end: bool,
1249}
1250
1251/// Emit the selected part of one rendered run, preceded by the images the
1252/// selection has run into. `run` holds the run's children with their offset
1253/// into the run's concatenated text.
1254fn emit_run(
1255    state: &Arc<Mutex<InlineState>>,
1256    run: &[(usize, &InlineNode)],
1257    pending_images: &mut Vec<String>,
1258    out: &mut MarkdownSource,
1259) -> RunSelection {
1260    let mut selected = RunSelection::default();
1261    let Ok(state) = state.lock() else {
1262        return selected;
1263    };
1264    let Some(selection) = &state.selection else {
1265        return selected;
1266    };
1267    if selection.start >= selection.end {
1268        return selected;
1269    }
1270
1271    selected.at_start = selection.start == 0;
1272    selected.at_end = selection.end >= state.text.len();
1273
1274    for (start, child) in run {
1275        let end = start + child.text.len();
1276        let lo = selection.start.max(*start);
1277        let hi = selection.end.min(end);
1278        if lo >= hi {
1279            continue;
1280        }
1281
1282        if !selected.emitted {
1283            if selected.at_start {
1284                out.push_images(pending_images);
1285            }
1286            pending_images.clear();
1287        }
1288        selected.emitted = true;
1289
1290        out.push_text(&child.text, &child.marks, (lo - start)..(hi - start));
1291    }
1292
1293    selected
1294}
1295
1296/// The Markdown source for an inline image, e.g. `![alt](url "title")`.
1297fn image_markdown(image: &ImageNode) -> String {
1298    let alt = image.alt.clone().unwrap_or_default();
1299    let title = image
1300        .title
1301        .clone()
1302        .map_or(String::new(), |title| format!(" \"{}\"", title));
1303    format!("![{}]({}{})", alt, image.url, title)
1304}
1305
1306/// Reconstruct the Markdown source for the `selection` sub-range of a text run
1307/// carrying `marks`.
1308///
1309/// `selection` is a byte range into `text`. For each mark that overlaps the
1310/// selection, the overlapping slice is wrapped in the mark's Markdown syntax
1311/// (see [`wrap_with_mark`]); slices not covered by any mark are emitted
1312/// verbatim. This lets a rendered-offset selection be copied back as Markdown
1313/// source (e.g. selecting inside a `**bold**` run yields `**bold**`).
1314#[cfg(test)]
1315pub(crate) fn reconstruct_markdown(
1316    text: &str,
1317    marks: &[(Range<usize>, TextMark)],
1318    selection: Range<usize>,
1319) -> String {
1320    let mut source = MarkdownSource::default();
1321    source.push_text(text, marks, selection);
1322    source.finish()
1323}
1324
1325/// Reconstruct the Markdown source of the selected cells of `table`.
1326///
1327/// Cells emit their own selected source; rows are piped (`| a | b |`) and the
1328/// delimiter/alignment row is inserted after the first row, so a selected
1329/// table round-trips as a Markdown table. A selection in one cell (a drag that
1330/// started in a cell stays in it) is that cell's source alone. Returns an empty
1331/// string when no cell is selected.
1332fn table_selected_source(table: &Table) -> String {
1333    let cell_source = |cell: &TableCell| cell.children.selected_source().replace('\n', " ");
1334
1335    let mut selected = table
1336        .children
1337        .iter()
1338        .flat_map(|row| row.children.iter())
1339        .map(cell_source)
1340        .filter(|source| !source.trim().is_empty());
1341    match (selected.next(), selected.next()) {
1342        (None, _) => return String::new(),
1343        (Some(only), None) => return only.trim().to_string(),
1344        _ => {}
1345    }
1346
1347    let mut lines: Vec<String> = Vec::new();
1348    for (row_ix, row) in table.children.iter().enumerate() {
1349        let cells: Vec<String> = row
1350            .children
1351            .iter()
1352            .map(|cell| cell_source(cell).trim().to_string())
1353            .collect();
1354        lines.push(format!("| {} |", cells.join(" | ")));
1355
1356        // The Markdown delimiter row carries the column alignments and must
1357        // follow the header row.
1358        if row_ix == 0 {
1359            let aligns: Vec<String> = (0..row.children.len())
1360                .map(|ix| {
1361                    match table.column_align(ix) {
1362                        ColumnumnAlign::None | ColumnumnAlign::Left => ":--",
1363                        ColumnumnAlign::Center => ":-:",
1364                        ColumnumnAlign::Right => "--:",
1365                    }
1366                    .to_string()
1367                })
1368                .collect();
1369            lines.push(format!("| {} |", aligns.join(" | ")));
1370        }
1371    }
1372
1373    lines.join("\n")
1374}
1375
1376/// Reconstruct the Markdown source of the selected items of a list.
1377///
1378/// Restores the list marker (`- ` / `N. `) and task-list checkbox (`[x] ` /
1379/// `[ ] `) of each item, and recurses into nested lists with a deeper `indent`
1380/// so nesting is preserved. `indent` is the leading whitespace for this level;
1381/// nested levels are indented by the width of the parent marker so continuation
1382/// and sub-list lines align under the item text. Items with no selected content
1383/// are skipped but still consume an ordered number, so the remaining items keep
1384/// their original numbering.
1385fn list_selected_source(
1386    children: &[BlockNode],
1387    ordered: bool,
1388    start: Option<u32>,
1389    indent: &str,
1390) -> String {
1391    let mut out = String::new();
1392    let mut item_ix = 0usize;
1393
1394    for child in children {
1395        let BlockNode::ListItem {
1396            children: item_children,
1397            checked,
1398            ..
1399        } = child
1400        else {
1401            continue;
1402        };
1403
1404        let marker = if ordered {
1405            format!("{}. ", ordered_list_ordinal(start, item_ix))
1406        } else {
1407            "- ".to_string()
1408        };
1409        let checkbox = match checked {
1410            Some(true) => "[x] ",
1411            Some(false) => "[ ] ",
1412            None => "",
1413        };
1414        let child_indent = format!("{}{}", indent, " ".repeat(marker.len()));
1415
1416        // Split the item into its own content and any nested lists, so the
1417        // nested lists can be indented under the content.
1418        let mut content = String::new();
1419        let mut nested = String::new();
1420        for sub in item_children {
1421            if let BlockNode::List {
1422                children: sub_children,
1423                ordered: sub_ordered,
1424                start: sub_start,
1425                ..
1426            } = sub
1427            {
1428                nested.push_str(&list_selected_source(
1429                    sub_children,
1430                    *sub_ordered,
1431                    *sub_start,
1432                    &child_indent,
1433                ));
1434            } else {
1435                content.push_str(&sub.text_by_kind(BlockTextKind::SelectedSource));
1436            }
1437        }
1438        let content = content.trim_end_matches('\n');
1439
1440        if content.is_empty() && nested.is_empty() {
1441            item_ix += 1;
1442            continue;
1443        }
1444
1445        if content.is_empty() {
1446            // An item whose only selected content is a nested list.
1447            out.push_str(indent);
1448            out.push_str(&marker);
1449            out.push_str(checkbox.trim_end());
1450            out.push('\n');
1451        } else {
1452            // The first line carries the marker and checkbox; continuation
1453            // lines are indented to align under the item text.
1454            let mut lines = content.lines();
1455            if let Some(first) = lines.next() {
1456                out.push_str(indent);
1457                out.push_str(&marker);
1458                out.push_str(checkbox);
1459                out.push_str(first);
1460                out.push('\n');
1461            }
1462            for line in lines {
1463                out.push_str(&child_indent);
1464                out.push_str(line);
1465                out.push('\n');
1466            }
1467        }
1468        out.push_str(&nested);
1469        item_ix += 1;
1470    }
1471
1472    out
1473}
1474
1475impl InlineNode {
1476    /// A run of text with no marks.
1477    pub fn new(text: impl Into<SharedString>) -> Self {
1478        Self {
1479            text: text.into(),
1480            image: None,
1481            custom: None,
1482            custom_selection: Arc::default(),
1483            marks: vec![],
1484            source_segments: vec![],
1485            state: Arc::new(Mutex::new(InlineState::default())),
1486        }
1487    }
1488
1489    /// A custom inline node, an atomic object in the flow.
1490    pub fn custom(node: MarkdownNode) -> Self {
1491        let mut this = Self::new(node.as_text().to_string());
1492        this.custom = Some(node);
1493        this
1494    }
1495
1496    /// An inline image, an atomic object in the flow.
1497    pub fn image(image: ImageNode) -> Self {
1498        let mut this = Self::new("");
1499        this.image = Some(image);
1500        this
1501    }
1502
1503    /// Sets the marks, each a byte range of the text and its style.
1504    pub fn marks(mut self, marks: Vec<(Range<usize>, TextMark)>) -> Self {
1505        self.marks = marks;
1506        self
1507    }
1508
1509    /// The text of the run (the fallback text of a custom node, empty for an image).
1510    pub fn text(&self) -> &str {
1511        &self.text
1512    }
1513
1514    /// The image, when this run is an image.
1515    pub fn as_image(&self) -> Option<&ImageNode> {
1516        self.image.as_ref()
1517    }
1518
1519    /// The custom node, when this run is one.
1520    pub fn as_custom(&self) -> Option<&MarkdownNode> {
1521        self.custom.as_ref()
1522    }
1523
1524    /// The marks, each a byte range of the text and its style.
1525    pub fn mark_runs(&self) -> &[(Range<usize>, TextMark)] {
1526        &self.marks
1527    }
1528
1529    pub(crate) fn source_segments(mut self, source_segments: Vec<SourceSegment>) -> Self {
1530        self.source_segments = source_segments;
1531        self
1532    }
1533
1534    fn selected_source_range(&self, selection: Range<usize>) -> Option<Range<usize>> {
1535        source_range_for_segments(&self.source_segments, selection)
1536    }
1537}
1538
1539/// The paragraph element, contains multiple text nodes.
1540///
1541/// Unlike other Element, this is cloneable, because it is used in the Node AST.
1542/// We are keep the selection state inside this AST Nodes.
1543#[derive(Debug, Clone, Default)]
1544pub struct Paragraph {
1545    pub(super) span: Option<Span>,
1546    pub(super) children: Vec<InlineNode>,
1547    /// The link references in this paragraph, used for reference links.
1548    ///
1549    /// The key is the identifier, the value is the url.
1550    pub(super) link_refs: HashMap<SharedString, SharedString>,
1551
1552    pub(crate) state: Arc<Mutex<InlineState>>,
1553    /// What the plain (text-only) render path derives from `children`, kept
1554    /// between frames; see [`ParagraphRender`].
1555    pub(super) render_cache: ParagraphRenderCache,
1556}
1557
1558/// Derived state, invisible to `Debug` and equality.
1559///
1560/// A clone carries what was cached so far: the cached value is a pure
1561/// function of the children and the style it is keyed on, a clone has the
1562/// same children, and every mutation of the children replaces the cache
1563/// (`invalidate_render_cache`). Streaming reparses deep-clone the document on
1564/// every append, and an empty clone made every paragraph rebuild.
1565#[derive(Default)]
1566pub(super) struct ParagraphRenderCache(Mutex<Option<Arc<ParagraphRender>>>);
1567
1568impl Clone for ParagraphRenderCache {
1569    fn clone(&self) -> Self {
1570        let cached = self.0.lock().ok().and_then(|cache| cache.clone());
1571        Self(Mutex::new(cached))
1572    }
1573}
1574
1575impl std::fmt::Debug for ParagraphRenderCache {
1576    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1577        f.write_str("ParagraphRenderCache")
1578    }
1579}
1580
1581/// The text, highlights and links a text-only paragraph renders with.
1582///
1583/// They are a pure function of the paragraph's children and the style they
1584/// are rendered under, yet every frame rebuilt them: the paragraph's text was
1585/// re-concatenated and copied into a fresh `SharedString`, and its marks
1586/// merged into highlights through `combine_highlights` once per child. On a
1587/// scroll that was ~8% of the frame for nothing. Streamed fades change every
1588/// frame and are layered on afterwards; reference links are resolved
1589/// afterwards too, since a definition can arrive later in the document.
1590struct ParagraphRender {
1591    style: Arc<TextViewStyle>,
1592    mono_font: SharedString,
1593    text: SharedString,
1594    highlights: Vec<(Range<usize>, InlineHighlight)>,
1595    /// Links as written, before reference resolution.
1596    links: Vec<(Range<usize>, LinkMark)>,
1597}
1598
1599impl PartialEq for Paragraph {
1600    fn eq(&self, other: &Self) -> bool {
1601        self.span == other.span
1602            && self.children == other.children
1603            && self.link_refs == other.link_refs
1604    }
1605}
1606
1607impl Paragraph {
1608    pub(crate) fn new(text: String) -> Self {
1609        Self {
1610            span: None,
1611            children: vec![InlineNode::new(&text)],
1612            link_refs: HashMap::new(),
1613            state: Arc::new(Mutex::new(InlineState::default())),
1614            render_cache: ParagraphRenderCache::default(),
1615        }
1616    }
1617
1618    /// The text, highlights and (unresolved) links of a text-only paragraph,
1619    /// from the cache when the style has not changed since they were built.
1620    fn plain_render(
1621        &self,
1622        node_cx: &NodeContext,
1623        cx: &App,
1624    ) -> (
1625        SharedString,
1626        Vec<(Range<usize>, InlineHighlight)>,
1627        Vec<(Range<usize>, LinkMark)>,
1628    ) {
1629        let mono_font = cx.theme().tokens.typography.mono.clone();
1630        if let Ok(cache) = self.render_cache.0.lock()
1631            && let Some(cached) = cache.as_ref()
1632            && (Arc::ptr_eq(&cached.style, &node_cx.style) || *cached.style == *node_cx.style)
1633            && cached.mono_font == mono_font
1634        {
1635            return (
1636                cached.text.clone(),
1637                cached.highlights.clone(),
1638                cached.links.clone(),
1639            );
1640        }
1641
1642        let mut text = String::new();
1643        let mut highlights: Vec<(Range<usize>, InlineHighlight)> = vec![];
1644        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];
1645        let mut offset = 0;
1646        for inline_node in &self.children {
1647            let text_len = inline_node.text.len();
1648            text.push_str(&inline_node.text);
1649            let mut node_highlights = vec![];
1650            for (range, style) in &inline_node.marks {
1651                let inner_range = (offset + range.start)..(offset + range.end);
1652                let mut highlight = mark_highlight(style, node_cx, cx);
1653                if let Some(link_mark) = style.link.clone() {
1654                    highlight.style.color = Some(node_cx.style.link());
1655                    highlight.style.underline = Some(link_underline(&node_cx.style));
1656                    links.push((inner_range.clone(), link_mark));
1657                }
1658                node_highlights.push((inner_range, highlight));
1659            }
1660            highlights = combine_highlights(highlights, node_highlights);
1661            offset += text_len;
1662        }
1663        let text = SharedString::from(text);
1664        if let Ok(mut cache) = self.render_cache.0.lock() {
1665            *cache = Some(Arc::new(ParagraphRender {
1666                style: node_cx.style.clone(),
1667                mono_font,
1668                text: text.clone(),
1669                highlights: highlights.clone(),
1670                links: links.clone(),
1671            }));
1672        }
1673        (text, highlights, links)
1674    }
1675
1676    pub(super) fn selected_text(&self) -> String {
1677        let mut text = String::new();
1678
1679        for c in self.children.iter() {
1680            let Ok(state) = c.state.lock() else {
1681                continue;
1682            };
1683            if let Some(selection) = &state.selection {
1684                text.push_str(&state.text[selection.start..selection.end]);
1685            }
1686            if let Some(custom) = &c.custom
1687                && c.custom_selection.lock().is_ok_and(|selected| *selected)
1688            {
1689                text.push_str(custom.as_text());
1690            }
1691        }
1692
1693        if let Ok(state) = self.state.lock()
1694            && let Some(selection) = &state.selection
1695        {
1696            text.push_str(&state.text[selection.start..selection.end]);
1697        }
1698
1699        text
1700    }
1701
1702    /// Map the current rendered selection to its exact Markdown source range.
1703    pub(super) fn selected_source_range(&self) -> SourceRangeSelection {
1704        let mut selected = SourceRangeSelection::Unselected;
1705        let mut run: Vec<(usize, &InlineNode)> = Vec::new();
1706        let mut offset = 0;
1707        let mut pending_images: Vec<Option<Span>> = Vec::new();
1708        let mut enters_image = true;
1709
1710        let include_run = |state: &Arc<Mutex<InlineState>>,
1711                           run: &[(usize, &InlineNode)]|
1712         -> (SourceRangeSelection, RunSelection) {
1713            let Ok(state) = state.lock() else {
1714                return (SourceRangeSelection::Unmapped, RunSelection::default());
1715            };
1716            let Some(selection) = state.selection else {
1717                return (SourceRangeSelection::Unselected, RunSelection::default());
1718            };
1719            if selection.start >= selection.end {
1720                return (SourceRangeSelection::Unselected, RunSelection::default());
1721            }
1722
1723            let mut run_selection = RunSelection {
1724                at_start: selection.start == 0,
1725                at_end: selection.end >= state.text.len(),
1726                ..Default::default()
1727            };
1728            let mut mapped = SourceRangeSelection::Unselected;
1729            let mut rendered_end = selection.start;
1730            for (start, child) in run {
1731                let end = start + child.text.len();
1732                let lo = selection.start.max(*start);
1733                let hi = selection.end.min(end);
1734                if lo >= hi {
1735                    continue;
1736                }
1737                run_selection.emitted = true;
1738                if lo > rendered_end {
1739                    return (SourceRangeSelection::Unmapped, run_selection);
1740                }
1741                let Some(range) = child.selected_source_range((lo - start)..(hi - start)) else {
1742                    return (SourceRangeSelection::Unmapped, run_selection);
1743                };
1744                mapped.merge(SourceRangeSelection::Mapped(range));
1745                rendered_end = rendered_end.max(hi);
1746            }
1747            if rendered_end < selection.end {
1748                (SourceRangeSelection::Unmapped, run_selection)
1749            } else {
1750                (mapped, run_selection)
1751            }
1752        };
1753
1754        let merge_images = |selected: &mut SourceRangeSelection, images: &mut Vec<Option<Span>>| {
1755            for span in images.drain(..) {
1756                selected.merge(
1757                    span.map(|span| SourceRangeSelection::Mapped(span.start..span.end))
1758                        .unwrap_or(SourceRangeSelection::Unmapped),
1759                );
1760            }
1761        };
1762
1763        for child in &self.children {
1764            if child.custom.is_some() {
1765                let (run_range, run_selection) = include_run(&child.state, &run);
1766                if run_selection.emitted && run_selection.at_start {
1767                    merge_images(&mut selected, &mut pending_images);
1768                }
1769                selected.merge(run_range);
1770
1771                match child.custom_selection.lock() {
1772                    Ok(value) if *value => {
1773                        if run.is_empty() || (run_selection.emitted && run_selection.at_end) {
1774                            merge_images(&mut selected, &mut pending_images);
1775                        }
1776                        selected.merge(
1777                            child
1778                                .custom
1779                                .as_ref()
1780                                .and_then(MarkdownNode::source_range)
1781                                .map(SourceRangeSelection::Mapped)
1782                                .unwrap_or(SourceRangeSelection::Unmapped),
1783                        );
1784                        enters_image = true;
1785                    }
1786                    Ok(_) => enters_image = false,
1787                    Err(_) => {
1788                        selected.merge(SourceRangeSelection::Unmapped);
1789                        enters_image = false;
1790                    }
1791                }
1792                pending_images.clear();
1793                run.clear();
1794                offset = 0;
1795                continue;
1796            }
1797            if let Some(image) = &child.image {
1798                let run_before = !run.is_empty();
1799                let (run_range, run_selection) = include_run(&child.state, &run);
1800                if run_selection.emitted && run_selection.at_start {
1801                    merge_images(&mut selected, &mut pending_images);
1802                }
1803                selected.merge(run_range);
1804                if run_before {
1805                    enters_image = run_selection.emitted && run_selection.at_end;
1806                }
1807                if enters_image {
1808                    pending_images.push(image.span);
1809                } else {
1810                    pending_images.clear();
1811                }
1812                run.clear();
1813                offset = 0;
1814                continue;
1815            }
1816            run.push((offset, child));
1817            offset += child.text.len();
1818        }
1819
1820        let (trailing_range, trailing) = include_run(&self.state, &run);
1821        if trailing.emitted && trailing.at_start {
1822            merge_images(&mut selected, &mut pending_images);
1823        }
1824        selected.merge(trailing_range);
1825        if !trailing.emitted
1826            && enters_image
1827            && !matches!(selected, SourceRangeSelection::Unselected)
1828        {
1829            merge_images(&mut selected, &mut pending_images);
1830        }
1831        selected
1832    }
1833
1834    /// Reconstruct the Markdown source for the current selection.
1835    ///
1836    /// Mirrors [`selected_text`](Self::selected_text), but emits Markdown
1837    /// instead of the rendered text, using each inline node's `marks` (see
1838    /// [`reconstruct_markdown`]).
1839    ///
1840    /// Selection offsets index an `InlineState.text`, and one such state spans
1841    /// *several* children: [`Paragraph::render`] concatenates children until it
1842    /// hits an inline image, stores that run in the image child's state, then
1843    /// starts over; whatever follows the last image is stored in `self.state`.
1844    /// So walk the children in the same runs and map each selected byte back to
1845    /// the child it was rendered from — mapping against a single child's text
1846    /// would attribute the same offsets to children in other runs.
1847    ///
1848    /// An image has no selection of its own, so it is emitted when the
1849    /// selection runs into it: reaching the end of the run before it, and
1850    /// starting at the beginning of the run after it. A paragraph that begins
1851    /// or ends with an image has no run on that side, which counts as reaching
1852    /// it.
1853    pub(super) fn selected_source(&self) -> String {
1854        self.selected_markdown().finish()
1855    }
1856
1857    /// How many styles the selection within this paragraph takes in.
1858    pub(super) fn selection_styles(&self) -> SelectionStyles {
1859        self.selected_markdown().styles()
1860    }
1861
1862    /// The selection as Markdown pieces, before their marks are serialized;
1863    /// see [`selected_source`](Self::selected_source).
1864    fn selected_markdown(&self) -> MarkdownSource {
1865        let mut source = MarkdownSource::default();
1866        let mut pending_images: Vec<String> = Vec::new();
1867        let mut run: Vec<(usize, &InlineNode)> = Vec::new();
1868        let mut offset = 0;
1869        let mut enters_image = true;
1870
1871        for child in self.children.iter() {
1872            if child.custom.is_some() {
1873                let selected = emit_run(&child.state, &run, &mut pending_images, &mut source);
1874                let object_selected = child
1875                    .custom_selection
1876                    .lock()
1877                    .is_ok_and(|selected| *selected);
1878                if object_selected {
1879                    if run.is_empty() || (selected.emitted && selected.at_end) {
1880                        source.push_images(&pending_images);
1881                    }
1882                    source.push_object(child);
1883                }
1884                pending_images.clear();
1885                enters_image = object_selected;
1886                run.clear();
1887                offset = 0;
1888                continue;
1889            }
1890            let Some(image) = &child.image else {
1891                run.push((offset, child));
1892                offset += child.text.len();
1893                continue;
1894            };
1895
1896            // The run before an image is stored in that image's own state.
1897            let run_before = !run.is_empty();
1898            let selected = emit_run(&child.state, &run, &mut pending_images, &mut source);
1899            if run_before {
1900                enters_image = selected.emitted && selected.at_end;
1901            }
1902            if enters_image {
1903                pending_images.push(image_markdown(image));
1904            } else {
1905                pending_images.clear();
1906            }
1907
1908            run.clear();
1909            offset = 0;
1910        }
1911
1912        let trailing = emit_run(&self.state, &run, &mut pending_images, &mut source);
1913        // Trailing images have no run after them to flush them.
1914        if !trailing.emitted && enters_image && !source.is_empty() {
1915            source.push_images(&pending_images);
1916        }
1917
1918        source
1919    }
1920
1921    pub(super) fn text(&self) -> String {
1922        let mut text = String::new();
1923        for node in self.children.iter() {
1924            text.push_str(&node.text);
1925        }
1926        text
1927    }
1928
1929    /// Synchronously clear the selection stored in every inline state.
1930    ///
1931    /// Mirrors the [`selected_text`](Self::selected_text) traversal.
1932    pub(super) fn has_selection(&self) -> bool {
1933        self.children.iter().any(|c| {
1934            c.state.lock().is_ok_and(|state| state.selection.is_some())
1935                || c.custom_selection.lock().is_ok_and(|selected| *selected)
1936        }) || self
1937            .state
1938            .lock()
1939            .is_ok_and(|state| state.selection.is_some())
1940    }
1941
1942    pub(super) fn clear_selection(&self) {
1943        for c in self.children.iter() {
1944            if let Ok(mut selected) = c.custom_selection.lock() {
1945                *selected = false;
1946            }
1947            if let Ok(mut state) = c.state.lock() {
1948                state.selection = None;
1949            }
1950        }
1951
1952        if let Ok(mut state) = self.state.lock() {
1953            state.selection = None;
1954        }
1955    }
1956}
1957
1958/// A table: rows of cell paragraphs and per-column alignment.
1959#[derive(Debug, Clone, Default, PartialEq)]
1960pub struct Table {
1961    pub(crate) children: Vec<TableRow>,
1962    pub(crate) column_aligns: Vec<ColumnumnAlign>,
1963    pub(crate) span: Option<Span>,
1964    /// The [`TableData`] handed to the `table_actions` hook, kept between
1965    /// frames; see [`Table::cached_table_data`].
1966    pub(crate) table_data_cache: TableDataCache,
1967    pub(crate) column_widths_cache: TableColumnWidthsCache,
1968}
1969
1970/// Derived state, invisible to `Debug` and equality.
1971///
1972/// A clone carries what was cached so far: a parsed table does not change
1973/// after parsing (cell mutations only touch selection state, which
1974/// [`TableData`] does not read), so a clone has the same data. Streaming
1975/// reparses deep-clone the document on every append, and an empty clone made
1976/// every table rebuild.
1977#[derive(Default)]
1978pub(crate) struct TableDataCache(Mutex<Option<Arc<TableData>>>);
1979
1980impl Clone for TableDataCache {
1981    fn clone(&self) -> Self {
1982        let cached = self.0.lock().ok().and_then(|cache| cache.clone());
1983        Self(Mutex::new(cached))
1984    }
1985}
1986
1987impl PartialEq for TableDataCache {
1988    fn eq(&self, _: &Self) -> bool {
1989        true
1990    }
1991}
1992
1993impl std::fmt::Debug for TableDataCache {
1994    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1995        f.write_str("TableDataCache")
1996    }
1997}
1998
1999/// Like `TableDataCache`, derived widths survive clones of an unchanged
2000/// parsed table. A newly parsed table starts with an empty cache.
2001#[derive(Default)]
2002pub(crate) struct TableColumnWidthsCache(Mutex<Option<Arc<TableColumnWidths>>>);
2003
2004impl Clone for TableColumnWidthsCache {
2005    fn clone(&self) -> Self {
2006        let cached = self.0.lock().ok().and_then(|cache| cache.clone());
2007        Self(Mutex::new(cached))
2008    }
2009}
2010
2011impl PartialEq for TableColumnWidthsCache {
2012    fn eq(&self, _: &Self) -> bool {
2013        true
2014    }
2015}
2016
2017impl std::fmt::Debug for TableColumnWidthsCache {
2018    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2019        f.write_str("TableColumnWidthsCache")
2020    }
2021}
2022
2023struct TableColumnWidths {
2024    text_system: Weak<gpui::WindowTextSystem>,
2025    /// The styles the header row and the body rows render with.
2026    text_styles: TableTextStyles,
2027    rem_size: Pixels,
2028    mono_family: SharedString,
2029    inline_code: HighlightStyle,
2030    /// Maxima from ordinary text only. Custom cells may shrink after an update.
2031    widths: Vec<f32>,
2032    custom_cells: Vec<(usize, usize)>,
2033}
2034
2035/// The text styles table rows render with: the window's, refined by
2036/// `style.table` on the frame, `style.table_head` on the header row and
2037/// `style.table_cell` on every cell, in the order the elements apply them.
2038#[derive(Clone, PartialEq)]
2039struct TableTextStyles {
2040    head: gpui::TextStyle,
2041    body: gpui::TextStyle,
2042    head_refinement: TextStyleRefinement,
2043    body_refinement: TextStyleRefinement,
2044}
2045
2046impl TableTextStyles {
2047    fn new(style: &TextViewStyle, window: &Window) -> Self {
2048        let mut body_refinement = style.table().text.clone();
2049        let mut head_refinement = body_refinement.clone();
2050        head_refinement.refine(&style.table_head().text);
2051        head_refinement.refine(&style.table_cell().text);
2052        body_refinement.refine(&style.table_cell().text);
2053        let mut head = window.text_style();
2054        let mut body = head.clone();
2055        head.refine(&head_refinement);
2056        body.refine(&body_refinement);
2057        Self {
2058            head,
2059            body,
2060            head_refinement,
2061            body_refinement,
2062        }
2063    }
2064
2065    fn row(&self, row_ix: usize) -> &gpui::TextStyle {
2066        if row_ix == 0 { &self.head } else { &self.body }
2067    }
2068
2069    fn row_refinement(&self, row_ix: usize) -> &TextStyleRefinement {
2070        if row_ix == 0 {
2071            &self.head_refinement
2072        } else {
2073            &self.body_refinement
2074        }
2075    }
2076}
2077
2078/// Plain snapshot of a rendered Markdown table, passed to the
2079/// [`crate::text::TextView::table_actions`] hook.
2080#[derive(Debug, Clone, Default, PartialEq)]
2081pub struct TableData {
2082    /// First table row (header cells) as plain text.
2083    pub headers: Vec<String>,
2084    /// Rows after the header as plain text cells. May be ragged while
2085    /// a table is still streaming in.
2086    pub rows: Vec<Vec<String>>,
2087    /// The table serialized back to GFM pipe-table Markdown, alignments kept.
2088    pub markdown: String,
2089    /// Byte range of the table in the Markdown source, for callers that need
2090    /// to map the table back to the document.
2091    ///
2092    /// Not needed to keep element ids apart: the actions row is wrapped in its
2093    /// own identified element, so plain ids like `"copy"` are already scoped
2094    /// per table.
2095    pub span: Option<Range<usize>>,
2096}
2097
2098impl Table {
2099    /// A table of `rows` (one paragraph per cell) with the column alignments.
2100    pub fn new(rows: Vec<Vec<Paragraph>>, column_aligns: Vec<ColumnumnAlign>) -> Self {
2101        Self {
2102            children: rows
2103                .into_iter()
2104                .map(|row| TableRow {
2105                    children: row
2106                        .into_iter()
2107                        .map(|children| TableCell {
2108                            children,
2109                            width: None,
2110                        })
2111                        .collect(),
2112                })
2113                .collect(),
2114            column_aligns,
2115            ..Default::default()
2116        }
2117    }
2118
2119    /// The rows, each an iterator over its cells' paragraphs.
2120    pub fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = &Paragraph>> {
2121        self.children
2122            .iter()
2123            .map(|row| row.children.iter().map(|cell| &cell.children))
2124    }
2125
2126    /// The alignment of each column.
2127    pub fn column_aligns(&self) -> &[ColumnumnAlign] {
2128        &self.column_aligns
2129    }
2130
2131    pub(crate) fn column_align(&self, index: usize) -> ColumnumnAlign {
2132        self.column_aligns.get(index).copied().unwrap_or_default()
2133    }
2134
2135    /// Serialize the table back to GFM pipe-table Markdown (`| a | b |`),
2136    /// preserving column alignments. Cell newlines collapse to spaces and
2137    /// `|` is escaped so rows stay intact.
2138    ///
2139    /// Mirrors [`table_selected_source`], which does the same for the selected
2140    /// cells only.
2141    pub(crate) fn to_markdown(&self) -> String {
2142        let mut lines: Vec<String> = Vec::with_capacity(self.children.len() + 1);
2143
2144        for (row_ix, row) in self.children.iter().enumerate() {
2145            let cells: Vec<String> = row
2146                .children
2147                .iter()
2148                .map(|cell| {
2149                    cell.children
2150                        .to_markdown()
2151                        .trim()
2152                        .replace('\n', " ")
2153                        .replace('|', "\\|")
2154                })
2155                .collect();
2156            lines.push(format!("| {} |", cells.join(" | ")));
2157
2158            // The Markdown delimiter row carries the column alignments and must
2159            // follow the header row.
2160            if row_ix == 0 {
2161                let aligns: Vec<String> = (0..row.children.len())
2162                    .map(|ix| {
2163                        match self.column_align(ix) {
2164                            ColumnumnAlign::None | ColumnumnAlign::Left => ":--",
2165                            ColumnumnAlign::Center => ":-:",
2166                            ColumnumnAlign::Right => "--:",
2167                        }
2168                        .to_string()
2169                    })
2170                    .collect();
2171                lines.push(format!("| {} |", aligns.join(" | ")));
2172            }
2173        }
2174
2175        lines.join("\n")
2176    }
2177
2178    /// Snapshot of this table for the [`crate::text::TextView::table_actions`]
2179    /// hook.
2180    pub(crate) fn table_data(&self) -> TableData {
2181        let row_text = |row: &TableRow| {
2182            row.children
2183                .iter()
2184                .map(|cell| cell.children.text().trim().to_string())
2185                .collect::<Vec<_>>()
2186        };
2187
2188        TableData {
2189            headers: self.children.first().map(row_text).unwrap_or_default(),
2190            rows: self.children.iter().skip(1).map(row_text).collect(),
2191            markdown: self.to_markdown(),
2192            span: self.span.map(|span| span.start..span.end),
2193        }
2194    }
2195
2196    /// [`Self::table_data`], built on the first render and reused by later
2197    /// ones: serializing every cell and the whole table back to Markdown on
2198    /// every scroll or stream frame is wasted work, and a parsed table does
2199    /// not change after parsing (a reparse builds a new one).
2200    fn cached_table_data(&self) -> Arc<TableData> {
2201        if let Ok(cache) = self.table_data_cache.0.lock()
2202            && let Some(cached) = cache.as_ref()
2203        {
2204            return cached.clone();
2205        }
2206
2207        let data = Arc::new(self.table_data());
2208        if let Ok(mut cache) = self.table_data_cache.0.lock() {
2209            *cache = Some(data.clone());
2210        }
2211        data
2212    }
2213}
2214
2215/// The alignment of a table column.
2216#[derive(Debug, Default, Copy, Clone, PartialEq)]
2217pub enum ColumnumnAlign {
2218    /// No alignment in the delimiter row (`---`); renders like `Left`.
2219    #[default]
2220    None,
2221    Left,
2222    Center,
2223    Right,
2224}
2225
2226impl From<mdast::AlignKind> for ColumnumnAlign {
2227    fn from(value: mdast::AlignKind) -> Self {
2228        match value {
2229            mdast::AlignKind::None => ColumnumnAlign::None,
2230            mdast::AlignKind::Left => ColumnumnAlign::Left,
2231            mdast::AlignKind::Center => ColumnumnAlign::Center,
2232            mdast::AlignKind::Right => ColumnumnAlign::Right,
2233        }
2234    }
2235}
2236
2237#[derive(Debug, Clone, Default, PartialEq)]
2238pub(crate) struct TableRow {
2239    pub children: Vec<TableCell>,
2240}
2241
2242#[derive(Debug, Clone, Default, PartialEq)]
2243pub(crate) struct TableCell {
2244    pub children: Paragraph,
2245    pub width: Option<DefiniteLength>,
2246}
2247
2248impl Paragraph {
2249    pub(crate) fn take(&mut self) -> Paragraph {
2250        std::mem::replace(
2251            self,
2252            Paragraph {
2253                span: None,
2254                children: vec![],
2255                link_refs: Default::default(),
2256                state: Arc::new(Mutex::new(InlineState::default())),
2257                render_cache: ParagraphRenderCache::default(),
2258            },
2259        )
2260    }
2261
2262    pub(crate) fn is_image(&self) -> bool {
2263        false
2264    }
2265
2266    pub(crate) fn set_span(&mut self, span: Span) {
2267        self.span = Some(span);
2268    }
2269
2270    pub(crate) fn push_str(&mut self, text: &str) {
2271        self.children.push(
2272            InlineNode::new(text.to_string()).marks(vec![(0..text.len(), TextMark::default())]),
2273        );
2274        self.invalidate_render_cache();
2275    }
2276
2277    /// Appends an inline run.
2278    pub fn push(&mut self, text: InlineNode) {
2279        self.children.push(text);
2280        self.invalidate_render_cache();
2281    }
2282
2283    /// The inline runs.
2284    pub fn children(&self) -> &[InlineNode] {
2285        &self.children
2286    }
2287
2288    /// Marks this paragraph as a leaf of an external editor: while a
2289    /// [`collect_leaf_geometry`](super::editor_host::collect_leaf_geometry)
2290    /// scope is active, it reports its geometry under `key` and paints the
2291    /// decorations given for `key`. Rekeying detaches runtime state from clones.
2292    pub fn with_leaf_key(mut self, key: u64) -> Self {
2293        set_leaf_key(&mut self.state, key);
2294        self
2295    }
2296
2297    pub(crate) fn push_image(&mut self, image: ImageNode) {
2298        self.children.push(InlineNode::image(image));
2299        self.invalidate_render_cache();
2300    }
2301
2302    /// The children changed, so what was derived from them is stale.
2303    fn invalidate_render_cache(&mut self) {
2304        self.render_cache = ParagraphRenderCache::default();
2305    }
2306
2307    pub(crate) fn is_empty(&self) -> bool {
2308        self.children.is_empty()
2309            || self
2310                .children
2311                .iter()
2312                .all(|node| node.text.is_empty() && node.image.is_none())
2313    }
2314
2315    /// Return length of children text.
2316    pub(crate) fn text_len(&self) -> usize {
2317        self.children
2318            .iter()
2319            .map(|node| node.text.len())
2320            .sum::<usize>()
2321    }
2322
2323    pub(crate) fn merge(&mut self, other: Self) {
2324        self.children.extend(other.children);
2325        self.invalidate_render_cache();
2326    }
2327}
2328
2329#[derive(Debug, Clone)]
2330pub struct CodeBlock {
2331    lang: Option<SharedString>,
2332    meta: Option<SharedString>,
2333    state: Arc<Mutex<InlineState>>,
2334    highlight_cache: Arc<Mutex<Option<CachedCodeBlockHighlights>>>,
2335    /// Rendered UTF-8 byte spans of the code paired with their exact Markdown
2336    /// source spans.
2337    pub(crate) source_segments: Vec<SourceSegment>,
2338    pub span: Option<Span>,
2339}
2340
2341struct CachedCodeBlockHighlights {
2342    highlighter: Arc<CodeBlockHighlighterFn>,
2343    styles: Vec<(Range<usize>, HighlightStyle)>,
2344}
2345
2346impl std::fmt::Debug for CachedCodeBlockHighlights {
2347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2348        f.debug_struct("CachedCodeBlockHighlights")
2349            .field("styles", &self.styles)
2350            .finish_non_exhaustive()
2351    }
2352}
2353
2354impl PartialEq for CodeBlock {
2355    fn eq(&self, other: &Self) -> bool {
2356        self.lang == other.lang
2357            && self.meta == other.meta
2358            && self.code() == other.code()
2359            && self.span == other.span
2360    }
2361}
2362
2363impl CodeBlock {
2364    /// Get the language of the code block.
2365    pub fn lang(&self) -> Option<SharedString> {
2366        self.lang.clone()
2367    }
2368
2369    /// The rest of the fence's info string after the language, when any.
2370    pub fn meta(&self) -> Option<SharedString> {
2371        self.meta.clone()
2372    }
2373
2374    /// Sets the fence's info string after the language. It is kept for
2375    /// round trips and is not rendered.
2376    pub fn with_meta(mut self, meta: Option<SharedString>) -> Self {
2377        self.meta = meta;
2378        self
2379    }
2380
2381    /// Marks this code block as a leaf of an external editor, like
2382    /// [`Paragraph::with_leaf_key`]. Rekeying detaches runtime state from clones.
2383    pub fn with_leaf_key(mut self, key: u64) -> Self {
2384        set_leaf_key(&mut self.state, key);
2385        self
2386    }
2387
2388    /// Get the code content of the code block.
2389    pub fn code(&self) -> SharedString {
2390        self.state
2391            .lock()
2392            .map(|state| state.text.clone())
2393            .unwrap_or_default()
2394    }
2395
2396    /// Builds a code block that is not tied to a parsed document.
2397    ///
2398    /// [`crate::TextView::code_block_highlighter`] hands a `&CodeBlock` to the
2399    /// highlighter it is given, so anyone writing one needs a way to build a
2400    /// block to exercise it against.
2401    pub fn from_code(code: impl Into<SharedString>, lang: Option<impl Into<SharedString>>) -> Self {
2402        Self::new(code.into(), lang.map(Into::into), None::<Span>)
2403    }
2404
2405    /// A code block of `code` in `lang`.
2406    pub fn new(
2407        code: SharedString,
2408        lang: Option<SharedString>,
2409        span: Option<impl Into<Span>>,
2410    ) -> Self {
2411        let state = Arc::new(Mutex::new(InlineState::default()));
2412        if let Ok(mut state) = state.lock() {
2413            state.set_text(code);
2414        }
2415
2416        Self {
2417            lang,
2418            meta: None,
2419            state,
2420            highlight_cache: Arc::new(Mutex::new(None)),
2421            source_segments: vec![],
2422            span: span.map(|s| s.into()),
2423        }
2424    }
2425
2426    pub(crate) fn source_segments(mut self, source_segments: Vec<SourceSegment>) -> Self {
2427        self.source_segments = source_segments;
2428        self
2429    }
2430
2431    #[cfg(test)]
2432    pub(crate) fn set_selection(&self, selection: Range<usize>) {
2433        if let Ok(mut state) = self.state.lock() {
2434            state.selection = Some(selection.into());
2435        }
2436    }
2437
2438    pub(super) fn selected_source_range(&self) -> SourceRangeSelection {
2439        let Ok(state) = self.state.lock() else {
2440            return SourceRangeSelection::Unmapped;
2441        };
2442        let Some(selection) = state.selection else {
2443            return SourceRangeSelection::Unselected;
2444        };
2445        if selection.start >= selection.end {
2446            return SourceRangeSelection::Unselected;
2447        }
2448        source_range_for_segments(&self.source_segments, selection.start..selection.end)
2449            .map(SourceRangeSelection::Mapped)
2450            .unwrap_or(SourceRangeSelection::Unmapped)
2451    }
2452
2453    fn highlighted_styles(
2454        &self,
2455        highlighter: &Arc<CodeBlockHighlighterFn>,
2456    ) -> Vec<(Range<usize>, HighlightStyle)> {
2457        if let Ok(cache) = self.highlight_cache.lock()
2458            && let Some(cache) = cache.as_ref()
2459            && Arc::ptr_eq(&cache.highlighter, highlighter)
2460        {
2461            return cache.styles.clone();
2462        }
2463
2464        let code_len = self.code().len();
2465        let styles = highlighter(self)
2466            .into_iter()
2467            .filter(|(range, _)| range.start <= range.end && range.end <= code_len)
2468            .collect::<Vec<_>>();
2469        if let Ok(mut cache) = self.highlight_cache.lock() {
2470            *cache = Some(CachedCodeBlockHighlights {
2471                highlighter: highlighter.clone(),
2472                styles: styles.clone(),
2473            });
2474        }
2475        styles
2476    }
2477
2478    pub(super) fn selected_text(&self) -> String {
2479        let mut text = String::new();
2480        if let Ok(state) = self.state.lock()
2481            && let Some(selection) = &state.selection
2482        {
2483            text.push_str(&state.text[selection.start..selection.end]);
2484        }
2485        text
2486    }
2487
2488    /// Markdown source for the current selection.
2489    ///
2490    /// The selected code is wrapped in a fenced code block carrying the block's
2491    /// language, so a selected code block round-trips as Markdown (e.g.
2492    /// ```` ```rust\n…\n``` ````) instead of pasting as bare, unfenced text.
2493    /// A partial selection is still emitted as a valid fenced block.
2494    pub(super) fn selected_source(&self) -> String {
2495        let code = self.selected_text();
2496        if code.is_empty() {
2497            return String::new();
2498        }
2499        let lang = self.lang.clone().unwrap_or_default();
2500        // Trim trailing newlines so the closing fence sits on its own line
2501        // directly after the last code line (no blank line before it).
2502        let code = code.trim_end_matches('\n');
2503        format!("```{}\n{}\n```", lang, code)
2504    }
2505
2506    pub(super) fn text(&self) -> String {
2507        self.state
2508            .lock()
2509            .map(|state| state.text.to_string())
2510            .unwrap_or_default()
2511    }
2512
2513    /// Synchronously clear the selection stored in the inline state.
2514    ///
2515    /// Mirrors the [`selected_text`](Self::selected_text) traversal.
2516    pub(super) fn has_selection(&self) -> bool {
2517        self.state
2518            .lock()
2519            .is_ok_and(|state| state.selection.is_some())
2520    }
2521
2522    pub(super) fn clear_selection(&self) {
2523        if let Ok(mut state) = self.state.lock() {
2524            state.selection = None;
2525        }
2526    }
2527
2528    fn render(
2529        &self,
2530        options: &NodeRenderOptions,
2531        node_cx: &NodeContext,
2532        window: &mut Window,
2533        cx: &mut App,
2534    ) -> AnyElement {
2535        let style = &node_cx.style;
2536        let leaf_key = self.span.map(|span| TextLeafKey::block(span.start));
2537        // The language sits quietly in the top-right corner, where custom
2538        // actions go instead when the caller provides them; the top padding
2539        // keeps the first line clear of it. Like the actions, it stays out of
2540        // the scrolled content so it holds its corner.
2541        let lang_label = self
2542            .lang()
2543            .filter(|lang| !lang.is_empty() && node_cx.code_block_actions.is_none())
2544            .map(|lang| {
2545                div()
2546                    .absolute()
2547                    .top(px(7.))
2548                    .right_3()
2549                    .font_family(cx.theme().tokens.typography.sans.clone())
2550                    .text_size(cx.theme().tokens.typography.xs.size)
2551                    .line_height(px(16.))
2552                    .text_color(style.muted_foreground())
2553                    .child(lang)
2554            });
2555
2556        let block = div()
2557            .w_full()
2558            .min_w_0()
2559            .px_4()
2560            .pt(if lang_label.is_some() {
2561                px(28.)
2562            } else {
2563                px(14.)
2564            })
2565            .pb(px(14.))
2566            .bg(style.code_background())
2567            .border_1()
2568            .border_color(style.border())
2569            .font_family(cx.theme().tokens.typography.mono.clone())
2570            .text_size(cx.theme().tokens.typography.mono_md.size)
2571            .line_height(relative(1.6))
2572            .relative()
2573            .refine_style(&style.code_block())
2574            .child(
2575                Inline::new(
2576                    self.state.clone(),
2577                    vec![],
2578                    fade_highlights(
2579                        node_cx
2580                            .code_block_highlighter
2581                            .as_ref()
2582                            .map(|highlighter| self.highlighted_styles(highlighter))
2583                            .unwrap_or_default()
2584                            .into_iter()
2585                            .map(|(range, style)| (range, InlineHighlight::from(style)))
2586                            .collect(),
2587                        node_cx.stream_fades(leaf_key),
2588                    ),
2589                    node_cx.link_click_handler.clone(),
2590                )
2591                .range_backgrounds(node_cx.range_backgrounds(leaf_key).to_vec())
2592                .reveal(node_cx.reveal_at(leaf_key, 0, self.code().len())),
2593            );
2594        let actions = node_cx.code_block_actions.clone().map(|actions| {
2595            div()
2596                .id("actions")
2597                .absolute()
2598                .top_2()
2599                .right_2()
2600                .bg(style.code_background())
2601                .rounded(cx.theme().tokens.radius.md)
2602                .child(actions(&self, window, cx))
2603        });
2604        // The id scopes the caller's action ids per code block, so plain ids
2605        // like `"copy"` don't collide across blocks.
2606        let id = block_element_id("codeblock", self.span, options.ix);
2607        let block = if matches!(style.code_block().overflow.y, Some(Overflow::Scroll)) {
2608            let scroll_handle = window
2609                .use_keyed_state(
2610                    block_element_id("codeblock-scroll", self.span, options.ix),
2611                    cx,
2612                    |_, _| ScrollHandle::default(),
2613                )
2614                .read(cx)
2615                .clone();
2616            // Scroll mode is opted in by setting `style.code_block`'s
2617            // `overflow.y` to `Overflow::Scroll`.
2618            // The mask consumes the wheel in the capture phase, so an ancestor
2619            // `gpui::list` doesn't scroll on the same event. Mask, scrollbar
2620            // and actions are siblings of the scrolled block, so they stay
2621            // pinned to the viewport instead of moving with the code.
2622            div()
2623                .id(id.clone())
2624                .w_full()
2625                .min_w_0()
2626                .relative()
2627                .child(block.id("scroll").track_scroll(&scroll_handle))
2628                .child(ScrollableMask::new(Axis::Vertical, &scroll_handle).id(id))
2629                .child(
2630                    div()
2631                        .absolute()
2632                        .inset_0()
2633                        .child(Scrollbar::vertical(&scroll_handle)),
2634                )
2635                .children(actions)
2636                .children(lang_label)
2637                .into_any_element()
2638        } else {
2639            // Without actions nothing under the block needs element state.
2640            match actions {
2641                Some(actions) => block.id(id).child(actions).into_any_element(),
2642                None => block.children(lang_label).into_any_element(),
2643            }
2644        };
2645
2646        gapped(
2647            block,
2648            if options.is_last {
2649                rems(0.)
2650            } else {
2651                style.paragraph_gap()
2652            },
2653        )
2654    }
2655}
2656
2657/// A context for rendering nodes, contains link references.
2658#[derive(Default, Clone)]
2659pub(crate) struct NodeContext {
2660    /// The byte offset of the node in the original markdown text.
2661    /// Used for incremental updates.
2662    pub(crate) offset: usize,
2663    pub(crate) link_refs: HashMap<SharedString, LinkMark>,
2664    pub(crate) style: Arc<TextViewStyle>,
2665    pub(crate) code_block_actions: Option<Arc<CodeBlockActionsFn>>,
2666    pub(crate) code_block_highlighter: Option<Arc<CodeBlockHighlighterFn>>,
2667    pub(crate) table_actions: Option<Arc<TableActionsFn>>,
2668    pub(crate) image_source: Option<Arc<super::text_view::ImageSourceFn>>,
2669    pub(crate) link_click_handler: Option<Arc<LinkClickHandlerFn>>,
2670    pub(crate) markdown_extensions: Arc<MarkdownExtensions>,
2671    /// This frame's streamed fade-in, when any text is still fading.
2672    pub(crate) stream_fade: Option<Arc<StreamFadeFrame>>,
2673    /// The application's range highlights, when there are any.
2674    pub(crate) range_highlights: Option<Arc<RangeHighlightFrame>>,
2675    /// The line being scrolled into view, when there is one.
2676    pub(crate) reveal: Option<RevealRequest>,
2677    /// The font size the text view inherits this frame, or zero when not
2678    /// resolved; read it through `body_font_size`.
2679    pub(crate) body_font_size: Pixels,
2680}
2681
2682impl NodeContext {
2683    fn image_source(&self, image: &ImageNode) -> ImageSource {
2684        match &self.image_source {
2685            Some(resolve) => resolve(&image.url),
2686            None => image.source(),
2687        }
2688    }
2689
2690    pub(super) fn add_ref(&mut self, identifier: SharedString, link: LinkMark) {
2691        self.link_refs.insert(identifier, link);
2692    }
2693
2694    /// The fade ranges of the text leaf `key`, in its rendered byte space.
2695    fn stream_fades(&self, key: Option<TextLeafKey>) -> &[(Range<usize>, f32)] {
2696        match (&self.stream_fade, key) {
2697            (Some(frame), Some(key)) => frame.fades(key).unwrap_or_default(),
2698            _ => &[],
2699        }
2700    }
2701
2702    /// The range highlight backgrounds of the text leaf `key`, in its
2703    /// rendered byte space.
2704    fn range_backgrounds(&self, key: Option<TextLeafKey>) -> &[(Range<usize>, Hsla)] {
2705        match (&self.range_highlights, key) {
2706            (Some(frame), Some(key)) => frame.backgrounds(key),
2707            _ => &[],
2708        }
2709    }
2710
2711    /// The pending reveal, when it starts in the text leaf `key` between
2712    /// `start` and `end`, rebased to `start`.
2713    fn reveal_at(&self, key: Option<TextLeafKey>, start: usize, end: usize) -> Option<RevealAt> {
2714        self.reveal.as_ref()?.at(key, start, end)
2715    }
2716}
2717
2718impl PartialEq for NodeContext {
2719    fn eq(&self, other: &Self) -> bool {
2720        self.link_refs == other.link_refs && self.style == other.style
2721        // Note: code_block_actions, table_actions and markdown_extensions are
2722        // intentionally not compared (closures can't be compared)
2723    }
2724}
2725
2726/// The highlight a text mark renders with. The link decoration is applied by
2727/// the caller, which also has to record the link range.
2728fn mark_highlight(mark: &TextMark, node_cx: &NodeContext, cx: &App) -> InlineHighlight {
2729    let mut highlight = HighlightStyle::default();
2730    if mark.bold {
2731        highlight.font_weight = Some(FontWeight::BOLD);
2732    }
2733    if mark.italic {
2734        highlight.font_style = Some(FontStyle::Italic);
2735    }
2736    if mark.strikethrough {
2737        highlight.strikethrough = Some(gpui::StrikethroughStyle {
2738            thickness: gpui::px(1.),
2739            ..Default::default()
2740        });
2741    }
2742    if mark.underline {
2743        highlight.underline = Some(gpui::UnderlineStyle {
2744            thickness: gpui::px(1.),
2745            ..Default::default()
2746        });
2747    }
2748    let mut font_family = None;
2749    if mark.code {
2750        highlight = highlight.highlight(node_cx.style.inline_code_highlight());
2751        let style = node_cx.style.inline_code_style();
2752        highlight = highlight.highlight(HighlightStyle {
2753            color: style.text.color,
2754            font_weight: style.text.font_weight,
2755            font_style: style.text.font_style,
2756            underline: style.text.underline,
2757            strikethrough: style.text.strikethrough,
2758            ..Default::default()
2759        });
2760        font_family = Some(
2761            style
2762                .text
2763                .font_family
2764                .clone()
2765                .unwrap_or_else(|| cx.theme().tokens.typography.mono.clone()),
2766        );
2767    }
2768    if let Some(color) = mark.highlight {
2769        highlight.background_color = Some(color);
2770    }
2771    InlineHighlight {
2772        style: highlight,
2773        font_family,
2774        font_size_scale: mark.code.then_some(0.875),
2775        code_style: mark.code.then(|| node_cx.style.inline_code_style().clone()),
2776    }
2777}
2778
2779impl Paragraph {
2780    /// The highlights over [`Self::text`], for measuring the paragraph with
2781    /// the runs it renders with. Link colors are left out: they do not move
2782    /// glyphs.
2783    fn inline_highlights(
2784        &self,
2785        node_cx: &NodeContext,
2786        cx: &App,
2787    ) -> Vec<(Range<usize>, InlineHighlight)> {
2788        let mut highlights = vec![];
2789        let mut offset = 0;
2790        for inline_node in &self.children {
2791            let node_highlights = inline_node
2792                .marks
2793                .iter()
2794                .map(|(range, mark)| {
2795                    (
2796                        (offset + range.start)..(offset + range.end),
2797                        mark_highlight(mark, node_cx, cx),
2798                    )
2799                })
2800                .collect::<Vec<_>>();
2801            highlights = combine_highlights(highlights, node_highlights);
2802            offset += inline_node.text.len();
2803        }
2804        highlights
2805    }
2806
2807    /// `fade_key` names this paragraph's text for the streamed fade-in and
2808    /// for range highlights; the owning block supplies it because a heading
2809    /// or table cell paragraph carries no span of its own.
2810    fn render(
2811        &self,
2812        fade_key: Option<TextLeafKey>,
2813        node_cx: &NodeContext,
2814        _window: &mut Window,
2815        cx: &mut App,
2816    ) -> AnyElement {
2817        let children = &self.children;
2818        let fades = node_cx.stream_fades(fade_key);
2819        let backgrounds = node_cx.range_backgrounds(fade_key);
2820
2821        if self.should_render_inline_flow() {
2822            return InlineFlow::new(
2823                leaf_element_id(fade_key),
2824                self.inline_flow_items(fade_key, fades, backgrounds, node_cx, cx),
2825                node_cx.link_click_handler.clone(),
2826            )
2827            .editor_key(self.leaf_key())
2828            .into_any_element();
2829        }
2830
2831        let has_image = children.iter().any(|child| child.image.is_some());
2832        // Text alone is one `Inline`, which needs no box of its own, and its
2833        // text, highlights and links are cached across frames.
2834        if !has_image {
2835            let (text, highlights, mut links) = self.plain_render(node_cx, cx);
2836            // An editor leaf is laid out even when empty, so the editor gets
2837            // a line box for the caret.
2838            if text.is_empty() && self.leaf_key().is_none() {
2839                return div().into_any_element();
2840            }
2841            for (_, link_mark) in &mut links {
2842                if let Some(identifier) = link_mark.identifier.as_ref()
2843                    && let Some(mark) = node_cx.link_refs.get(identifier)
2844                {
2845                    *link_mark = mark.clone();
2846                }
2847            }
2848            let highlights = fade_highlights(highlights, &slice_fades(fades, 0, text.len()));
2849            let backgrounds = slice_backgrounds(backgrounds, 0, text.len());
2850            let reveal = node_cx.reveal_at(fade_key, 0, text.len());
2851            if let Ok(mut state) = self.state.lock() {
2852                state.set_text(text);
2853            }
2854            return Inline::new(
2855                self.state.clone(),
2856                links,
2857                highlights,
2858                node_cx.link_click_handler.clone(),
2859            )
2860            .range_backgrounds(backgrounds)
2861            .reveal(reveal)
2862            .into_any_element();
2863        }
2864
2865        let mut child_nodes: Vec<AnyElement> = vec![];
2866
2867        let mut text = String::new();
2868        let mut highlights: Vec<(Range<usize>, InlineHighlight)> = vec![];
2869        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];
2870        let mut offset = 0;
2871        // Where `text` starts in the paragraph's whole rendered text, which
2872        // is the byte space the fade ranges use.
2873        let mut consumed = 0;
2874
2875        for (ix, inline_node) in children.iter().enumerate() {
2876            let text_len = inline_node.text.len();
2877            text.push_str(&inline_node.text);
2878
2879            if let Some(image) = &inline_node.image {
2880                if text.len() > 0 {
2881                    if let Ok(mut state) = inline_node.state.lock() {
2882                        state.set_text(text.clone().into());
2883                    }
2884                    child_nodes.push(
2885                        Inline::new(
2886                            inline_node.state.clone(),
2887                            links.clone(),
2888                            fade_highlights(
2889                                highlights.clone(),
2890                                &slice_fades(fades, consumed, consumed + text.len()),
2891                            ),
2892                            node_cx.link_click_handler.clone(),
2893                        )
2894                        .range_backgrounds(slice_backgrounds(
2895                            backgrounds,
2896                            consumed,
2897                            consumed + text.len(),
2898                        ))
2899                        .reveal(node_cx.reveal_at(fade_key, consumed, consumed + text.len()))
2900                        .into_any_element(),
2901                    );
2902                }
2903                let link_click_handler = node_cx.link_click_handler.clone();
2904                child_nodes.push(
2905                    img(node_cx.image_source(image))
2906                        .id(ix)
2907                        .object_fit(ObjectFit::Contain)
2908                        .max_w(relative(1.))
2909                        .when_some(image.width, |this, width| this.w(width))
2910                        .when_some(image.link.clone(), |this, link| {
2911                            let link_click_handler = link_click_handler.clone();
2912                            let aux_link = link.clone();
2913                            let aux_link_click_handler = link_click_handler.clone();
2914                            this.cursor_pointer()
2915                                .on_click(move |event, window, cx| {
2916                                    crate::TextSelection::end(window, cx);
2917                                    cx.stop_propagation();
2918                                    handle_link_click(
2919                                        &link_click_handler,
2920                                        link.url.clone(),
2921                                        event.clone(),
2922                                        window,
2923                                        cx,
2924                                    );
2925                                })
2926                                .on_aux_click(move |event, window, cx| {
2927                                    crate::TextSelection::end(window, cx);
2928                                    cx.stop_propagation();
2929                                    handle_link_click(
2930                                        &aux_link_click_handler,
2931                                        aux_link.url.clone(),
2932                                        event.clone(),
2933                                        window,
2934                                        cx,
2935                                    );
2936                                })
2937                        })
2938                        .into_any_element(),
2939                );
2940
2941                consumed += text.len();
2942                text.clear();
2943                links.clear();
2944                highlights.clear();
2945                offset = 0;
2946            } else {
2947                let mut node_highlights = vec![];
2948                for (range, style) in &inline_node.marks {
2949                    let inner_range = (offset + range.start)..(offset + range.end);
2950                    let mut highlight = mark_highlight(style, node_cx, cx);
2951
2952                    if let Some(mut link_mark) = style.link.clone() {
2953                        highlight.style.color = Some(node_cx.style.link());
2954                        highlight.style.underline = Some(link_underline(&node_cx.style));
2955
2956                        // convert link references, replace link
2957                        if let Some(identifier) = link_mark.identifier.as_ref() {
2958                            if let Some(mark) = node_cx.link_refs.get(identifier) {
2959                                link_mark = mark.clone();
2960                            }
2961                        }
2962
2963                        links.push((inner_range.clone(), link_mark));
2964                    }
2965
2966                    node_highlights.push((inner_range, highlight));
2967                }
2968
2969                highlights = combine_highlights(highlights, node_highlights);
2970                offset += text_len;
2971            }
2972        }
2973
2974        // Add the last text node
2975        if text.len() > 0 {
2976            let text_end = consumed + text.len();
2977            let highlights = fade_highlights(highlights, &slice_fades(fades, consumed, text_end));
2978            if let Ok(mut state) = self.state.lock() {
2979                state.set_text(text.into());
2980            }
2981            child_nodes.push(
2982                Inline::new(
2983                    self.state.clone(),
2984                    links,
2985                    highlights,
2986                    node_cx.link_click_handler.clone(),
2987                )
2988                .range_backgrounds(slice_backgrounds(backgrounds, consumed, text_end))
2989                .reveal(node_cx.reveal_at(fade_key, consumed, text_end))
2990                .into_any_element(),
2991            );
2992        }
2993
2994        // Text alone is one `Inline`, which needs no box of its own. Images
2995        // keep an identified box: an image's element state is its animation,
2996        // and the box scopes that state per paragraph.
2997        if !has_image {
2998            return child_nodes
2999                .pop()
3000                .unwrap_or_else(|| div().into_any_element());
3001        }
3002
3003        div()
3004            .id(leaf_element_id(fade_key))
3005            .children(child_nodes)
3006            .into_any_element()
3007    }
3008
3009    /// The key an external editor gave this leaf, if any.
3010    fn leaf_key(&self) -> Option<u64> {
3011        self.state.lock().ok().and_then(|state| state.editor_key)
3012    }
3013
3014    fn should_render_inline_flow(&self) -> bool {
3015        let has_image = self.children.iter().any(|child| child.image.is_some());
3016        let has_text = self.children.iter().any(|child| !child.text.is_empty());
3017        // An editor leaf with an image always uses the flow, which reports
3018        // its pieces to the editor (an image-only paragraph would otherwise
3019        // take the plain image path, which reports nothing).
3020        if has_image && self.leaf_key().is_some() {
3021            return true;
3022        }
3023        self.children.iter().any(|child| child.custom.is_some())
3024            || (has_image && has_text)
3025            || self
3026                .children
3027                .iter()
3028                .any(|child| child.marks.iter().any(|(_, mark)| mark.code))
3029    }
3030
3031    fn inline_flow_items(
3032        &self,
3033        leaf_key: Option<TextLeafKey>,
3034        fades: &[(Range<usize>, f32)],
3035        backgrounds: &[(Range<usize>, Hsla)],
3036        node_cx: &NodeContext,
3037        cx: &mut App,
3038    ) -> Vec<InlineFlowItem> {
3039        let mut items = Vec::new();
3040        let mut text = String::new();
3041        let mut highlights: Vec<(Range<usize>, InlineHighlight)> = vec![];
3042        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];
3043        let mut offset = 0;
3044        // Where `text` starts in the paragraph's whole rendered text, which
3045        // is the byte space the fade and background ranges use.
3046        let mut consumed = 0;
3047
3048        for inline_node in &self.children {
3049            if let Some(node) = &inline_node.custom {
3050                if let Ok(mut state) = inline_node.state.lock() {
3051                    state.set_text(text.clone().into());
3052                }
3053                if !text.is_empty() {
3054                    let item_fades = slice_fades(fades, consumed, consumed + text.len());
3055                    let item_backgrounds =
3056                        slice_backgrounds(backgrounds, consumed, consumed + text.len());
3057                    let item_reveal = node_cx.reveal_at(leaf_key, consumed, consumed + text.len());
3058                    consumed += text.len();
3059                    items.push(InlineFlowItem::Text {
3060                        state: inline_node.state.clone(),
3061                        text: std::mem::take(&mut text).into(),
3062                        links: std::mem::take(&mut links),
3063                        highlights: fade_highlights(std::mem::take(&mut highlights), &item_fades),
3064                        backgrounds: item_backgrounds,
3065                        reveal: item_reveal,
3066                    });
3067                }
3068                let mut object_style = HighlightStyle::default();
3069                let mut object_link = None;
3070                for (_, mark) in &inline_node.marks {
3071                    object_style = object_style.highlight(mark_highlight(mark, node_cx, cx).style);
3072                    if let Some(link) = &mark.link {
3073                        object_link = Some(
3074                            link.identifier
3075                                .as_ref()
3076                                .and_then(|id| node_cx.link_refs.get(id))
3077                                .unwrap_or(link)
3078                                .clone(),
3079                        );
3080                        object_style.color = Some(node_cx.style.link());
3081                        object_style.underline = Some(link_underline(&node_cx.style));
3082                    }
3083                }
3084                let rendered_node = node.clone();
3085                let extensions = node_cx.markdown_extensions.clone();
3086                items.push(InlineFlowItem::Object {
3087                    text: node.shared_text(),
3088                    accessibility_label: node.shared_accessibility_name(),
3089                    id: node.source_range().map_or(items.len(), |range| range.start),
3090                    renderer: Arc::new(move |context, window, cx| {
3091                        extensions.render_inline(&rendered_node, context, window, cx)
3092                    }),
3093                    selected: inline_node.custom_selection.clone(),
3094                    style: object_style,
3095                    link: object_link,
3096                });
3097                consumed += inline_node.text.len();
3098                offset = 0;
3099                continue;
3100            }
3101            let text_len = inline_node.text.len();
3102            text.push_str(&inline_node.text);
3103
3104            if let Some(image) = &inline_node.image {
3105                if !text.is_empty() {
3106                    if let Ok(mut state) = inline_node.state.lock() {
3107                        state.set_text(text.clone().into());
3108                    }
3109                    items.push(InlineFlowItem::Text {
3110                        state: inline_node.state.clone(),
3111                        text: text.clone().into(),
3112                        links: links.clone(),
3113                        highlights: fade_highlights(
3114                            highlights.clone(),
3115                            &slice_fades(fades, consumed, consumed + text.len()),
3116                        ),
3117                        backgrounds: slice_backgrounds(
3118                            backgrounds,
3119                            consumed,
3120                            consumed + text.len(),
3121                        ),
3122                        reveal: node_cx.reveal_at(leaf_key, consumed, consumed + text.len()),
3123                    });
3124                }
3125
3126                items.push(InlineFlowItem::Image {
3127                    source: node_cx.image_source(image),
3128                    link: image.link.clone(),
3129                    title: image.title(),
3130                    width: image.width,
3131                    height: image.height,
3132                });
3133
3134                consumed += text.len();
3135                text.clear();
3136                links.clear();
3137                highlights.clear();
3138                offset = 0;
3139            } else {
3140                let mut node_highlights = vec![];
3141                for (range, style) in &inline_node.marks {
3142                    let inner_range = (offset + range.start)..(offset + range.end);
3143                    let mut highlight = mark_highlight(style, node_cx, cx);
3144
3145                    if let Some(mut link_mark) = style.link.clone() {
3146                        highlight.style.color = Some(node_cx.style.link());
3147                        highlight.style.underline = Some(link_underline(&node_cx.style));
3148
3149                        if let Some(identifier) = link_mark.identifier.as_ref()
3150                            && let Some(mark) = node_cx.link_refs.get(identifier)
3151                        {
3152                            link_mark = mark.clone();
3153                        }
3154
3155                        links.push((inner_range.clone(), link_mark));
3156                    }
3157
3158                    node_highlights.push((inner_range, highlight));
3159                }
3160
3161                highlights = combine_highlights(highlights, node_highlights);
3162                offset += text_len;
3163            }
3164        }
3165
3166        if !text.is_empty() {
3167            if let Ok(mut state) = self.state.lock() {
3168                state.set_text(text.clone().into());
3169            }
3170            let highlights = fade_highlights(
3171                highlights,
3172                &slice_fades(fades, consumed, consumed + text.len()),
3173            );
3174            let backgrounds = slice_backgrounds(backgrounds, consumed, consumed + text.len());
3175            let reveal = node_cx.reveal_at(leaf_key, consumed, consumed + text.len());
3176            items.push(InlineFlowItem::Text {
3177                state: self.state.clone(),
3178                text: text.into(),
3179                links,
3180                highlights,
3181                backgrounds,
3182                reveal,
3183            });
3184        }
3185
3186        items
3187    }
3188}
3189
3190/// The element id of a block that needs one, from `kind` and the block's
3191/// source start, which is unique across a parsed Markdown document. The HTML
3192/// parser records no spans, so its blocks fall back to their index among
3193/// their siblings: unique among them, though not across nesting levels.
3194fn block_element_id(kind: &'static str, span: Option<Span>, ix: usize) -> ElementId {
3195    (kind, span.map_or(ix, |span| span.start)).into()
3196}
3197
3198/// The element id of a paragraph that needs one: an inline flow, whose
3199/// objects are accessibility nodes, or an image, whose element state is its
3200/// animation. The leaf key is unique per paragraph in a parsed Markdown
3201/// document; the HTML parser records no spans, so its paragraphs share one.
3202fn leaf_element_id(fade_key: Option<TextLeafKey>) -> ElementId {
3203    fade_key.map_or_else(|| ElementId::from("p"), ElementId::from)
3204}
3205
3206/// A link's underline: a hairline in a translucent link color, so a run of
3207/// links reads as text first and as links second.
3208fn link_underline(style: &TextViewStyle) -> gpui::UnderlineStyle {
3209    gpui::UnderlineStyle {
3210        thickness: px(1.),
3211        color: Some(style.link().opacity(0.4)),
3212        ..Default::default()
3213    }
3214}
3215
3216/// The font size the blocks inherit, which heading sizes and the spacing of
3217/// lists, tables and rules are proportional to (like CSS `em`), so a compact
3218/// 14px view and a 16px document keep the same rhythm.
3219///
3220/// Resolved once per frame into the context; a context built without it
3221/// (as in tests) resolves it from the window.
3222fn body_font_size(node_cx: &NodeContext, window: &Window) -> Pixels {
3223    if node_cx.body_font_size > Pixels::ZERO {
3224        node_cx.body_font_size
3225    } else {
3226        window.text_style().font_size.to_pixels(window.rem_size())
3227    }
3228}
3229
3230/// A heading's `(size scale, line height, section gap)`, the size relative to
3231/// the body text and the gap above it relative to the heading itself.
3232/// Hierarchy comes from weight rather than size: every level is semibold and
3233/// the sizes step down gently from the body.
3234fn heading_metrics(level: u8) -> (f32, f32, f32) {
3235    match level {
3236        1 => (1.8, 1.25, 1.1),
3237        2 => (4. / 3., 1.35, 1.6),
3238        3 => (17. / 15., 1.35, 1.4),
3239        4 => (1., 1.35, 1.4),
3240        5 => (14. / 15., 1.35, 1.4),
3241        _ => (13. / 15., 1.35, 1.4),
3242    }
3243}
3244
3245/// The gap below a heading, relative to the heading's size.
3246const HEADING_BOTTOM_GAP: f32 = 0.35;
3247
3248/// The size a heading renders at, including one a caller's heading
3249/// refinement pins, which its gaps follow.
3250fn heading_size(level: u8, node_cx: &NodeContext, window: &Window) -> Pixels {
3251    node_cx.style.heading(level).text.font_size.map_or_else(
3252        || body_font_size(node_cx, window) * heading_metrics(level).0,
3253        |size| size.to_pixels(window.rem_size()),
3254    )
3255}
3256
3257/// The room above and below a horizontal rule.
3258fn rule_space(body_font_size: Pixels) -> Pixels {
3259    body_font_size * 1.6
3260}
3261
3262/// The gap below a block: a paragraph gap, unless nothing follows it.
3263fn block_gap(options: &NodeRenderOptions, style: &TextViewStyle) -> Rems {
3264    if options.in_list || options.is_last {
3265        rems(0.)
3266    } else {
3267        style.paragraph_gap()
3268    }
3269}
3270
3271/// `block` with `gap` below it. The box only exists to hold the padding,
3272/// so a block with no gap below it is returned as is.
3273fn gapped(block: AnyElement, gap: Rems) -> AnyElement {
3274    if gap.is_zero() {
3275        block
3276    } else {
3277        div().pb(gap).child(block).into_any_element()
3278    }
3279}
3280
3281/// The fade ranges overlapping `start..end`, rebased to start at `start`.
3282fn slice_fades(
3283    fades: &[(Range<usize>, f32)],
3284    start: usize,
3285    end: usize,
3286) -> Vec<(Range<usize>, f32)> {
3287    slice_ranges(fades, start, end, |range, fade_out| (range, *fade_out))
3288}
3289
3290/// The range highlight backgrounds overlapping `start..end`, rebased to
3291/// start at `start`.
3292fn slice_backgrounds(
3293    backgrounds: &[(Range<usize>, Hsla)],
3294    start: usize,
3295    end: usize,
3296) -> Vec<(Range<usize>, Hsla)> {
3297    slice_ranges(backgrounds, start, end, |range, color| (range, *color))
3298}
3299
3300/// A table column's cell widths for layout, excluding the divider after it.
3301struct TableColumn {
3302    /// The width that fits the content on its widest line.
3303    max: f32,
3304    /// The width below which the column stops shrinking.
3305    min: f32,
3306    divider: f32,
3307}
3308
3309/// Horizontal padding of a table cell: `px_3` unless `style.table_cell`
3310/// sets it.
3311fn table_cell_padding_x(style: &TextViewStyle, rem_size: Pixels) -> f32 {
3312    let padding = &style.table_cell().padding;
3313    let side = |length: Option<DefiniteLength>| match length {
3314        Some(DefiniteLength::Absolute(length)) => f32::from(length.to_pixels(rem_size)),
3315        _ => CELL_PAD_PX / 2.,
3316    };
3317    side(padding.left) + side(padding.right)
3318}
3319
3320const CELL_PAD_PX: f32 = 24.0; // px_3 horizontal padding
3321const CELL_MIN_PX: f32 = 48.0;
3322const CELL_BORDER_PX: f32 = 1.0; // border_r_1 drawn by every column but the last
3323
3324/// The max-content width of every table column: the widest cell line,
3325/// shaped with the runs the cell renders with, plus the cell's padding and
3326/// border. Never capped: a cap would clip overflowing text *and* leave it
3327/// outside the scrollable width, making it unreachable.
3328fn measure_table_columns(
3329    table: &Table,
3330    col_count: usize,
3331    node_cx: &NodeContext,
3332    window: &mut Window,
3333    cx: &mut App,
3334) -> Vec<f32> {
3335    let text_styles = TableTextStyles::new(&node_cx.style, window);
3336    let rem_size = window.rem_size();
3337    let mono_family = cx.theme().tokens.typography.mono.clone();
3338    let inline_code = node_cx.style.inline_code_highlight();
3339    let cached = table.column_widths_cache.0.lock().ok().and_then(|cache| {
3340        cache
3341            .as_ref()
3342            .filter(|cached| {
3343                cached.widths.len() == col_count
3344                    && cached.text_styles == text_styles
3345                    && cached.rem_size == rem_size
3346                    && cached.mono_family == mono_family
3347                    && cached.inline_code == inline_code
3348                    && cached
3349                        .text_system
3350                        .upgrade()
3351                        .is_some_and(|system| Arc::ptr_eq(&system, window.text_system()))
3352            })
3353            .cloned()
3354    });
3355    let measured = cached.unwrap_or_else(|| {
3356        let (widths, custom_cells) =
3357            measure_table_text_columns(table, col_count, &text_styles, node_cx, window, cx);
3358        let measured = Arc::new(TableColumnWidths {
3359            text_system: Arc::downgrade(window.text_system()),
3360            text_styles: text_styles.clone(),
3361            rem_size,
3362            mono_family,
3363            inline_code,
3364            widths,
3365            custom_cells,
3366        });
3367        if let Ok(mut cache) = table.column_widths_cache.0.lock() {
3368            *cache = Some(measured.clone());
3369        }
3370        measured
3371    });
3372
3373    // Resource-backed cells must be measured again, even when their text is
3374    // unchanged. Keep callbacks outside the cache lock and do not retain their
3375    // widths: a loaded image or a plugin can get narrower as well as wider.
3376    let mut col_w = measured.widths.clone();
3377    for &(row_ix, ix) in &measured.custom_cells {
3378        let cell = &table.children[row_ix].children[ix];
3379        let items = cell.children.inline_flow_items(None, &[], &[], node_cx, cx);
3380        let style = text_styles.row_refinement(row_ix).clone();
3381        let width = window.with_text_style(Some(style), |window| {
3382            super::inline_flow::intrinsic_width(&items, window, cx)
3383        });
3384        let border = if ix + 1 < col_count {
3385            CELL_BORDER_PX
3386        } else {
3387            0.
3388        };
3389        col_w[ix] = col_w[ix].max(f32::from(width) + CELL_PAD_PX + border);
3390    }
3391    col_w
3392}
3393
3394// The first measurement still visits all ordinary cells. This cache avoids
3395// repeating that work; it is not a budget for the initial table layout.
3396fn measure_table_text_columns(
3397    table: &Table,
3398    col_count: usize,
3399    text_styles: &TableTextStyles,
3400    node_cx: &NodeContext,
3401    window: &mut Window,
3402    cx: &mut App,
3403) -> (Vec<f32>, Vec<(usize, usize)>) {
3404    let mut col_w = vec![CELL_MIN_PX; col_count];
3405    let mut custom_cells = Vec::new();
3406    for (row_ix, row) in table.children.iter().enumerate() {
3407        let text_style = text_styles.row(row_ix);
3408        let font_size = text_style.font_size.to_pixels(window.rem_size());
3409        for (ix, cell) in row.children.iter().enumerate() {
3410            let Some(slot) = col_w.get_mut(ix) else {
3411                continue;
3412            };
3413            if cell
3414                .children
3415                .children
3416                .iter()
3417                .any(|node| node.custom.is_some())
3418            {
3419                custom_cells.push((row_ix, ix));
3420                continue;
3421            }
3422            let text = cell.children.text();
3423            let highlights = cell.children.inline_highlights(node_cx, cx);
3424            let mut w = 0.0_f32;
3425            let mut line_start = 0;
3426            for line in text.split('\n') {
3427                let start = line_start + (line.len() - line.trim_start().len());
3428                let line_end = line_start + line.len();
3429                line_start = line_end + 1;
3430                let line = line.trim();
3431                if line.is_empty() {
3432                    continue;
3433                }
3434                let end = start + line.len();
3435                let line_highlights = highlights
3436                    .iter()
3437                    .filter_map(|(range, highlight)| {
3438                        let clipped = range.start.max(start)..range.end.min(end);
3439                        (clipped.start < clipped.end).then(|| {
3440                            (
3441                                clipped.start - start..clipped.end - start,
3442                                highlight.clone(),
3443                            )
3444                        })
3445                    })
3446                    .collect::<Vec<_>>();
3447                let mut line_w = gpui::Pixels::ZERO;
3448                for (range, scale) in
3449                    text_size_ranges(line.len(), &line_highlights, font_size, window.rem_size())
3450                {
3451                    let highlights = slice_ranges(
3452                        &line_highlights,
3453                        range.start,
3454                        range.end,
3455                        |range, highlight| (range, highlight.clone()),
3456                    );
3457                    let insets = code_insets(&highlights, font_size * scale, window.rem_size());
3458                    line_w += insets.left + insets.right;
3459                    let runs = text_runs(range.len(), text_style, &highlights);
3460                    line_w += window
3461                        .text_system()
3462                        .layout_line(&line[range], font_size * scale, &runs, None)
3463                        .width;
3464                }
3465                w = w.max(f32::from(line_w));
3466            }
3467            // Border-box widths, so the padding and border the cell draws
3468            // must leave the measured text its full width.
3469            let border = if ix + 1 < col_count {
3470                CELL_BORDER_PX
3471            } else {
3472                0.
3473            };
3474            *slot = slot.max(w + CELL_PAD_PX + border);
3475        }
3476    }
3477    (col_w, custom_cells)
3478}
3479
3480impl Paragraph {
3481    fn to_markdown(&self) -> String {
3482        if self.children.iter().any(|node| node.custom.is_some()) {
3483            let mut source = MarkdownSource::default();
3484            for node in &self.children {
3485                if node.custom.is_some() {
3486                    source.push_object(node);
3487                } else {
3488                    source.push_text(&node.text, &node.marks, 0..node.text.len());
3489                    if let Some(image) = &node.image {
3490                        source.push_str(&image_markdown(image));
3491                    }
3492                }
3493            }
3494            let mut text = source.finish();
3495            text.push_str("\n\n");
3496            return text;
3497        }
3498        let mut text = self
3499            .children
3500            .iter()
3501            .map(|text_node| {
3502                let mut text = text_node.text.to_string();
3503                for (range, style) in &text_node.marks {
3504                    if style.bold {
3505                        text = format!("**{}**", &text_node.text[range.clone()]);
3506                    }
3507                    if style.italic {
3508                        text = format!("*{}*", &text_node.text[range.clone()]);
3509                    }
3510                    if style.strikethrough {
3511                        text = format!("~~{}~~", &text_node.text[range.clone()]);
3512                    }
3513                    if style.code {
3514                        text = format!("`{}`", &text_node.text[range.clone()]);
3515                    }
3516                    if style.highlight.is_some() {
3517                        text = format!("=={}==", &text_node.text[range.clone()]);
3518                    }
3519                    if let Some(link) = &style.link {
3520                        text = format!("[{}]({})", &text_node.text[range.clone()], link.url);
3521                    }
3522                }
3523
3524                if let Some(image) = &text_node.image {
3525                    let alt = image.alt.clone().unwrap_or_default();
3526                    let title = image
3527                        .title
3528                        .clone()
3529                        .map_or(String::new(), |t| format!(" \"{}\"", t));
3530                    text.push_str(&format!("![{}]({}{})", alt, image.url, title))
3531                }
3532
3533                text
3534            })
3535            .collect::<Vec<_>>()
3536            .join("");
3537
3538        text.push_str("\n\n");
3539        text
3540    }
3541}
3542
3543impl BlockNode {
3544    /// Converts the node to markdown format.
3545    ///
3546    /// This is used to generate markdown for test.
3547    #[allow(dead_code)]
3548    pub(crate) fn to_markdown(&self) -> String {
3549        match self {
3550            BlockNode::Root { children, .. } => children
3551                .iter()
3552                .map(|child| child.to_markdown())
3553                .collect::<Vec<_>>()
3554                .join("\n\n"),
3555            BlockNode::Paragraph(paragraph) => paragraph.to_markdown(),
3556            BlockNode::Heading {
3557                level, children, ..
3558            } => {
3559                let hashes = "#".repeat(*level as usize);
3560                format!("{} {}", hashes, children.to_markdown())
3561            }
3562            BlockNode::Blockquote { children, .. } => {
3563                let content = children
3564                    .iter()
3565                    .map(|child| child.to_markdown())
3566                    .collect::<Vec<_>>()
3567                    .join("\n\n");
3568
3569                content
3570                    .lines()
3571                    .map(|line| format!("> {}", line))
3572                    .collect::<Vec<_>>()
3573                    .join("\n")
3574            }
3575            BlockNode::List {
3576                children,
3577                ordered,
3578                start,
3579                ..
3580            } => children
3581                .iter()
3582                .enumerate()
3583                .map(|(i, child)| {
3584                    let prefix = if *ordered {
3585                        format!("{}. ", ordered_list_ordinal(*start, i))
3586                    } else {
3587                        "- ".to_string()
3588                    };
3589                    format!("{}{}", prefix, child.to_markdown())
3590                })
3591                .collect::<Vec<_>>()
3592                .join("\n"),
3593            BlockNode::ListItem {
3594                children, checked, ..
3595            } => {
3596                let checkbox = if let Some(checked) = checked {
3597                    if *checked { "[x] " } else { "[ ] " }
3598                } else {
3599                    ""
3600                };
3601                format!(
3602                    "{}{}",
3603                    checkbox,
3604                    children
3605                        .iter()
3606                        .map(|child| child.to_markdown())
3607                        .collect::<Vec<_>>()
3608                        .join("\n")
3609                )
3610            }
3611            BlockNode::CodeBlock(code_block) => {
3612                format!(
3613                    "```{}\n{}\n```",
3614                    code_block.lang.clone().unwrap_or_default(),
3615                    code_block.code()
3616                )
3617            }
3618            BlockNode::Table(table) => table.to_markdown(),
3619            BlockNode::Break { html, .. } => {
3620                if *html {
3621                    "<br>".to_string()
3622                } else {
3623                    "\n".to_string()
3624                }
3625            }
3626            BlockNode::HorizontalRule { .. } => "---".to_string(),
3627            BlockNode::Custom { node, .. } => node.to_markdown(),
3628            BlockNode::Definition {
3629                identifier,
3630                url,
3631                title,
3632                ..
3633            } => {
3634                if let Some(title) = title {
3635                    format!("[{}]: {} \"{}\"", identifier, url, title)
3636                } else {
3637                    format!("[{}]: {}", identifier, url)
3638                }
3639            }
3640            BlockNode::Unknown { .. } => "".to_string(),
3641        }
3642        .trim()
3643        .to_string()
3644    }
3645}
3646
3647impl BlockNode {
3648    fn render_list_item_row(
3649        content: AnyElement,
3650        ix: usize,
3651        options: NodeRenderOptions,
3652        checked: Option<bool>,
3653        style: &TextViewStyle,
3654        line_height: Pixels,
3655        indent: Pixels,
3656    ) -> Div {
3657        h_flex()
3658            .w_full()
3659            .min_w_0()
3660            .relative()
3661            .items_start()
3662            .content_start()
3663            .when(!options.todo && checked.is_none(), |this| {
3664                // The marker hangs in an indent column, right-aligned against
3665                // the text like an outside list marker, and steps back in
3666                // color so the item text leads.
3667                this.child(
3668                    div()
3669                        .flex_none()
3670                        .min_w(indent)
3671                        .pr(indent * 0.3)
3672                        .text_right()
3673                        .text_color(style.muted_foreground())
3674                        .refine_style(style.list_style())
3675                        .child(
3676                            list_item_prefix(
3677                                ix,
3678                                options.list_start,
3679                                options.ordered,
3680                                options.depth,
3681                            )
3682                            .trim_end()
3683                            .to_string(),
3684                        ),
3685                )
3686            })
3687            .when_some(checked, |this, checked| {
3688                // Todo list checkbox
3689                let check_svg = if style.is_dark() {
3690                    CHECK_SVG_DARK
3691                } else {
3692                    CHECK_SVG_LIGHT
3693                };
3694                this.child(
3695                    div()
3696                        .flex()
3697                        .mr_2()
3698                        .h(line_height)
3699                        .flex_none()
3700                        .items_center()
3701                        .justify_center()
3702                        .child(
3703                            div()
3704                                .flex()
3705                                .size(rems(0.9375))
3706                                .items_center()
3707                                .justify_center()
3708                                .rounded(px(4.))
3709                                .border(px(1.5))
3710                                .border_color(
3711                                    style
3712                                        .list_style()
3713                                        .text
3714                                        .color
3715                                        .unwrap_or(style.muted_foreground()),
3716                                )
3717                                .when(checked, |this| {
3718                                    this.bg(style.link()).border_color(style.link()).child(
3719                                        img(Arc::new(Image::from_bytes(
3720                                            ImageFormat::Svg,
3721                                            check_svg.to_vec(),
3722                                        )))
3723                                        .size(rems(0.75)),
3724                                    )
3725                                }),
3726                        ),
3727                )
3728            })
3729            .child(
3730                div()
3731                    .flex_1()
3732                    .min_w_0()
3733                    .overflow_hidden()
3734                    // A done task steps back so the open ones stand out.
3735                    .when(checked == Some(true), |this| {
3736                        this.text_color(style.muted_foreground())
3737                    })
3738                    .child(content),
3739            )
3740    }
3741
3742    fn render_list_item(
3743        item: &BlockNode,
3744        ix: usize,
3745        options: NodeRenderOptions,
3746        node_cx: &NodeContext,
3747        window: &mut Window,
3748        cx: &mut App,
3749    ) -> AnyElement {
3750        match item {
3751            BlockNode::ListItem {
3752                children,
3753                spread,
3754                checked,
3755                ..
3756            } => {
3757                let body = body_font_size(node_cx, window);
3758                let indent = body * 1.4;
3759                div()
3760                    .w_full()
3761                    .min_w_0()
3762                    // Items breathe a little apart, less than paragraphs do.
3763                    .when(ix > 0, |this| this.pt(body * 0.25))
3764                    .when(*spread, |this| this.child(div()))
3765                    .children({
3766                        let mut items: Vec<Div> = Vec::with_capacity(children.len());
3767
3768                        for (child_ix, child) in children.iter().enumerate() {
3769                            match child {
3770                                BlockNode::Paragraph { .. } => {
3771                                    let last_not_list = child_ix > 0
3772                                        && !matches!(
3773                                            children[child_ix - 1],
3774                                            BlockNode::List { .. }
3775                                        );
3776
3777                                    let text = child.render_block(
3778                                        NodeRenderOptions {
3779                                            depth: options.depth + 1,
3780                                            todo: checked.is_some(),
3781                                            is_last: true,
3782                                            ..options
3783                                        },
3784                                        node_cx,
3785                                        window,
3786                                        cx,
3787                                    );
3788
3789                                    // Continuation paragraph — stack vertically below
3790                                    // the previous row, indented to align with the text
3791                                    // column (past bullet/number prefix).
3792                                    if last_not_list {
3793                                        if let Some(preceding_row) = items.pop() {
3794                                            items.push(
3795                                                div().child(preceding_row).child(
3796                                                    div()
3797                                                        .w_full()
3798                                                        .pl(indent)
3799                                                        .overflow_hidden()
3800                                                        .child(text),
3801                                                ),
3802                                            );
3803                                            continue;
3804                                        }
3805                                    }
3806
3807                                    items.push(Self::render_list_item_row(
3808                                        text,
3809                                        ix,
3810                                        options,
3811                                        *checked,
3812                                        &node_cx.style,
3813                                        window.line_height(),
3814                                        indent,
3815                                    ));
3816                                }
3817                                BlockNode::List { .. } => {
3818                                    items.push(div().ml(indent).child(child.render_block(
3819                                        NodeRenderOptions {
3820                                            depth: options.depth + 1,
3821                                            todo: checked.is_some(),
3822                                            is_last: true,
3823                                            ..options
3824                                        },
3825                                        node_cx,
3826                                        window,
3827                                        cx,
3828                                    )));
3829                                }
3830                                BlockNode::Root { .. }
3831                                | BlockNode::Heading { .. }
3832                                | BlockNode::Blockquote { .. }
3833                                | BlockNode::CodeBlock(_)
3834                                | BlockNode::Custom { .. }
3835                                | BlockNode::Table(_)
3836                                | BlockNode::HorizontalRule { .. } => {
3837                                    let block = child.render_block(
3838                                        NodeRenderOptions {
3839                                            depth: options.depth + 1,
3840                                            todo: checked.is_some(),
3841                                            is_last: true,
3842                                            ..options
3843                                        },
3844                                        node_cx,
3845                                        window,
3846                                        cx,
3847                                    );
3848
3849                                    if child_ix == 0 {
3850                                        items.push(Self::render_list_item_row(
3851                                            block,
3852                                            ix,
3853                                            options,
3854                                            *checked,
3855                                            &node_cx.style,
3856                                            window.line_height(),
3857                                            indent,
3858                                        ));
3859                                    } else {
3860                                        // Indent continuation blocks to align with a
3861                                        // nested sub-list (`ml(indent)`) and with
3862                                        // continuation paragraphs.
3863                                        items.push(
3864                                            div()
3865                                                .w_full()
3866                                                .min_w_0()
3867                                                .pl(indent)
3868                                                .overflow_hidden()
3869                                                .child(block),
3870                                        );
3871                                    }
3872                                }
3873                                BlockNode::ListItem { .. }
3874                                | BlockNode::Break { .. }
3875                                | BlockNode::Definition { .. }
3876                                | BlockNode::Unknown => {}
3877                            }
3878                        }
3879                        items
3880                    })
3881                    .into_any_element()
3882            }
3883            _ => div().into_any_element(),
3884        }
3885    }
3886
3887    /// Render a Markdown table.
3888    ///
3889    /// Columns are sized from the measured max-content width of their cells,
3890    /// so they line up and fit their content on proportional fonts and mixed
3891    /// scripts alike. The layout adapts to the frame:
3892    ///
3893    /// - Wider than the content: every column fits its content and the spare
3894    ///   width is shared in proportion to it.
3895    /// - Narrower: the narrow columns keep their full content width and only
3896    ///   the widest ones shrink and wrap, all down to one common width (see
3897    ///   [`Self::render_table_rows`]). Shrinking every column by the same
3898    ///   ratio would wrap the last word or character of a column that nearly
3899    ///   fits onto a line of its own.
3900    /// - Narrower than the column floors: the default layout clips, while
3901    ///   `style.table` overflow-x: scroll keeps the floors and scrolls
3902    ///   horizontally, so no content ever becomes unreachable.
3903    ///
3904    /// `white_space: nowrap` on `style.table_cell` composes like in CSS: the
3905    /// refinement keeps cell text on a single line, and the scroll layout
3906    /// raises the floors to the full content widths so the single-line
3907    /// columns never shrink.
3908    fn render_table(
3909        item: &BlockNode,
3910        options: &NodeRenderOptions,
3911        node_cx: &NodeContext,
3912        text_size: Pixels,
3913        window: &mut Window,
3914        cx: &mut App,
3915    ) -> impl IntoElement {
3916        // A scrolling column stops shrinking (and the table starts to scroll)
3917        // at a floor scaled to its content: roughly the width at which the
3918        // text wraps to `CELL_WRAP_MAX_LINES` lines, clamped between the two
3919        // bounds so moderate columns can still wrap meaningfully while one
3920        // huge column cannot push the scroll threshold arbitrarily high.
3921        const CELL_WRAP_MAX_LINES: f32 = 2.0;
3922        const CELL_WRAP_MIN_PX: f32 = 160.0;
3923        const CELL_WRAP_MAX_PX: f32 = 480.0;
3924        const TABLE_BORDER_PX: f32 = 2.0; // the frame's border_1, left + right
3925
3926        let table = match item {
3927            BlockNode::Table(table) => table,
3928            _ => return div().into_any_element(),
3929        };
3930
3931        let style = &node_cx.style;
3932        let col_count = table
3933            .children
3934            .iter()
3935            .map(|row| row.children.len())
3936            .max()
3937            .unwrap_or(0);
3938        let col_w = measure_table_columns(table, col_count, node_cx, window, cx);
3939        let scroll = matches!(style.table().overflow.x, Some(Overflow::Scroll));
3940        let nowrap = style.table_cell().text.white_space == Some(WhiteSpace::Nowrap);
3941        let rem_size = window.rem_size();
3942        let wrap_min_px = f32::from(rems(4.).to_pixels(rem_size));
3943        // Widths were measured with the default cell padding.
3944        let padding_delta = table_cell_padding_x(style, rem_size) - CELL_PAD_PX;
3945        // Lengths are rounded to device pixels for layout. Round the content
3946        // widths up, or a column can lose the fraction of a pixel that keeps
3947        // its text on one line.
3948        let scale_factor = window.scale_factor();
3949        let device_ceil = |width: f32| (width * scale_factor).ceil() / scale_factor;
3950        let columns: Vec<TableColumn> = col_w
3951            .iter()
3952            .enumerate()
3953            .map(|(ix, &w)| {
3954                let w = w + padding_delta;
3955                let floor = if scroll && nowrap {
3956                    w
3957                } else if scroll {
3958                    (w / CELL_WRAP_MAX_LINES)
3959                        .clamp(CELL_WRAP_MIN_PX, CELL_WRAP_MAX_PX)
3960                        .min(w)
3961                } else {
3962                    w.min(wrap_min_px)
3963                };
3964                let divider = if ix + 1 < col_count {
3965                    CELL_BORDER_PX
3966                } else {
3967                    0.
3968                };
3969                let max = device_ceil(w - divider);
3970                TableColumn {
3971                    min: device_ceil(floor - divider).min(max),
3972                    max,
3973                    divider,
3974                }
3975            })
3976            .collect();
3977
3978        let rows = Self::render_table_rows(table, &columns, node_cx, window, cx);
3979        let frame = StyleRefinement::default()
3980            .text_size(text_size)
3981            .bg(style
3982                .table_background()
3983                .unwrap_or(cx.theme().tokens.colors.surface))
3984            .border_1()
3985            .border_color(style.border())
3986            .refine_style(style.table());
3987        let body = if scroll {
3988            let scroll_handle = window
3989                .use_keyed_state(
3990                    block_element_id("table-scroll", table.span, options.ix),
3991                    cx,
3992                    |_, _| ScrollHandle::default(),
3993                )
3994                .read(cx)
3995                .clone();
3996            let min_total_w = columns
3997                .iter()
3998                .map(|column| column.min + column.divider)
3999                .sum::<f32>()
4000                + TABLE_BORDER_PX;
4001            // Scroll viewport owns the visible frame, including any
4002            // caller-provided radius. Keeping the border here makes the
4003            // rounded frame stable while the wider row track moves below it.
4004            //
4005            // `horizontal_scroll_area` clips with `overflow_hidden` and
4006            // delegates the wheel to a sibling `ScrollableMask`, so the
4007            // gesture is locked to its starting axis and a horizontal swipe
4008            // is consumed before an ancestor scroller (`gpui::list` under
4009            // `TextView::scrollable`) can take its vertical component.
4010            horizontal_scroll_area(
4011                block_element_id("table", table.span, options.ix),
4012                &scroll_handle,
4013                &frame,
4014                // Row track sized to `max(viewport, column floors)`:
4015                // `min_w_full` fills the frame while the columns can still
4016                // shrink-to-fit (their text wrapping), the definite
4017                // `w(min_total_w)` keeps the floors once they are reached,
4018                // letting the track exceed the viewport and scroll.
4019                div().min_w_full().w(px(min_total_w)).children(rows),
4020            )
4021            .into_any_element()
4022        } else {
4023            div()
4024                .w_full()
4025                .overflow_hidden()
4026                .refine_style(&frame)
4027                .children(rows)
4028                .into_any_element()
4029        };
4030
4031        div()
4032            .pb(block_gap(options, style))
4033            .w_full()
4034            .child(body)
4035            // Custom actions row (e.g. copy / download) rendered below the
4036            // table. The hook's element spans full width; alignment is up to
4037            // the caller (e.g. `h_flex().justify_end()`). The gap keeps hover
4038            // backgrounds of the action buttons off the table border, and the
4039            // id scopes the caller's element ids per table, so plain ids like
4040            // `"copy"` don't collide across tables (same as code blocks).
4041            .children(node_cx.table_actions.clone().map(|f| {
4042                div()
4043                    .id(block_element_id("table-actions", table.span, options.ix))
4044                    .mt_1()
4045                    .child(f(&table.cached_table_data(), window, cx))
4046            }))
4047            .into_any_element()
4048    }
4049
4050    /// Corner radii for the first and last table rows, derived from the
4051    /// frame radius in `style.table()`.
4052    ///
4053    /// GPUI clips children with a rectangular content mask, so a square row
4054    /// background (e.g. the header fill) pokes out of a rounded table frame
4055    /// at the corners. Rounding the first row's top and the last row's
4056    /// bottom corners to the frame radius — inset by the frame's 1px border
4057    /// sitting between frame and rows — keeps the row fills inside the
4058    /// frame. (A horizontally scrolled track can still meet the viewport
4059    /// corner with a square edge mid-scroll; only a rounded content mask
4060    /// could clip that, and gpui masks are rectangular.)
4061    fn table_row_corner_radii(style: &TextViewStyle, window: &Window) -> [Option<Pixels>; 4] {
4062        let rem_size = window.rem_size();
4063        let inset = |radius: Option<gpui::AbsoluteLength>| {
4064            radius.map(|radius| (radius.to_pixels(rem_size) - px(1.)).max(px(0.)))
4065        };
4066        let radii = &style.table().corner_radii;
4067        [
4068            inset(radii.top_left),
4069            inset(radii.top_right),
4070            inset(radii.bottom_left),
4071            inset(radii.bottom_right),
4072        ]
4073    }
4074
4075    /// The table rows, each a flex row laid out on the same column grid.
4076    ///
4077    /// Every column is a cell plus slack beside it. A cell grows from zero up
4078    /// to its content width `max` and never below its floor `min`:
4079    /// flex layout grows all cells equally and freezes each one that reaches
4080    /// its content width, so the narrow columns fit their content and the
4081    /// widest ones share what is left. The slack grows only after every cell
4082    /// is frozen, so a frame wider than the content spreads the extra width
4083    /// in proportion to the columns, placed after, around or before the cell
4084    /// to follow the column alignment.
4085    ///
4086    /// A row with fewer cells than the table keeps empty cells in their
4087    /// place, so its columns line up with the other rows.
4088    fn render_table_rows(
4089        table: &Table,
4090        columns: &[TableColumn],
4091        node_cx: &NodeContext,
4092        window: &mut Window,
4093        cx: &mut App,
4094    ) -> Vec<AnyElement> {
4095        // The slack's share of the frame while any cell is still growing is
4096        // `SLACK_GROW / (cells * CELL_GROW)`, a negligible fraction of a
4097        // pixel. The slack factors still add up to `SLACK_GROW`: flex layout
4098        // hands out only that fraction of the free space when the factors
4099        // sum to less than one, and the slack must take all of it.
4100        const CELL_GROW: f32 = 1e6;
4101        const SLACK_GROW: f32 = 1.0;
4102
4103        let style = &node_cx.style;
4104        let col_count = columns.len();
4105        let total_w = columns.iter().map(|column| column.max).sum::<f32>().max(1.);
4106        // The slack is part of the column, so it carries the cell's fill and
4107        // the column divider in the cell's border color.
4108        let mut slack_style = StyleRefinement::default();
4109        slack_style.background = style.table_cell().background.clone();
4110        slack_style.border_color = style.table_cell().border_color;
4111        let slack = |grow: f32| {
4112            div()
4113                .flex_basis(px(0.))
4114                .flex_grow(grow)
4115                .flex_shrink(0.)
4116                .border_color(style.border())
4117                .refine_style(&slack_style)
4118        };
4119
4120        let row_count = table.children.len();
4121        let [top_left, top_right, bottom_left, bottom_right] =
4122            Self::table_row_corner_radii(style, window);
4123        let mut rows = Vec::with_capacity(row_count);
4124        let mut cell_ordinal = 0;
4125        for (row_ix, row) in table.children.iter().enumerate() {
4126            let mut cells = Vec::with_capacity(col_count * 2);
4127            for (ix, column) in columns.iter().enumerate() {
4128                let is_last_col = ix + 1 == col_count;
4129                let align = table.column_align(ix);
4130                let content = row.children.get(ix).map(|cell| {
4131                    let fade_key = table
4132                        .span
4133                        .map(|span| TextLeafKey::table_cell(span.start, cell_ordinal));
4134                    cell_ordinal += 1;
4135                    cell.children.render(fade_key, node_cx, window, cx)
4136                });
4137
4138                let grow = SLACK_GROW * column.max / total_w;
4139                let (lead, trail) = match align {
4140                    ColumnumnAlign::None | ColumnumnAlign::Left => (0., grow),
4141                    ColumnumnAlign::Center => (grow / 2., grow / 2.),
4142                    ColumnumnAlign::Right => (grow, 0.),
4143                };
4144                if lead > 0. {
4145                    cells.push(slack(lead).into_any_element());
4146                }
4147                cells.push(
4148                    TableCellElement(
4149                        div()
4150                            .flex_basis(px(0.))
4151                            .flex_grow(CELL_GROW)
4152                            .min_w(px(column.min))
4153                            .max_w(px(column.max))
4154                            .overflow_hidden()
4155                            .when(align == ColumnumnAlign::Center, |this| this.text_center())
4156                            .when(align == ColumnumnAlign::Right, |this| this.text_right())
4157                            .px_3()
4158                            .py(px(7.))
4159                            .refine_style(&style.table_cell())
4160                            .children(content)
4161                            .into_any_element(),
4162                    )
4163                    .into_any_element(),
4164                );
4165                if trail > 0. || !is_last_col {
4166                    cells.push(
4167                        slack(trail)
4168                            .when(!is_last_col, |this| this.border_r_1())
4169                            .into_any_element(),
4170                    );
4171                }
4172            }
4173            rows.push(
4174                div()
4175                    .w_full()
4176                    .when(row_ix < row_count - 1, |this| this.border_b_1())
4177                    .border_color(style.border())
4178                    .flex()
4179                    .flex_row()
4180                    // The first row is the header, as everywhere else that
4181                    // reads a table (`table_data`, `to_markdown`). The
4182                    // refinement comes last so it can override the defaults.
4183                    .when(row_ix == 0, |this| {
4184                        this.bg(style.code_background())
4185                            .text_color(style.foreground())
4186                            .when_some(top_left, |this, radius| this.rounded_tl(radius))
4187                            .when_some(top_right, |this, radius| this.rounded_tr(radius))
4188                            .refine_style(&style.table_head())
4189                    })
4190                    .when(row_ix + 1 == row_count, |this| {
4191                        this.when_some(bottom_left, |this, radius| this.rounded_bl(radius))
4192                            .when_some(bottom_right, |this, radius| this.rounded_br(radius))
4193                    })
4194                    .children(cells)
4195                    .into_any_element(),
4196            );
4197        }
4198        rows
4199    }
4200
4201    pub(crate) fn render_block(
4202        &self,
4203        options: NodeRenderOptions,
4204        node_cx: &NodeContext,
4205        window: &mut Window,
4206        cx: &mut App,
4207    ) -> AnyElement {
4208        let mb = block_gap(&options, &node_cx.style);
4209
4210        match self {
4211            BlockNode::Root { children, .. } => div()
4212                .children(children.into_iter().enumerate().map(move |(ix, node)| {
4213                    // A nested root (an HTML block container) continues the
4214                    // flow around it: until it shows a block of its own, it
4215                    // stands where the container stands.
4216                    let (is_first, prev) = match flow_position(children, ix) {
4217                        (true, _) => (options.is_first, options.prev),
4218                        (false, prev) => (false, prev),
4219                    };
4220                    node.render_block(
4221                        NodeRenderOptions {
4222                            ix,
4223                            is_first,
4224                            prev,
4225                            is_last: options.is_last && ix + 1 == children.len(),
4226                            ..options
4227                        },
4228                        node_cx,
4229                        window,
4230                        cx,
4231                    )
4232                }))
4233                .into_any_element(),
4234            BlockNode::Paragraph(paragraph) => gapped(
4235                paragraph.render(
4236                    paragraph.span.map(|span| TextLeafKey::block(span.start)),
4237                    node_cx,
4238                    window,
4239                    cx,
4240                ),
4241                mb,
4242            ),
4243            BlockNode::Heading {
4244                level,
4245                children,
4246                span,
4247            } => {
4248                let (_, line_height, section_gap) = heading_metrics(*level);
4249                let refinement = node_cx.style.heading(*level);
4250                let text_size = heading_size(*level, node_cx, window);
4251                // The previous block already leaves a gap below itself, so
4252                // only the rest of the section gap is added here -- the two
4253                // collapse like CSS margins. A heading right after another one
4254                // belongs to the same section and stays close. Nested in a
4255                // list item, a heading sits flush with the item.
4256                let top = if options.is_first || options.depth > 0 {
4257                    px(0.)
4258                } else {
4259                    match options.prev {
4260                        PrevBlock::Heading(_) => text_size * 0.25,
4261                        PrevBlock::Rule => (text_size * section_gap
4262                            - rule_space(body_font_size(node_cx, window)))
4263                        .max(px(0.)),
4264                        PrevBlock::Other => (text_size * section_gap
4265                            - node_cx.style.paragraph_gap().to_pixels(window.rem_size()))
4266                        .max(px(0.)),
4267                    }
4268                };
4269                let bottom = if options.is_last {
4270                    px(0.)
4271                } else {
4272                    text_size * HEADING_BOTTOM_GAP
4273                };
4274
4275                div()
4276                    .pt(top)
4277                    .pb(bottom)
4278                    .whitespace_normal()
4279                    .text_size(text_size)
4280                    .line_height(relative(line_height))
4281                    .font_weight(FontWeight::SEMIBOLD)
4282                    .when(*level >= 6, |this| {
4283                        this.text_color(node_cx.style.muted_foreground())
4284                    })
4285                    .refine_style(&refinement)
4286                    .child(children.render(
4287                        span.map(|span| TextLeafKey::block(span.start)),
4288                        node_cx,
4289                        window,
4290                        cx,
4291                    ))
4292                    .into_any_element()
4293            }
4294            BlockNode::Blockquote { children, .. } => gapped(
4295                div()
4296                    .w_full()
4297                    .text_color(node_cx.style.muted_foreground())
4298                    .border_l_2()
4299                    .border_color(node_cx.style.border())
4300                    .pl(body_font_size(node_cx, window) * 0.9)
4301                    .refine_style(node_cx.style.blockquote_style())
4302                    // Nested tables measure while rendering, before this div applies its text style.
4303                    .children({
4304                        let children_len = children.len();
4305                        children.into_iter().enumerate().map(move |(index, c)| {
4306                            let is_last = index == children_len - 1;
4307                            let (is_first, prev) = flow_position(children, index);
4308                            window.with_text_style(
4309                                Some(node_cx.style.blockquote_style().text.clone()),
4310                                |window| {
4311                                    c.render_block(
4312                                        NodeRenderOptions {
4313                                            is_first,
4314                                            prev,
4315                                            ..options.is_last(is_last)
4316                                        },
4317                                        node_cx,
4318                                        window,
4319                                        cx,
4320                                    )
4321                                },
4322                            )
4323                        })
4324                    })
4325                    .into_any_element(),
4326                mb,
4327            ),
4328            BlockNode::List {
4329                children,
4330                ordered,
4331                start,
4332                ..
4333            } => div()
4334                .w_full()
4335                .min_w_0()
4336                .pb(mb)
4337                .children({
4338                    let mut items = Vec::with_capacity(children.len());
4339                    let mut item_index = 0;
4340                    for (ix, item) in children.into_iter().enumerate() {
4341                        let is_item = item.is_list_item();
4342
4343                        items.push(Self::render_list_item(
4344                            item,
4345                            item_index,
4346                            NodeRenderOptions {
4347                                ix,
4348                                ordered: *ordered,
4349                                list_start: *start,
4350                                ..options
4351                            },
4352                            node_cx,
4353                            window,
4354                            cx,
4355                        ));
4356
4357                        if is_item {
4358                            item_index += 1;
4359                        }
4360                    }
4361                    items
4362                })
4363                .into_any_element(),
4364            BlockNode::CodeBlock(code_block) => code_block.render(&options, node_cx, window, cx),
4365            BlockNode::Custom { node, state } => {
4366                let inner = match node_cx.markdown_extensions.render_block(node, window, cx) {
4367                    Some(rendered) => rendered,
4368                    None => div().child(node.as_text().to_string()).into_any_element(),
4369                };
4370
4371                div()
4372                    .pb(mb)
4373                    .child(CustomBlockElement {
4374                        content: inner,
4375                        state: state.clone(),
4376                    })
4377                    .into_any_element()
4378            }
4379            BlockNode::Table { .. } => {
4380                // Tables are data and read a step denser than the body. The
4381                // columns are measured while rendering, so they are measured
4382                // at the same size the cells are laid out with; the actions
4383                // row below keeps the body size.
4384                let text_size = body_font_size(node_cx, window) * (14. / 15.);
4385                window.with_text_style(
4386                    Some(TextStyleRefinement {
4387                        font_size: Some(text_size.into()),
4388                        ..Default::default()
4389                    }),
4390                    |window| {
4391                        Self::render_table(self, &options, node_cx, text_size, window, cx)
4392                            .into_any_element()
4393                    },
4394                )
4395            }
4396            BlockNode::HorizontalRule { .. } => {
4397                // A section break: a hairline with more room around it than
4398                // a paragraph gap. The previous block's own gap counts toward
4399                // the space above, as with headings.
4400                let space = rule_space(body_font_size(node_cx, window));
4401                let top = if options.is_first || options.depth > 0 {
4402                    px(0.)
4403                } else {
4404                    let above = match options.prev {
4405                        PrevBlock::Rule => space,
4406                        PrevBlock::Heading(level) => {
4407                            heading_size(level, node_cx, window) * HEADING_BOTTOM_GAP
4408                        }
4409                        PrevBlock::Other => {
4410                            node_cx.style.paragraph_gap().to_pixels(window.rem_size())
4411                        }
4412                    };
4413                    (space - above).max(px(0.))
4414                };
4415                div()
4416                    .pt(top)
4417                    .when(!options.is_last, |this| this.pb(space))
4418                    .child(div().bg(node_cx.style.border()).h(px(1.)))
4419                    .into_any_element()
4420            }
4421            BlockNode::Break { .. } => div().into_any_element(),
4422            BlockNode::Unknown { .. } | BlockNode::Definition { .. } => div().into_any_element(),
4423            _ => {
4424                if cfg!(debug_assertions) {
4425                    tracing::warn!("unknown implementation: {:?}", self);
4426                }
4427
4428                div().into_any_element()
4429            }
4430        }
4431    }
4432}
4433
4434#[cfg(test)]
4435mod tests {
4436    use super::*;
4437
4438    #[test]
4439    fn block_plugin_selection_copies_both_formats_and_clears_synchronously() {
4440        let source = "$$\nx\n$$";
4441        let mut node = MarkdownNode::new("math", ()).text("x").markdown(source);
4442        node.set_span(Some(Span {
4443            start: 0,
4444            end: source.len(),
4445        }));
4446        let state = BlockState::default();
4447        let same_content = BlockNode::Custom {
4448            node: node.clone(),
4449            state: BlockState::default(),
4450        };
4451        let block = BlockNode::Custom {
4452            node,
4453            state: state.clone(),
4454        };
4455        let document = crate::text::document::ParsedDocument {
4456            source: source.into(),
4457            blocks: Arc::new(vec![block.clone()]),
4458        };
4459        assert!(!block.has_selection());
4460        state.set_selection(Some(BlockSelection::Full));
4461        // Runtime selection does not affect parsed content equality.
4462        assert_eq!(block, same_content);
4463        for blocks in [None, Some(0..=0)] {
4464            assert_eq!(
4465                document.selected_text(SelectionFormat::Plain, blocks.clone()),
4466                "x\n"
4467            );
4468            assert_eq!(
4469                document.selected_text(SelectionFormat::Source, blocks),
4470                source
4471            );
4472        }
4473        assert_eq!(document.selected_source_range(), Some(0..source.len()));
4474
4475        // A clone shares the retained state, including clearing before repaint.
4476        block.clear_selection();
4477        assert!(!document.blocks[0].has_selection());
4478        assert_eq!(
4479            document.selected_text(SelectionFormat::Plain, Some(0..=0)),
4480            ""
4481        );
4482        assert_eq!(document.selected_source_range(), None);
4483    }
4484
4485    #[test]
4486    fn block_plugin_empty_endpoint_suppresses_virtualized_copy_fallback() {
4487        let state = BlockState::default();
4488        let block = BlockNode::Custom {
4489            node: MarkdownNode::new("math", ())
4490                .text("formula")
4491                .markdown("$$formula$$"),
4492            state: state.clone(),
4493        };
4494        let after = CodeBlock::from_code("after", None::<SharedString>);
4495        after.set_selection(0..5);
4496        let document = crate::text::document::ParsedDocument {
4497            blocks: Arc::new(vec![block, BlockNode::CodeBlock(after)]),
4498            ..Default::default()
4499        };
4500        assert!(
4501            document
4502                .selected_text(SelectionFormat::Plain, Some(0..=1))
4503                .contains("formula")
4504        );
4505        state.set_selection(Some(BlockSelection::Empty));
4506        assert!(document.blocks[0].has_selection());
4507        for format in [SelectionFormat::Plain, SelectionFormat::Source] {
4508            let text = document.selected_text(format, Some(0..=1));
4509            assert!(text.contains("after"));
4510            assert!(!text.contains("formula"));
4511        }
4512    }
4513
4514    #[test]
4515    fn block_plugin_geometry_selects_whole_blocks_in_both_drag_directions() {
4516        let bounds = Bounds::new(gpui::point(px(10.), px(20.)), gpui::size(px(100.), px(40.)));
4517        let inside = bounds.center();
4518        let above = gpui::point(px(0.), px(0.));
4519        let below = gpui::point(px(0.), px(80.));
4520        for (start, end) in [(above, inside), (inside, below), (above, below)] {
4521            assert!(custom_block_is_selected(bounds, start, end));
4522            assert!(custom_block_is_selected(bounds, end, start));
4523        }
4524        assert!(!custom_block_is_selected(bounds, inside, inside));
4525        // Pointer travel during a click, and any drag that never leaves the
4526        // block, select nothing.
4527        for (start, end) in [
4528            (inside, inside + gpui::point(px(1.), px(0.))),
4529            (
4530                bounds.origin,
4531                bounds.bottom_right() - gpui::point(px(1.), px(1.)),
4532            ),
4533        ] {
4534            assert!(!custom_block_is_selected(bounds, start, end));
4535            assert!(!custom_block_is_selected(bounds, end, start));
4536        }
4537        assert!(!custom_block_is_selected(
4538            bounds,
4539            gpui::point(px(150.), px(30.)),
4540            gpui::point(px(160.), px(40.))
4541        ));
4542    }
4543
4544    #[test]
4545    fn selected_inline_objects_coalesce_surrounding_emphasis() {
4546        for (object_mark, expected) in [
4547            (TextMark::default().italic(), "*fore $x$ aft*"),
4548            (TextMark::default().italic().bold(), "*fore **$x$** aft*"),
4549        ] {
4550            let italic = TextMark::default().italic();
4551            let before = InlineNode::new("before ").marks(vec![(0..7, italic.clone())]);
4552            let formula =
4553                InlineNode::custom(MarkdownNode::new("math", ()).text("x").markdown("$x$"))
4554                    .marks(vec![(0..1, object_mark)]);
4555            let after = InlineNode::new(" after").marks(vec![(0..6, italic)]);
4556            {
4557                let mut state = formula.state.lock().unwrap();
4558                state.text = "before ".into();
4559                state.selection = Some((2..7).into());
4560            }
4561            *formula.custom_selection.lock().unwrap() = true;
4562            let paragraph = Paragraph {
4563                children: vec![before, formula, after],
4564                ..Default::default()
4565            };
4566            {
4567                let mut state = paragraph.state.lock().unwrap();
4568                state.text = " after".into();
4569                state.selection = Some((0..4).into());
4570            }
4571            assert_eq!(paragraph.selected_source(), expected);
4572        }
4573    }
4574
4575    #[test]
4576    fn consecutive_inline_objects_copy_atomically_without_neighboring_text() {
4577        let first =
4578            InlineNode::custom(MarkdownNode::new("math", ()).text("甲²").markdown("$甲^2$"));
4579        let second = InlineNode::custom(MarkdownNode::new("math", ()).text("b").markdown("$b$"));
4580        *first.custom_selection.lock().unwrap() = true;
4581        *second.custom_selection.lock().unwrap() = true;
4582        let paragraph = Paragraph {
4583            children: vec![first, second],
4584            ..Default::default()
4585        };
4586        assert_eq!(paragraph.selected_text(), "甲²b");
4587        assert_eq!(paragraph.selected_source(), "$甲^2$$b$");
4588        assert!(paragraph.has_selection());
4589        paragraph.clear_selection();
4590        assert_eq!(paragraph.selected_text(), "");
4591        assert_eq!(paragraph.selected_source(), "");
4592        assert!(!paragraph.has_selection());
4593    }
4594
4595    #[test]
4596    fn selected_inline_object_interleaves_runs_and_preserves_enclosing_mark() {
4597        let before = InlineNode::new("中文 ");
4598        let formula =
4599            InlineNode::custom(MarkdownNode::new("math", ()).text("x²").markdown("$x^2$"))
4600                .marks(vec![(0..3, TextMark::default().bold())]);
4601        let after = InlineNode::new(" English");
4602        {
4603            let mut preceding = formula.state.lock().unwrap();
4604            preceding.text = "中文 ".into();
4605            preceding.selection = Some((3..7).into());
4606        }
4607        *formula.custom_selection.lock().unwrap() = true;
4608        let paragraph = Paragraph {
4609            children: vec![before, formula, after],
4610            ..Default::default()
4611        };
4612        {
4613            let mut trailing = paragraph.state.lock().unwrap();
4614            trailing.text = " English".into();
4615            trailing.selection = Some((0..4).into());
4616        }
4617        assert_eq!(paragraph.selected_text(), "文 x² Eng");
4618        assert_eq!(paragraph.selected_source(), "文 **$x^2$** Eng");
4619    }
4620
4621    #[test]
4622    fn custom_inline_inherits_marks_and_resolves_link_references() {
4623        use gpui::{Empty, TestApp};
4624        let mut app = TestApp::new();
4625        let mut window = app.open_window(|_, _| Empty);
4626        window.update(|_, _, cx| {
4627            cx.set_global(crate::Theme::default());
4628            let mut node_cx = NodeContext::default();
4629            let mut mark = TextMark::default().bold();
4630            mark.italic = true;
4631            mark.strikethrough = true;
4632            mark.link = Some(LinkMark {
4633                identifier: Some("ref".into()),
4634                ..Default::default()
4635            });
4636            node_cx.link_refs.insert(
4637                "ref".into(),
4638                LinkMark {
4639                    url: "https://example.com".into(),
4640                    ..Default::default()
4641                },
4642            );
4643            let paragraph = Paragraph {
4644                children: vec![
4645                    InlineNode::custom(MarkdownNode::new("test", ()).text("x"))
4646                        .marks(vec![(0..1, mark)]),
4647                ],
4648                ..Default::default()
4649            };
4650            let items = paragraph.inline_flow_items(None, &[], &[], &node_cx, cx);
4651            let InlineFlowItem::Object { style, link, .. } = &items[0] else {
4652                panic!()
4653            };
4654            assert_eq!(style.font_weight, Some(FontWeight::BOLD));
4655            assert_eq!(style.font_style, Some(FontStyle::Italic));
4656            assert!(style.strikethrough.is_some());
4657            assert_eq!(link.as_ref().unwrap().url.as_ref(), "https://example.com");
4658        });
4659    }
4660
4661    #[test]
4662    fn table_column_uses_prepared_inline_metrics_after_resource_update() {
4663        use crate::text::InlineElement;
4664        use crate::text::inline::test_draw::in_prepaint;
4665        use gpui::{Image, ImageFormat, TestApp};
4666        let mut app = TestApp::new();
4667        let width = Arc::new(std::sync::atomic::AtomicUsize::new(400));
4668        let render_width = width.clone();
4669        let mut node_cx = NodeContext::default();
4670        node_cx.markdown_extensions = Arc::new(MarkdownExtensions::default().plugin(
4671            crate::text::markdown_ext::TestInlinePlugin::new("test").render_with(
4672                move |_, _, _, _| {
4673                    Some(
4674                        InlineElement::new(
4675                            gpui::img(Arc::new(Image::from_bytes(
4676                                ImageFormat::Svg,
4677                                b"<svg/>".to_vec(),
4678                            )))
4679                            .w(px(
4680                                render_width.load(std::sync::atomic::Ordering::Relaxed) as f32
4681                            ))
4682                            .h(px(20.)),
4683                        )
4684                        .with_baseline(px(15.)),
4685                    )
4686                },
4687            ),
4688        ));
4689        let table = table_of(
4690            vec![
4691                vec![plain_cell("ordinary text")],
4692                vec![TableCell {
4693                    children: Paragraph {
4694                        children: vec![InlineNode::custom(MarkdownNode::new("test", ()).text("x"))],
4695                        ..Default::default()
4696                    },
4697                    width: None,
4698                }],
4699            ],
4700            vec![],
4701        );
4702        in_prepaint(&mut app, move |window, cx| {
4703            let mut cached = None;
4704            for expected in [400, 600, 10, 400] {
4705                width.store(expected, std::sync::atomic::Ordering::Relaxed);
4706                let measured = measure_table_columns(&table, 1, &node_cx, window, cx)[0];
4707                let current = cached_column_widths(&table);
4708                assert_eq!(
4709                    measured,
4710                    current.widths[0].max(expected as f32 + CELL_PAD_PX)
4711                );
4712                assert_eq!(current.custom_cells, vec![(1, 0)]);
4713                if let Some(cached) = cached.replace(current.clone()) {
4714                    assert!(Arc::ptr_eq(&cached, &current));
4715                }
4716            }
4717        });
4718    }
4719
4720    fn cached_column_widths(table: &Table) -> Arc<TableColumnWidths> {
4721        table.column_widths_cache.0.lock().unwrap().clone().unwrap()
4722    }
4723
4724    #[test]
4725    fn blockquote_typography_reaches_nested_table_measurement() {
4726        use crate::text::inline::test_draw::in_prepaint;
4727        use crate::text::inline::test_fonts::{BODY, MONO, WideMonoTextSystem};
4728        use gpui::TestApp;
4729
4730        let quote = BlockNode::Blockquote {
4731            children: vec![BlockNode::Table(table_of(
4732                vec![vec![plain_cell("0123456789")]],
4733                vec![],
4734            ))],
4735            span: None,
4736        };
4737        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
4738        in_prepaint(&mut app, move |window, cx| {
4739            cx.set_global(crate::Theme::default());
4740            window.with_text_style(
4741                Some(
4742                    StyleRefinement::default()
4743                        .font_family(BODY)
4744                        .text_size(px(15.))
4745                        .text,
4746                ),
4747                |window| {
4748                    for (refinement, expected_width) in [
4749                        (StyleRefinement::default(), 94.),
4750                        (
4751                            StyleRefinement::default()
4752                                .font_family(MONO)
4753                                .text_size(px(30.))
4754                                .font_weight(FontWeight::BOLD),
4755                            199.,
4756                        ),
4757                    ] {
4758                        let node_cx = NodeContext {
4759                            style: Arc::new(
4760                                TextViewStyle::default().with_blockquote_style(refinement),
4761                            ),
4762                            body_font_size: px(15.),
4763                            ..Default::default()
4764                        };
4765                        quote.render_block(NodeRenderOptions::default(), &node_cx, window, cx);
4766                        let BlockNode::Blockquote { children, .. } = &quote else {
4767                            unreachable!()
4768                        };
4769                        let BlockNode::Table(table) = &children[0] else {
4770                            unreachable!()
4771                        };
4772                        // Ten glyphs at the table's 14px size, plus 24px cell padding.
4773                        assert_eq!(cached_column_widths(table).widths, vec![expected_width]);
4774                    }
4775                },
4776            );
4777        });
4778    }
4779
4780    #[test]
4781    fn table_columns_cache_preserves_plain_widths_and_clones() {
4782        use crate::text::inline::test_draw::in_prepaint;
4783        use crate::text::inline::test_fonts::{BODY, WideMonoTextSystem, record_shaped_lines};
4784        use gpui::{Refineable as _, TestApp, TextStyle};
4785
4786        let long = "é界".repeat(2048);
4787        let table = table_of(
4788            vec![
4789                vec![plain_cell(&format!(" \t{long} \nshort\n ")), plain_cell("")],
4790                vec![plain_cell("short")],
4791                vec![],
4792                vec![plain_cell(""), plain_cell("a\nabcdefghijklmnop")],
4793            ],
4794            vec![],
4795        );
4796        let uncached = table.clone();
4797        let node_cx = NodeContext::default();
4798        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
4799        let (table, node_cx, cached) = in_prepaint(&mut app, move |window, cx| {
4800            window.set_rem_size(px(20.));
4801            let style = TextStyle {
4802                font_family: BODY.into(),
4803                font_size: rems(1.).into(),
4804                ..Default::default()
4805            };
4806            let cached =
4807                window.with_text_style(Some(style.subtract(&Default::default())), |window| {
4808                    let (widths, shaped) = record_shaped_lines(|| {
4809                        measure_table_columns(&table, 2, &node_cx, window, cx)
4810                    });
4811                    assert_eq!(widths, vec![40960. + CELL_PAD_PX + CELL_BORDER_PX, 184.]);
4812                    assert!(shaped.iter().any(|line| line == &long));
4813                    assert_eq!(
4814                        table, uncached,
4815                        "derived widths do not change table equality"
4816                    );
4817                    assert!(uncached.column_widths_cache.0.lock().unwrap().is_none());
4818
4819                    let cached = cached_column_widths(&table);
4820                    let cloned = table.clone();
4821                    for same in [&table, &cloned] {
4822                        let (again, shaped) = record_shaped_lines(|| {
4823                            measure_table_columns(same, 2, &node_cx, window, cx)
4824                        });
4825                        assert_eq!(again, widths);
4826                        assert!(shaped.is_empty());
4827                        assert!(Arc::ptr_eq(&cached, &cached_column_widths(same)));
4828                    }
4829
4830                    // A different column count changes which cells draw borders.
4831                    assert_eq!(
4832                        measure_table_columns(&cloned, 3, &node_cx, window, cx),
4833                        vec![widths[0], widths[1] + CELL_BORDER_PX, CELL_MIN_PX]
4834                    );
4835                    assert!(!Arc::ptr_eq(&cached, &cached_column_widths(&cloned)));
4836                    assert!(Arc::ptr_eq(&cached, &cached_column_widths(&table)));
4837                    assert!(
4838                        measure_table_columns(&Table::default(), 0, &node_cx, window, cx)
4839                            .is_empty()
4840                    );
4841                    cached
4842                });
4843            (table, node_cx, cached)
4844        });
4845        // A parsed table can be rendered in another window. Do not reuse a
4846        // pixel-width cache from a different window's text system.
4847        in_prepaint(&mut app, move |window, cx| {
4848            window.set_rem_size(cached.rem_size);
4849            window.with_text_style(
4850                Some(cached.text_styles.body.subtract(&Default::default())),
4851                |window| {
4852                    measure_table_columns(&table, 2, &node_cx, window, cx);
4853                    assert!(!Arc::ptr_eq(&cached, &cached_column_widths(&table)));
4854                },
4855            );
4856        });
4857    }
4858
4859    #[test]
4860    fn table_columns_cache_tracks_typography() {
4861        use crate::text::inline::test_draw::in_prepaint;
4862        use crate::text::inline::test_fonts::{BODY, MONO, WideMonoTextSystem};
4863        use gpui::{Refineable as _, TestApp, TextStyle};
4864
4865        let code = "0123456789";
4866        let mut paragraph = Paragraph::default();
4867        paragraph
4868            .push(InlineNode::new(code).marks(vec![(0..code.len(), TextMark::default().code())]));
4869        let table = table_of(
4870            vec![vec![
4871                plain_cell(code),
4872                TableCell {
4873                    children: paragraph,
4874                    width: None,
4875                },
4876            ]],
4877            vec![],
4878        );
4879        let base = TextStyle {
4880            font_family: BODY.into(),
4881            font_size: rems(1.).into(),
4882            ..Default::default()
4883        };
4884        let mut larger = base.clone();
4885        larger.font_size = rems(2.).into();
4886        let mut bold = base.clone();
4887        bold.font_weight = FontWeight::BOLD;
4888        let mut mono = base.clone();
4889        mono.font_family = MONO.into();
4890        let normal_code = HighlightStyle::default();
4891        let bold_code = HighlightStyle {
4892            font_weight: Some(FontWeight::BOLD),
4893            ..Default::default()
4894        };
4895        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
4896        in_prepaint(&mut app, move |window, cx| {
4897            let mut previous = None;
4898            for (style, rem_size, family, inline_code, expected) in [
4899                (base.clone(), 20., MONO, normal_code, [125., 207.]),
4900                (base.clone(), 30., MONO, normal_code, [175., 294.5]),
4901                (larger, 20., MONO, normal_code, [225., 382.]),
4902                (bold, 20., MONO, normal_code, [175., 250.75]),
4903                (mono, 20., MONO, normal_code, [225., 207.]),
4904                (base.clone(), 20., MONO, normal_code, [125., 207.]),
4905                (base.clone(), 20., BODY, normal_code, [125., 119.5]),
4906                (base.clone(), 20., MONO, normal_code, [125., 207.]),
4907                (base, 20., MONO, bold_code, [125., 250.75]),
4908            ] {
4909                let mut theme = crate::Theme::default();
4910                theme.tokens.typography.mono = family.into();
4911                cx.set_global(theme);
4912                window.set_rem_size(px(rem_size));
4913                let node_cx = NodeContext {
4914                    style: Arc::new(TextViewStyle::default().with_inline_code(inline_code)),
4915                    ..Default::default()
4916                };
4917                window.with_text_style(Some(style.subtract(&Default::default())), |window| {
4918                    let widths = measure_table_columns(&table, 2, &node_cx, window, cx);
4919                    for (width, expected) in widths.iter().zip(expected) {
4920                        assert!((width - expected).abs() < 0.01, "{width} != {expected}");
4921                    }
4922                    let cached = cached_column_widths(&table);
4923                    if let Some(previous) = previous.replace(cached.clone()) {
4924                        assert!(!Arc::ptr_eq(&previous, &cached));
4925                    }
4926                    assert_eq!(
4927                        measure_table_columns(&table, 2, &node_cx, window, cx),
4928                        widths
4929                    );
4930                    assert!(Arc::ptr_eq(&cached, &cached_column_widths(&table)));
4931                });
4932            }
4933        });
4934    }
4935
4936    #[test]
4937    fn table_columns_cache_is_rebuilt_for_reparsed_tables() {
4938        use crate::text::format::markdown;
4939        use crate::text::inline::test_draw::in_prepaint;
4940        use crate::text::inline::test_fonts::{BODY, WideMonoTextSystem};
4941        use gpui::{Refineable as _, TestApp, TextStyle};
4942
4943        let mut node_cx = NodeContext::default();
4944        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
4945        in_prepaint(&mut app, move |window, cx| {
4946            let style = TextStyle {
4947                font_family: BODY.into(),
4948                font_size: px(20.).into(),
4949                ..Default::default()
4950            };
4951            window.with_text_style(Some(style.subtract(&Default::default())), |window| {
4952                let mut previous = None;
4953                for (text, expected) in [("longer cell", 134.), ("x", CELL_MIN_PX)] {
4954                    let source = format!("| h |\n| - |\n| {text} |\n");
4955                    let document = markdown::parse(&source, &mut node_cx).unwrap();
4956                    let BlockNode::Table(table) = &document.blocks[0] else {
4957                        panic!("expected a parsed table");
4958                    };
4959                    assert!(table.column_widths_cache.0.lock().unwrap().is_none());
4960                    assert_eq!(
4961                        measure_table_columns(table, 1, &node_cx, window, cx),
4962                        vec![expected]
4963                    );
4964                    let cached = cached_column_widths(table);
4965                    if let Some(previous) = previous.replace(cached.clone()) {
4966                        assert!(!Arc::ptr_eq(&previous, &cached));
4967                    }
4968                    assert_eq!(document.source.as_ref(), source);
4969                }
4970            });
4971        });
4972    }
4973
4974    /// Table columns are sized from shaped text, so a column of inline code
4975    /// has to be measured in the code family. Measured in the body font, the
4976    /// wide-mono test font makes `col_w` come out at half the rendered width.
4977    #[test]
4978    fn table_column_of_inline_code_cells_fits_the_mono_width() {
4979        use crate::text::inline::test_fonts::{MONO, WideMonoTextSystem};
4980        use gpui::{Empty, TestApp};
4981
4982        let code = "method_name()";
4983        let mut paragraph = Paragraph::default();
4984        paragraph
4985            .push(InlineNode::new(code).marks(vec![(0..code.len(), TextMark::default().code())]));
4986        let table = Table {
4987            children: vec![TableRow {
4988                children: vec![TableCell {
4989                    children: paragraph,
4990                    width: None,
4991                }],
4992            }],
4993            column_aligns: vec![],
4994            span: None,
4995            table_data_cache: TableDataCache::default(),
4996            column_widths_cache: TableColumnWidthsCache::default(),
4997        };
4998        let node_cx = NodeContext::default();
4999
5000        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
5001        let mut window = app.open_window(|_, _| Empty);
5002        let (col_w, font_size) = window.update(|_, window, cx| {
5003            let mut theme = crate::Theme::default();
5004            theme.tokens.typography.mono = MONO.into();
5005            cx.set_global(theme);
5006            let font_size = window.text_style().font_size.to_pixels(window.rem_size());
5007            (
5008                measure_table_columns(&table, 1, &node_cx, window, cx),
5009                font_size,
5010            )
5011        });
5012
5013        let mono_w = f32::from(WideMonoTextSystem::width_of(code, MONO, font_size * 0.875));
5014        assert!(
5015            (col_w[0]
5016                - (mono_w + CELL_PAD_PX + crate::text::inline_flow::INLINE_CODE_PADDING * 2.))
5017                .abs()
5018                < 0.01,
5019            "col_w {} must fit the mono width {} plus padding {}",
5020            col_w[0],
5021            mono_w,
5022            CELL_PAD_PX
5023        );
5024    }
5025
5026    #[test]
5027    fn code_block_highlights_are_cached_by_highlighter_identity() {
5028        use std::sync::atomic::{AtomicUsize, Ordering};
5029
5030        let calls = Arc::new(AtomicUsize::new(0));
5031        let calls_for_highlighter = calls.clone();
5032        let highlighter: Arc<CodeBlockHighlighterFn> = Arc::new(move |_| {
5033            calls_for_highlighter.fetch_add(1, Ordering::Relaxed);
5034            Vec::new()
5035        });
5036        let block = CodeBlock::new("fn main() {}".into(), Some("rust".into()), None::<Span>);
5037
5038        block.highlighted_styles(&highlighter);
5039        block.highlighted_styles(&highlighter);
5040        assert_eq!(calls.load(Ordering::Relaxed), 1);
5041
5042        let replacement: Arc<CodeBlockHighlighterFn> = Arc::new(|_| Vec::new());
5043        block.highlighted_styles(&replacement);
5044        assert!(Arc::ptr_eq(
5045            &block
5046                .highlight_cache
5047                .lock()
5048                .unwrap()
5049                .as_ref()
5050                .unwrap()
5051                .highlighter,
5052            &replacement
5053        ));
5054    }
5055
5056    #[test]
5057    fn a_new_highlighter_replaces_styles_instead_of_reusing_the_cache() {
5058        // Swapping the highlighter is how a theme change reaches a code block:
5059        // the parsed document is untouched, so the styles must come from the
5060        // new highlighter rather than from the styles cached for the old one.
5061        let light: Arc<CodeBlockHighlighterFn> = Arc::new(|_| {
5062            vec![(
5063                0..2,
5064                HighlightStyle {
5065                    color: Some(gpui::rgb(0x0000ff).into()),
5066                    ..Default::default()
5067                },
5068            )]
5069        });
5070        let dark: Arc<CodeBlockHighlighterFn> = Arc::new(|_| {
5071            vec![(
5072                0..2,
5073                HighlightStyle {
5074                    color: Some(gpui::rgb(0xffff00).into()),
5075                    ..Default::default()
5076                },
5077            )]
5078        });
5079        let block = CodeBlock::from_code("42", Some("json"));
5080
5081        let light_styles = block.highlighted_styles(&light);
5082        let dark_styles = block.highlighted_styles(&dark);
5083
5084        assert_eq!(light_styles[0].1.color, Some(gpui::rgb(0x0000ff).into()));
5085        assert_eq!(dark_styles[0].1.color, Some(gpui::rgb(0xffff00).into()));
5086        assert_eq!(block.code(), "42", "the document must survive the swap");
5087    }
5088
5089    #[test]
5090    fn reconstruct_markdown_wraps_marked_runs() {
5091        // "bold" fully covered by a bold mark.
5092        let marks = vec![(0..4, TextMark::default().bold())];
5093        assert_eq!(reconstruct_markdown("bold", &marks, 0..4), "**bold**");
5094        // Partial selection inside the bold run still wraps the slice.
5095        assert_eq!(reconstruct_markdown("bold", &marks, 1..3), "**ol**");
5096    }
5097
5098    #[test]
5099    fn reconstruct_markdown_emits_unmarked_text_verbatim() {
5100        // "a b c": plain, code, plain across three runs concatenated.
5101        let text = "a b c";
5102        let marks = vec![(2..3, TextMark::default().code())];
5103        assert_eq!(reconstruct_markdown(text, &marks, 0..5), "a `b` c");
5104        // Selecting only the plain tail.
5105        assert_eq!(reconstruct_markdown(text, &marks, 3..5), " c");
5106    }
5107
5108    #[test]
5109    fn reconstruct_markdown_handles_code_italic_strike_link() {
5110        assert_eq!(
5111            reconstruct_markdown("x", &[(0..1, TextMark::default().code())], 0..1),
5112            "`x`"
5113        );
5114        assert_eq!(
5115            reconstruct_markdown("x", &[(0..1, TextMark::default().italic())], 0..1),
5116            "*x*"
5117        );
5118        assert_eq!(
5119            reconstruct_markdown("x", &[(0..1, TextMark::default().strikethrough())], 0..1),
5120            "~~x~~"
5121        );
5122        let link = TextMark::default().link(LinkMark {
5123            url: "https://example.com".into(),
5124            ..Default::default()
5125        });
5126        assert_eq!(
5127            reconstruct_markdown("x", &[(0..1, link)], 0..1),
5128            "[x](https://example.com)"
5129        );
5130    }
5131
5132    #[test]
5133    fn reconstruct_markdown_nested_bold_italic() {
5134        // A single run marked both bold and italic (as produced by `**_x_**`).
5135        let mark = TextMark::default().bold().italic();
5136        // Inner (italic) is applied first, then bold: `***x***`.
5137        assert_eq!(reconstruct_markdown("x", &[(0..1, mark)], 0..1), "***x***");
5138    }
5139
5140    /// Build a paragraph whose combined `state.text` is the concatenation of
5141    /// its children (mirroring `Paragraph::render`), then set the paragraph
5142    /// selection so `selected_source` can be exercised without a real paint.
5143    fn paragraph_with_children(children: Vec<InlineNode>) -> Paragraph {
5144        let combined: String = children.iter().map(|c| c.text.to_string()).collect();
5145        let paragraph = Paragraph {
5146            span: None,
5147            children,
5148            link_refs: HashMap::new(),
5149            state: Arc::new(Mutex::new(InlineState::default())),
5150            render_cache: ParagraphRenderCache::default(),
5151        };
5152        if let Ok(mut state) = paragraph.state.lock() {
5153            state.set_text(combined.into());
5154        }
5155        paragraph
5156    }
5157
5158    fn set_paragraph_selection(paragraph: &Paragraph, range: Range<usize>) {
5159        if let Ok(mut state) = paragraph.state.lock() {
5160            state.selection = Some(range.into());
5161        }
5162    }
5163
5164    #[test]
5165    fn paragraph_selected_source_maps_partial_selection_across_runs() {
5166        // "This has **bold** text." rendered as ["This has ", "bold", " text."].
5167        let children = vec![
5168            InlineNode::new("This has ").marks(vec![(0..9, TextMark::default())]),
5169            InlineNode::new("bold").marks(vec![(0..4, TextMark::default().bold())]),
5170            InlineNode::new(" text.").marks(vec![(0..6, TextMark::default())]),
5171        ];
5172        let paragraph = paragraph_with_children(children);
5173
5174        // Select the whole paragraph: "This has bold text." -> source with **.
5175        set_paragraph_selection(&paragraph, 0..(9 + 4 + 6));
5176        assert_eq!(paragraph.selected_source(), "This has **bold** text.");
5177
5178        // Select only across the boundary "has **bold** te".
5179        // Rendered offsets: "has " starts at 5, "bold" at 9..13, " te" 13..16.
5180        set_paragraph_selection(&paragraph, 5..16);
5181        assert_eq!(paragraph.selected_source(), "has **bold** te");
5182
5183        // Select entirely inside the bold run -> still wrapped.
5184        set_paragraph_selection(&paragraph, 10..12);
5185        assert_eq!(paragraph.selected_source(), "**ol**");
5186    }
5187
5188    #[test]
5189    fn paragraph_selected_source_matches_text_when_no_marks() {
5190        let children =
5191            vec![InlineNode::new("plain words").marks(vec![(0..11, TextMark::default())])];
5192        let paragraph = paragraph_with_children(children);
5193        set_paragraph_selection(&paragraph, 0..11);
5194        assert_eq!(paragraph.selected_source(), "plain words");
5195        assert_eq!(paragraph.selected_text(), "plain words");
5196    }
5197
5198    /// Copy `blocks` the way a view in [`SelectionFormat::SourceAcrossStyles`]
5199    /// does, with every block painted.
5200    fn copy_across_styles(blocks: Vec<BlockNode>) -> String {
5201        let document = crate::text::document::ParsedDocument {
5202            blocks: blocks.into(),
5203            ..Default::default()
5204        };
5205        document.selected_text(SelectionFormat::SourceAcrossStyles, None)
5206    }
5207
5208    #[test]
5209    fn source_across_styles_copies_plain_text_within_one_style() {
5210        // "This has **bold** text." rendered as ["This has ", "bold", " text."].
5211        let paragraph = || {
5212            paragraph_with_children(vec![
5213                InlineNode::new("This has ").marks(vec![(0..9, TextMark::default())]),
5214                InlineNode::new("bold").marks(vec![(0..4, TextMark::default().bold())]),
5215                InlineNode::new(" text.").marks(vec![(0..6, TextMark::default())]),
5216            ])
5217        };
5218        let copy = |range: Range<usize>| {
5219            let paragraph = paragraph();
5220            set_paragraph_selection(&paragraph, range);
5221            copy_across_styles(vec![BlockNode::Paragraph(paragraph)])
5222        };
5223
5224        // Inside the bold run, and up to both of its boundaries.
5225        assert_eq!(copy(10..12), "ol\n");
5226        assert_eq!(copy(9..13), "bold\n");
5227        // Inside the plain text before it, up to the boundary.
5228        assert_eq!(copy(0..9), "This has \n");
5229        // Across the boundary, the selection takes in two styles.
5230        assert_eq!(copy(5..16), "has **bold** te");
5231        assert_eq!(copy(8..10), " **b**");
5232    }
5233
5234    #[test]
5235    fn source_across_styles_drops_the_url_of_a_link_selected_alone() {
5236        let link = TextMark::default().link(LinkMark {
5237            url: "https://example.com".into(),
5238            ..Default::default()
5239        });
5240        let paragraph = paragraph_with_children(vec![
5241            InlineNode::new("see ").marks(vec![(0..4, TextMark::default())]),
5242            InlineNode::new("the docs").marks(vec![(0..8, link)]),
5243        ]);
5244
5245        set_paragraph_selection(&paragraph, 4..12);
5246        assert_eq!(
5247            copy_across_styles(vec![BlockNode::Paragraph(paragraph.clone())]),
5248            "the docs\n"
5249        );
5250
5251        set_paragraph_selection(&paragraph, 0..12);
5252        assert_eq!(
5253            copy_across_styles(vec![BlockNode::Paragraph(paragraph)]),
5254            "see [the docs](https://example.com)"
5255        );
5256    }
5257
5258    #[test]
5259    fn source_across_styles_drops_the_markup_of_a_single_block() {
5260        let heading = BlockNode::Heading {
5261            level: 2,
5262            children: selected_paragraph("Title"),
5263            span: None,
5264        };
5265        assert_eq!(copy_across_styles(vec![heading]), "Title\n");
5266
5267        let code = CodeBlock::from_code("let a = 1;\nlet b = 2;", Some("rust"));
5268        code.set_selection(0..10);
5269        assert_eq!(
5270            copy_across_styles(vec![BlockNode::CodeBlock(code)]),
5271            "let a = 1;\n"
5272        );
5273
5274        let item = |text: &str| BlockNode::ListItem {
5275            children: vec![BlockNode::Paragraph(selected_paragraph(text))],
5276            spread: false,
5277            checked: None,
5278            span: None,
5279        };
5280        let unselected_item = BlockNode::ListItem {
5281            children: vec![BlockNode::Paragraph(paragraph_with_children(vec![
5282                InlineNode::new("two"),
5283            ]))],
5284            spread: false,
5285            checked: None,
5286            span: None,
5287        };
5288        let list = BlockNode::List {
5289            ordered: false,
5290            start: None,
5291            span: None,
5292            children: vec![item("one"), unselected_item],
5293        };
5294        assert_eq!(copy_across_styles(vec![list]), "one\n");
5295    }
5296
5297    #[test]
5298    fn source_across_styles_keeps_the_source_across_blocks() {
5299        let item = |text: &str| BlockNode::ListItem {
5300            children: vec![BlockNode::Paragraph(selected_paragraph(text))],
5301            spread: false,
5302            checked: None,
5303            span: None,
5304        };
5305        let list = BlockNode::List {
5306            ordered: false,
5307            start: None,
5308            span: None,
5309            children: vec![item("one"), item("two")],
5310        };
5311        assert_eq!(copy_across_styles(vec![list]), "- one\n- two");
5312
5313        assert_eq!(
5314            copy_across_styles(vec![
5315                BlockNode::Paragraph(selected_paragraph("start")),
5316                BlockNode::Paragraph(selected_paragraph("end")),
5317            ]),
5318            "start\n\nend"
5319        );
5320    }
5321
5322    #[test]
5323    fn source_across_styles_keeps_the_source_of_an_inline_object() {
5324        let formula = InlineNode::custom(MarkdownNode::new("math", ()).text("x").markdown("$x$"));
5325        *formula.custom_selection.lock().unwrap() = true;
5326        let paragraph = Paragraph {
5327            children: vec![formula],
5328            ..Default::default()
5329        };
5330        assert_eq!(
5331            copy_across_styles(vec![BlockNode::Paragraph(paragraph)]),
5332            "$x$"
5333        );
5334    }
5335
5336    fn selected_paragraph(text: &str) -> Paragraph {
5337        let len = text.len();
5338        let paragraph = paragraph_with_children(vec![
5339            InlineNode::new(text).marks(vec![(0..len, TextMark::default())]),
5340        ]);
5341        set_paragraph_selection(&paragraph, 0..len);
5342        paragraph
5343    }
5344
5345    #[test]
5346    fn heading_selected_source_prefixes_hashes() {
5347        let heading = BlockNode::Heading {
5348            level: 2,
5349            children: selected_paragraph("Title"),
5350            span: None,
5351        };
5352        assert_eq!(heading.selected_text(SelectionFormat::Source), "## Title\n");
5353        // Rendered text keeps no marker.
5354        assert_eq!(heading.selected_text(SelectionFormat::Plain), "Title\n");
5355    }
5356
5357    #[test]
5358    fn unordered_list_selected_source_prefixes_dash() {
5359        let list = BlockNode::List {
5360            ordered: false,
5361            start: None,
5362            span: None,
5363            children: vec![
5364                BlockNode::ListItem {
5365                    children: vec![BlockNode::Paragraph(selected_paragraph("one"))],
5366                    spread: false,
5367                    checked: None,
5368                    span: None,
5369                },
5370                BlockNode::ListItem {
5371                    children: vec![BlockNode::Paragraph(selected_paragraph("two"))],
5372                    spread: false,
5373                    checked: None,
5374                    span: None,
5375                },
5376            ],
5377        };
5378        assert_eq!(
5379            list.selected_text(SelectionFormat::Source),
5380            "- one\n- two\n"
5381        );
5382    }
5383
5384    #[test]
5385    fn ordered_list_selected_source_preserves_start() {
5386        let list = BlockNode::List {
5387            ordered: true,
5388            start: Some(3),
5389            span: None,
5390            children: vec![
5391                BlockNode::ListItem {
5392                    children: vec![BlockNode::Paragraph(selected_paragraph("three"))],
5393                    spread: false,
5394                    checked: None,
5395                    span: None,
5396                },
5397                BlockNode::ListItem {
5398                    children: vec![BlockNode::Paragraph(selected_paragraph("four"))],
5399                    spread: false,
5400                    checked: None,
5401                    span: None,
5402                },
5403            ],
5404        };
5405        assert_eq!(list.to_markdown(), "3. three\n4. four");
5406        assert_eq!(
5407            list.selected_text(SelectionFormat::Source),
5408            "3. three\n4. four\n"
5409        );
5410    }
5411
5412    #[test]
5413    fn ordered_list_selected_source_preserves_nested_starts_and_continuations() {
5414        let nested = BlockNode::List {
5415            ordered: true,
5416            start: Some(4),
5417            span: None,
5418            children: vec![
5419                BlockNode::ListItem {
5420                    children: vec![BlockNode::Paragraph(selected_paragraph("nested"))],
5421                    spread: false,
5422                    checked: None,
5423                    span: None,
5424                },
5425                BlockNode::ListItem {
5426                    children: vec![BlockNode::Paragraph(selected_paragraph("again"))],
5427                    spread: false,
5428                    checked: None,
5429                    span: None,
5430                },
5431            ],
5432        };
5433        let nested_list = BlockNode::List {
5434            ordered: true,
5435            start: Some(3),
5436            span: None,
5437            children: vec![
5438                BlockNode::ListItem {
5439                    children: vec![BlockNode::Paragraph(selected_paragraph("three")), nested],
5440                    spread: false,
5441                    checked: None,
5442                    span: None,
5443                },
5444                BlockNode::ListItem {
5445                    children: vec![BlockNode::Paragraph(selected_paragraph("four"))],
5446                    spread: false,
5447                    checked: None,
5448                    span: None,
5449                },
5450            ],
5451        };
5452        assert_eq!(
5453            nested_list.selected_text(SelectionFormat::Source),
5454            "3. three\n   4. nested\n   5. again\n4. four\n"
5455        );
5456
5457        let loose_list = BlockNode::List {
5458            ordered: true,
5459            start: Some(3),
5460            span: None,
5461            children: vec![
5462                BlockNode::ListItem {
5463                    children: vec![
5464                        BlockNode::Paragraph(selected_paragraph("loose")),
5465                        BlockNode::Paragraph(selected_paragraph("continuation")),
5466                    ],
5467                    spread: true,
5468                    checked: None,
5469                    span: None,
5470                },
5471                BlockNode::ListItem {
5472                    children: vec![BlockNode::Paragraph(selected_paragraph("four"))],
5473                    spread: false,
5474                    checked: None,
5475                    span: None,
5476                },
5477            ],
5478        };
5479        assert_eq!(
5480            loose_list.selected_text(SelectionFormat::Source),
5481            "3. loose\n   continuation\n4. four\n"
5482        );
5483    }
5484
5485    #[test]
5486    fn nested_list_selected_source_indents_sublists() {
5487        // - one
5488        //   - nested
5489        // - two
5490        let nested = BlockNode::List {
5491            ordered: false,
5492            start: None,
5493            span: None,
5494            children: vec![BlockNode::ListItem {
5495                children: vec![BlockNode::Paragraph(selected_paragraph("nested"))],
5496                spread: false,
5497                checked: None,
5498                span: None,
5499            }],
5500        };
5501        let list = BlockNode::List {
5502            ordered: false,
5503            start: None,
5504            span: None,
5505            children: vec![
5506                BlockNode::ListItem {
5507                    children: vec![BlockNode::Paragraph(selected_paragraph("one")), nested],
5508                    spread: false,
5509                    checked: None,
5510                    span: None,
5511                },
5512                BlockNode::ListItem {
5513                    children: vec![BlockNode::Paragraph(selected_paragraph("two"))],
5514                    spread: false,
5515                    checked: None,
5516                    span: None,
5517                },
5518            ],
5519        };
5520        assert_eq!(
5521            list.selected_text(SelectionFormat::Source),
5522            "- one\n  - nested\n- two\n"
5523        );
5524    }
5525
5526    #[test]
5527    fn task_list_selected_source_restores_checkboxes() {
5528        let list = BlockNode::List {
5529            ordered: false,
5530            start: None,
5531            span: None,
5532            children: vec![
5533                BlockNode::ListItem {
5534                    children: vec![BlockNode::Paragraph(selected_paragraph("done"))],
5535                    spread: false,
5536                    checked: Some(true),
5537                    span: None,
5538                },
5539                BlockNode::ListItem {
5540                    children: vec![BlockNode::Paragraph(selected_paragraph("todo"))],
5541                    spread: false,
5542                    checked: Some(false),
5543                    span: None,
5544                },
5545            ],
5546        };
5547        assert_eq!(
5548            list.selected_text(SelectionFormat::Source),
5549            "- [x] done\n- [ ] todo\n"
5550        );
5551    }
5552
5553    #[test]
5554    fn blockquote_selected_source_prefixes_gt() {
5555        let quote = BlockNode::Blockquote {
5556            span: None,
5557            children: vec![BlockNode::Paragraph(selected_paragraph("quoted text"))],
5558        };
5559        assert_eq!(
5560            quote.selected_text(SelectionFormat::Source),
5561            "> quoted text\n"
5562        );
5563    }
5564
5565    #[test]
5566    fn table_selected_source_pipes_cells_with_alignment_row() {
5567        let cell = |text: &str| TableCell {
5568            children: selected_paragraph(text),
5569            width: None,
5570        };
5571        let table = Table {
5572            children: vec![
5573                TableRow {
5574                    children: vec![cell("Name"), cell("Age")],
5575                },
5576                TableRow {
5577                    children: vec![cell("Alice"), cell("30")],
5578                },
5579            ],
5580            column_aligns: vec![ColumnumnAlign::Left, ColumnumnAlign::Right],
5581            span: None,
5582            table_data_cache: TableDataCache::default(),
5583            column_widths_cache: TableColumnWidthsCache::default(),
5584        };
5585        let block = BlockNode::Table(table);
5586        assert_eq!(
5587            block.selected_text(SelectionFormat::Source),
5588            "| Name | Age |\n| :-- | --: |\n| Alice | 30 |\n"
5589        );
5590    }
5591
5592    /// A drag that starts in a table cell stays in it, so the copy is the
5593    /// cell's text alone: no pipes, no delimiter row, no other cells or rows.
5594    #[test]
5595    fn a_selection_in_one_table_cell_copies_only_that_cell() {
5596        let table = table_of(
5597            vec![
5598                vec![plain_cell("Name"), plain_cell("Age")],
5599                vec![
5600                    TableCell {
5601                        children: selected_paragraph("Alice"),
5602                        width: None,
5603                    },
5604                    plain_cell("30"),
5605                ],
5606            ],
5607            vec![ColumnumnAlign::Left, ColumnumnAlign::Right],
5608        );
5609        let block = BlockNode::Table(table);
5610        assert_eq!(block.selected_text(SelectionFormat::Plain).trim(), "Alice");
5611        assert_eq!(block.selected_text(SelectionFormat::Source), "Alice\n");
5612    }
5613
5614    /// A cell holding plain text, as `Table::to_markdown` and
5615    /// `Table::table_data` see it (neither needs a selection).
5616    fn plain_cell(text: &str) -> TableCell {
5617        TableCell {
5618            children: Paragraph::new(text.to_string()),
5619            width: None,
5620        }
5621    }
5622
5623    fn table_of(rows: Vec<Vec<TableCell>>, column_aligns: Vec<ColumnumnAlign>) -> Table {
5624        Table {
5625            children: rows
5626                .into_iter()
5627                .map(|children| TableRow { children })
5628                .collect(),
5629            column_aligns,
5630            span: None,
5631            table_data_cache: TableDataCache::default(),
5632            column_widths_cache: TableColumnWidthsCache::default(),
5633        }
5634    }
5635
5636    #[test]
5637    fn table_to_markdown_pipes_cells_with_alignment_row() {
5638        let table = table_of(
5639            vec![
5640                vec![plain_cell("Name"), plain_cell("Age"), plain_cell("Score")],
5641                vec![plain_cell("Alice"), plain_cell("30"), plain_cell("9.5")],
5642            ],
5643            vec![
5644                ColumnumnAlign::Left,
5645                ColumnumnAlign::Center,
5646                ColumnumnAlign::Right,
5647            ],
5648        );
5649
5650        assert_eq!(
5651            table.to_markdown(),
5652            "| Name | Age | Score |\n| :-- | :-: | --: |\n| Alice | 30 | 9.5 |"
5653        );
5654        // The block arm delegates to it.
5655        assert_eq!(
5656            BlockNode::Table(table.clone()).to_markdown(),
5657            table.to_markdown()
5658        );
5659    }
5660
5661    #[test]
5662    fn table_to_markdown_keeps_outer_pipes_for_a_single_column() {
5663        let table = table_of(
5664            vec![vec![plain_cell("Symbol")], vec![plain_cell("TSLA.US")]],
5665            vec![ColumnumnAlign::Left],
5666        );
5667
5668        assert_eq!(table.to_markdown(), "| Symbol |\n| :-- |\n| TSLA.US |");
5669    }
5670
5671    #[test]
5672    fn table_to_markdown_escapes_pipes_and_keeps_inline_marks() {
5673        let bold = TableCell {
5674            children: paragraph_with_children(vec![
5675                InlineNode::new("bold").marks(vec![(0..4, TextMark::default().bold())]),
5676            ]),
5677            width: None,
5678        };
5679        let table = table_of(
5680            vec![
5681                vec![plain_cell("a | b"), plain_cell("plain")],
5682                vec![plain_cell("c"), bold],
5683            ],
5684            vec![ColumnumnAlign::Left, ColumnumnAlign::Left],
5685        );
5686
5687        assert_eq!(
5688            table.to_markdown(),
5689            "| a \\| b | plain |\n| :-- | :-- |\n| c | **bold** |"
5690        );
5691    }
5692
5693    #[test]
5694    fn table_data_snapshots_plain_cells_and_markdown() {
5695        let mut table = table_of(
5696            vec![
5697                vec![plain_cell("  Name  "), plain_cell("Age")],
5698                vec![plain_cell("Alice"), plain_cell("30")],
5699            ],
5700            vec![ColumnumnAlign::Left, ColumnumnAlign::Right],
5701        );
5702        table.span = Some(Span { start: 4, end: 42 });
5703
5704        let data = table.table_data();
5705        assert_eq!(data.headers, vec!["Name", "Age"]);
5706        assert_eq!(data.rows, vec![vec!["Alice", "30"]]);
5707        assert_eq!(data.markdown, table.to_markdown());
5708        assert_eq!(data.span, Some(4..42));
5709    }
5710
5711    #[test]
5712    fn cloned_table_keeps_cached_table_data() {
5713        // Streaming appends deep-clone every block; the clone must reuse the
5714        // cached snapshot instead of rebuilding it.
5715        let table = table_of(
5716            vec![vec![plain_cell("Name")], vec![plain_cell("Alice")]],
5717            vec![ColumnumnAlign::Left],
5718        );
5719        let data = table.cached_table_data();
5720
5721        let cloned = table.clone();
5722        assert!(Arc::ptr_eq(&data, &cloned.cached_table_data()));
5723    }
5724
5725    #[test]
5726    fn table_data_handles_tables_without_rows() {
5727        // Header only: still a valid table, with no data rows.
5728        let header_only = table_of(
5729            vec![vec![plain_cell("Name"), plain_cell("Age")]],
5730            vec![ColumnumnAlign::Left, ColumnumnAlign::Left],
5731        );
5732        let data = header_only.table_data();
5733        assert_eq!(data.headers, vec!["Name", "Age"]);
5734        assert!(data.rows.is_empty());
5735        assert_eq!(data.markdown, "| Name | Age |\n| :-- | :-- |");
5736
5737        // No rows at all (a table still streaming in): an empty snapshot.
5738        assert_eq!(Table::default().table_data(), TableData::default());
5739    }
5740
5741    #[test]
5742    fn test_image_node_source() {
5743        use gpui::{ImageFormat, ImageSource, Resource};
5744
5745        fn image_node(url: &str) -> ImageNode {
5746            ImageNode {
5747                url: url.into(),
5748                ..Default::default()
5749            }
5750        }
5751
5752        // Document-provided values stay URI-backed, including `file://` and
5753        // scheme-less strings, so the document never gets implicit
5754        // filesystem access through `Resource::Embedded`.
5755        fn assert_uri(url: &str) {
5756            match image_node(url).source() {
5757                ImageSource::Resource(Resource::Uri(uri)) => assert_eq!(uri.as_ref(), url),
5758                _ => panic!("expected Uri for {url:?}"),
5759            }
5760        }
5761        assert_uri("https://example.com/logo.png");
5762        assert_uri("http://example.com/logo.png");
5763        assert_uri("website/public/logo.svg");
5764        assert_uri("./images/a.png");
5765        assert_uri("../images/a.png");
5766        assert_uri("/absolute/path/logo.svg");
5767        assert_uri("file:///absolute/path/logo.svg");
5768        assert_uri(r"C:\images\logo.png");
5769        assert_uri("docs/a:b.png");
5770        assert_uri("data:text/plain;base64,aGVsbG8=");
5771
5772        // A `data:` image is decoded once and the same decoded image is
5773        // handed to every render.
5774        let node = image_node("data:image/png;base64,iVBORw0KGgo=");
5775        let ImageSource::Image(first) = node.source() else {
5776            panic!("expected an embedded image");
5777        };
5778        assert_eq!(first.format(), ImageFormat::Png);
5779        assert_eq!(first.bytes(), b"\x89PNG\r\n\x1a\n");
5780        let ImageSource::Image(second) = node.source() else {
5781            panic!("expected an embedded image");
5782        };
5783        assert!(Arc::ptr_eq(&first, &second));
5784    }
5785
5786    fn image_paragraph(alt: &str, url: &str) -> Paragraph {
5787        let image = ImageNode {
5788            url: url.into(),
5789            alt: Some(alt.into()),
5790            ..Default::default()
5791        };
5792        Paragraph {
5793            span: None,
5794            children: vec![InlineNode::image(image)],
5795            link_refs: HashMap::new(),
5796            state: Arc::new(Mutex::new(InlineState::default())),
5797            render_cache: ParagraphRenderCache::default(),
5798        }
5799    }
5800
5801    /// Every mark round-trips, including the two Markdown has no plain syntax
5802    /// for.
5803    #[test]
5804    fn marks_round_trip_through_reconstruction() {
5805        let wrap = |mark: TextMark| reconstruct_markdown("x", &[(0..1, mark)], 0..1);
5806
5807        assert_eq!(wrap(TextMark::default().bold()), "**x**");
5808        assert_eq!(wrap(TextMark::default().italic()), "*x*");
5809        assert_eq!(wrap(TextMark::default().code()), "`x`");
5810        assert_eq!(wrap(TextMark::default().strikethrough()), "~~x~~");
5811        assert_eq!(
5812            wrap(TextMark::default().highlight(gpui::rgb(0xfef08a).into())),
5813            "==x=="
5814        );
5815        // No Markdown syntax for underline, so it keeps the tag it came from.
5816        assert_eq!(wrap(TextMark::default().underline()), "<u>x</u>");
5817
5818        // A link keeps its title, which Markdown carries after the URL.
5819        assert_eq!(
5820            wrap(TextMark::default().link(LinkMark {
5821                url: "https://example.com".into(),
5822                title: Some("Tip".into()),
5823                ..Default::default()
5824            })),
5825            "[x](https://example.com \"Tip\")"
5826        );
5827    }
5828
5829    /// A block the selection covers whole comes straight from the source, so it
5830    /// keeps what the author wrote instead of a normalized reconstruction.
5831    #[test]
5832    fn document_selected_source_slices_covered_blocks_from_the_source() {
5833        use crate::text::document::ParsedDocument;
5834
5835        // `_italic_`, the `3.` start and the column padding all survive only
5836        // because the block is copied, not rebuilt.
5837        let source = "start\n\n3. _one_\n4. two\n\n---\n\nend";
5838        let list = "3. _one_\n4. two";
5839        let list_start = source.find(list).unwrap();
5840        let rule_start = source.find("---").unwrap();
5841
5842        let document = ParsedDocument {
5843            source: source.into(),
5844            blocks: vec![
5845                BlockNode::Paragraph(selected_paragraph("start")),
5846                BlockNode::List {
5847                    ordered: true,
5848                    start: Some(3),
5849                    children: vec![],
5850                    span: Some(Span {
5851                        start: list_start,
5852                        end: list_start + list.len(),
5853                    }),
5854                },
5855                BlockNode::HorizontalRule {
5856                    span: Some(Span {
5857                        start: rule_start,
5858                        end: rule_start + 3,
5859                    }),
5860                },
5861                BlockNode::Paragraph(selected_paragraph("end")),
5862            ]
5863            .into(),
5864        };
5865
5866        assert_eq!(
5867            document.selected_text(SelectionFormat::Source, None),
5868            "start\n\n3. _one_\n4. two\n\n---\n\nend"
5869        );
5870    }
5871
5872    #[test]
5873    fn document_selected_source_includes_enclosed_image() {
5874        use crate::text::document::ParsedDocument;
5875
5876        // A standalone image between two selected paragraphs is covered by the
5877        // selection, so it is copied whole even though it holds no selection of
5878        // its own — straight out of the source the parser located it in.
5879        let source = "before\n\n![alt](https://example.com/i.png)\n\nafter";
5880        let image_markdown = "![alt](https://example.com/i.png)";
5881        let start = source.find(image_markdown).unwrap();
5882        let mut image = image_paragraph("alt", "https://example.com/i.png");
5883        image.span = Some(Span {
5884            start,
5885            end: start + image_markdown.len(),
5886        });
5887
5888        let document = ParsedDocument {
5889            source: source.into(),
5890            blocks: vec![
5891                BlockNode::Paragraph(selected_paragraph("before")),
5892                BlockNode::Paragraph(image),
5893                BlockNode::Paragraph(selected_paragraph("after")),
5894            ]
5895            .into(),
5896        };
5897        assert_eq!(
5898            document.selected_text(SelectionFormat::Source, None),
5899            "before\n\n![alt](https://example.com/i.png)\n\nafter"
5900        );
5901    }
5902
5903    #[test]
5904    fn document_selected_source_drops_unenclosed_image() {
5905        use crate::text::document::ParsedDocument;
5906
5907        // An image after the only selected block, with nothing selected after
5908        // it, is not enclosed and is dropped.
5909        let document = ParsedDocument {
5910            source: String::new().into(),
5911            blocks: vec![
5912                BlockNode::Paragraph(selected_paragraph("before")),
5913                BlockNode::Paragraph(image_paragraph("alt", "u")),
5914            ]
5915            .into(),
5916        };
5917        assert_eq!(
5918            document.selected_text(SelectionFormat::Source, None),
5919            "before"
5920        );
5921    }
5922
5923    fn selected_code_block(code: &str, lang: Option<&str>) -> BlockNode {
5924        let block = CodeBlock::new(
5925            code.to_string().into(),
5926            lang.map(|l| l.to_string().into()),
5927            None::<Span>,
5928        );
5929        if let Ok(mut state) = block.state.lock() {
5930            let len = state.text.len();
5931            state.selection = Some((0..len).into());
5932        }
5933        BlockNode::CodeBlock(block)
5934    }
5935
5936    #[test]
5937    fn code_block_selected_source_wraps_in_fence_with_lang() {
5938        let block = selected_code_block("let x = 1;\n", Some("rust"));
5939        let code = block.selected_text(SelectionFormat::Plain);
5940        let code_trimmed = code.trim_end_matches('\n');
5941        // The source wraps the (trailing-newline-trimmed) selected code in a
5942        // fenced block carrying the language; the block arm adds one trailing
5943        // newline.
5944        assert_eq!(
5945            block.selected_text(SelectionFormat::Source),
5946            format!("```rust\n{}\n```\n", code_trimmed)
5947        );
5948        assert!(
5949            block
5950                .selected_text(SelectionFormat::Source)
5951                .starts_with("```rust\n")
5952        );
5953        assert!(
5954            block
5955                .selected_text(SelectionFormat::Source)
5956                .trim_end()
5957                .ends_with("\n```")
5958        );
5959    }
5960
5961    #[test]
5962    fn code_block_selected_source_without_lang() {
5963        let block = selected_code_block("plain\n", None);
5964        let code_trimmed = block.selected_text(SelectionFormat::Plain);
5965        let code_trimmed = code_trimmed.trim_end_matches('\n');
5966        assert_eq!(
5967            block.selected_text(SelectionFormat::Source),
5968            format!("```\n{}\n```\n", code_trimmed)
5969        );
5970    }
5971
5972    #[test]
5973    fn document_selected_source_joins_blocks_with_blank_line() {
5974        use crate::text::document::ParsedDocument;
5975
5976        // A heading, a paragraph, and a two-item ordered list, each fully
5977        // selected. Top-level blocks must be separated by a blank line so the
5978        // copied Markdown re-renders with the same structure.
5979        let document = ParsedDocument {
5980            source: String::new().into(),
5981            blocks: vec![
5982                BlockNode::Heading {
5983                    level: 1,
5984                    children: selected_paragraph("Title"),
5985                    span: None,
5986                },
5987                BlockNode::Paragraph(selected_paragraph("A paragraph.")),
5988                selected_code_block("let x = 1;\n", Some("rust")),
5989                BlockNode::List {
5990                    ordered: true,
5991                    start: Some(1),
5992                    span: None,
5993                    children: vec![
5994                        BlockNode::ListItem {
5995                            children: vec![BlockNode::Paragraph(selected_paragraph("one"))],
5996                            spread: false,
5997                            checked: None,
5998                            span: None,
5999                        },
6000                        BlockNode::ListItem {
6001                            children: vec![BlockNode::Paragraph(selected_paragraph("two"))],
6002                            spread: false,
6003                            checked: None,
6004                            span: None,
6005                        },
6006                    ],
6007                },
6008            ]
6009            .into(),
6010        };
6011
6012        assert_eq!(
6013            document.selected_text(SelectionFormat::Source, None),
6014            "# Title\n\nA paragraph.\n\n```rust\nlet x = 1;\n```\n\n1. one\n2. two"
6015        );
6016    }
6017
6018    #[test]
6019    fn code_block_equality_includes_code_content() {
6020        let first = CodeBlock::new("let value = 1;".into(), Some("rust".into()), None::<Span>);
6021        let second = CodeBlock::new("let value = 2;".into(), Some("rust".into()), None::<Span>);
6022
6023        assert_ne!(first, second);
6024    }
6025}