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},
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    background: Hsla,
1594    text: SharedString,
1595    highlights: Vec<(Range<usize>, InlineHighlight)>,
1596    /// Links as written, before reference resolution.
1597    links: Vec<(Range<usize>, LinkMark)>,
1598}
1599
1600impl PartialEq for Paragraph {
1601    fn eq(&self, other: &Self) -> bool {
1602        self.span == other.span
1603            && self.children == other.children
1604            && self.link_refs == other.link_refs
1605    }
1606}
1607
1608impl Paragraph {
1609    pub(crate) fn new(text: String) -> Self {
1610        Self {
1611            span: None,
1612            children: vec![InlineNode::new(&text)],
1613            link_refs: HashMap::new(),
1614            state: Arc::new(Mutex::new(InlineState::default())),
1615            render_cache: ParagraphRenderCache::default(),
1616        }
1617    }
1618
1619    /// The text, highlights and (unresolved) links of a text-only paragraph,
1620    /// from the cache when the style has not changed since they were built.
1621    fn plain_render(
1622        &self,
1623        node_cx: &NodeContext,
1624        cx: &App,
1625    ) -> (
1626        SharedString,
1627        Vec<(Range<usize>, InlineHighlight)>,
1628        Vec<(Range<usize>, LinkMark)>,
1629    ) {
1630        let mono_font = cx.theme().tokens.typography.mono.clone();
1631        let background = cx.theme().tokens.colors.background;
1632        if let Ok(cache) = self.render_cache.0.lock()
1633            && let Some(cached) = cache.as_ref()
1634            && (Arc::ptr_eq(&cached.style, &node_cx.style) || *cached.style == *node_cx.style)
1635            && cached.mono_font == mono_font
1636            && cached.background == background
1637        {
1638            return (
1639                cached.text.clone(),
1640                cached.highlights.clone(),
1641                cached.links.clone(),
1642            );
1643        }
1644
1645        let mut text = String::new();
1646        let mut highlights: Vec<(Range<usize>, InlineHighlight)> = vec![];
1647        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];
1648        let mut offset = 0;
1649        for inline_node in &self.children {
1650            let text_len = inline_node.text.len();
1651            text.push_str(&inline_node.text);
1652            let mut node_highlights = vec![];
1653            for (range, style) in &inline_node.marks {
1654                let inner_range = (offset + range.start)..(offset + range.end);
1655                let mut highlight = mark_highlight(style, node_cx, cx);
1656                if let Some(link_mark) = style.link.clone() {
1657                    highlight.style.color = Some(node_cx.style.link());
1658                    highlight.style.underline = Some(link_underline(&node_cx.style));
1659                    links.push((inner_range.clone(), link_mark));
1660                }
1661                node_highlights.push((inner_range, highlight));
1662            }
1663            highlights = combine_highlights(highlights, node_highlights);
1664            offset += text_len;
1665        }
1666        let text = SharedString::from(text);
1667        if let Ok(mut cache) = self.render_cache.0.lock() {
1668            *cache = Some(Arc::new(ParagraphRender {
1669                style: node_cx.style.clone(),
1670                mono_font,
1671                background,
1672                text: text.clone(),
1673                highlights: highlights.clone(),
1674                links: links.clone(),
1675            }));
1676        }
1677        (text, highlights, links)
1678    }
1679
1680    pub(super) fn selected_text(&self) -> String {
1681        let mut text = String::new();
1682
1683        for c in self.children.iter() {
1684            let Ok(state) = c.state.lock() else {
1685                continue;
1686            };
1687            if let Some(selection) = &state.selection {
1688                text.push_str(&state.text[selection.start..selection.end]);
1689            }
1690            if let Some(custom) = &c.custom
1691                && c.custom_selection.lock().is_ok_and(|selected| *selected)
1692            {
1693                text.push_str(custom.as_text());
1694            }
1695        }
1696
1697        if let Ok(state) = self.state.lock()
1698            && let Some(selection) = &state.selection
1699        {
1700            text.push_str(&state.text[selection.start..selection.end]);
1701        }
1702
1703        text
1704    }
1705
1706    /// Map the current rendered selection to its exact Markdown source range.
1707    pub(super) fn selected_source_range(&self) -> SourceRangeSelection {
1708        let mut selected = SourceRangeSelection::Unselected;
1709        let mut run: Vec<(usize, &InlineNode)> = Vec::new();
1710        let mut offset = 0;
1711        let mut pending_images: Vec<Option<Span>> = Vec::new();
1712        let mut enters_image = true;
1713
1714        let include_run = |state: &Arc<Mutex<InlineState>>,
1715                           run: &[(usize, &InlineNode)]|
1716         -> (SourceRangeSelection, RunSelection) {
1717            let Ok(state) = state.lock() else {
1718                return (SourceRangeSelection::Unmapped, RunSelection::default());
1719            };
1720            let Some(selection) = state.selection else {
1721                return (SourceRangeSelection::Unselected, RunSelection::default());
1722            };
1723            if selection.start >= selection.end {
1724                return (SourceRangeSelection::Unselected, RunSelection::default());
1725            }
1726
1727            let mut run_selection = RunSelection {
1728                at_start: selection.start == 0,
1729                at_end: selection.end >= state.text.len(),
1730                ..Default::default()
1731            };
1732            let mut mapped = SourceRangeSelection::Unselected;
1733            let mut rendered_end = selection.start;
1734            for (start, child) in run {
1735                let end = start + child.text.len();
1736                let lo = selection.start.max(*start);
1737                let hi = selection.end.min(end);
1738                if lo >= hi {
1739                    continue;
1740                }
1741                run_selection.emitted = true;
1742                if lo > rendered_end {
1743                    return (SourceRangeSelection::Unmapped, run_selection);
1744                }
1745                let Some(range) = child.selected_source_range((lo - start)..(hi - start)) else {
1746                    return (SourceRangeSelection::Unmapped, run_selection);
1747                };
1748                mapped.merge(SourceRangeSelection::Mapped(range));
1749                rendered_end = rendered_end.max(hi);
1750            }
1751            if rendered_end < selection.end {
1752                (SourceRangeSelection::Unmapped, run_selection)
1753            } else {
1754                (mapped, run_selection)
1755            }
1756        };
1757
1758        let merge_images = |selected: &mut SourceRangeSelection, images: &mut Vec<Option<Span>>| {
1759            for span in images.drain(..) {
1760                selected.merge(
1761                    span.map(|span| SourceRangeSelection::Mapped(span.start..span.end))
1762                        .unwrap_or(SourceRangeSelection::Unmapped),
1763                );
1764            }
1765        };
1766
1767        for child in &self.children {
1768            if child.custom.is_some() {
1769                let (run_range, run_selection) = include_run(&child.state, &run);
1770                if run_selection.emitted && run_selection.at_start {
1771                    merge_images(&mut selected, &mut pending_images);
1772                }
1773                selected.merge(run_range);
1774
1775                match child.custom_selection.lock() {
1776                    Ok(value) if *value => {
1777                        if run.is_empty() || (run_selection.emitted && run_selection.at_end) {
1778                            merge_images(&mut selected, &mut pending_images);
1779                        }
1780                        selected.merge(
1781                            child
1782                                .custom
1783                                .as_ref()
1784                                .and_then(MarkdownNode::source_range)
1785                                .map(SourceRangeSelection::Mapped)
1786                                .unwrap_or(SourceRangeSelection::Unmapped),
1787                        );
1788                        enters_image = true;
1789                    }
1790                    Ok(_) => enters_image = false,
1791                    Err(_) => {
1792                        selected.merge(SourceRangeSelection::Unmapped);
1793                        enters_image = false;
1794                    }
1795                }
1796                pending_images.clear();
1797                run.clear();
1798                offset = 0;
1799                continue;
1800            }
1801            if let Some(image) = &child.image {
1802                let run_before = !run.is_empty();
1803                let (run_range, run_selection) = include_run(&child.state, &run);
1804                if run_selection.emitted && run_selection.at_start {
1805                    merge_images(&mut selected, &mut pending_images);
1806                }
1807                selected.merge(run_range);
1808                if run_before {
1809                    enters_image = run_selection.emitted && run_selection.at_end;
1810                }
1811                if enters_image {
1812                    pending_images.push(image.span);
1813                } else {
1814                    pending_images.clear();
1815                }
1816                run.clear();
1817                offset = 0;
1818                continue;
1819            }
1820            run.push((offset, child));
1821            offset += child.text.len();
1822        }
1823
1824        let (trailing_range, trailing) = include_run(&self.state, &run);
1825        if trailing.emitted && trailing.at_start {
1826            merge_images(&mut selected, &mut pending_images);
1827        }
1828        selected.merge(trailing_range);
1829        if !trailing.emitted
1830            && enters_image
1831            && !matches!(selected, SourceRangeSelection::Unselected)
1832        {
1833            merge_images(&mut selected, &mut pending_images);
1834        }
1835        selected
1836    }
1837
1838    /// Reconstruct the Markdown source for the current selection.
1839    ///
1840    /// Mirrors [`selected_text`](Self::selected_text), but emits Markdown
1841    /// instead of the rendered text, using each inline node's `marks` (see
1842    /// [`reconstruct_markdown`]).
1843    ///
1844    /// Selection offsets index an `InlineState.text`, and one such state spans
1845    /// *several* children: [`Paragraph::render`] concatenates children until it
1846    /// hits an inline image, stores that run in the image child's state, then
1847    /// starts over; whatever follows the last image is stored in `self.state`.
1848    /// So walk the children in the same runs and map each selected byte back to
1849    /// the child it was rendered from — mapping against a single child's text
1850    /// would attribute the same offsets to children in other runs.
1851    ///
1852    /// An image has no selection of its own, so it is emitted when the
1853    /// selection runs into it: reaching the end of the run before it, and
1854    /// starting at the beginning of the run after it. A paragraph that begins
1855    /// or ends with an image has no run on that side, which counts as reaching
1856    /// it.
1857    pub(super) fn selected_source(&self) -> String {
1858        self.selected_markdown().finish()
1859    }
1860
1861    /// How many styles the selection within this paragraph takes in.
1862    pub(super) fn selection_styles(&self) -> SelectionStyles {
1863        self.selected_markdown().styles()
1864    }
1865
1866    /// The selection as Markdown pieces, before their marks are serialized;
1867    /// see [`selected_source`](Self::selected_source).
1868    fn selected_markdown(&self) -> MarkdownSource {
1869        let mut source = MarkdownSource::default();
1870        let mut pending_images: Vec<String> = Vec::new();
1871        let mut run: Vec<(usize, &InlineNode)> = Vec::new();
1872        let mut offset = 0;
1873        let mut enters_image = true;
1874
1875        for child in self.children.iter() {
1876            if child.custom.is_some() {
1877                let selected = emit_run(&child.state, &run, &mut pending_images, &mut source);
1878                let object_selected = child
1879                    .custom_selection
1880                    .lock()
1881                    .is_ok_and(|selected| *selected);
1882                if object_selected {
1883                    if run.is_empty() || (selected.emitted && selected.at_end) {
1884                        source.push_images(&pending_images);
1885                    }
1886                    source.push_object(child);
1887                }
1888                pending_images.clear();
1889                enters_image = object_selected;
1890                run.clear();
1891                offset = 0;
1892                continue;
1893            }
1894            let Some(image) = &child.image else {
1895                run.push((offset, child));
1896                offset += child.text.len();
1897                continue;
1898            };
1899
1900            // The run before an image is stored in that image's own state.
1901            let run_before = !run.is_empty();
1902            let selected = emit_run(&child.state, &run, &mut pending_images, &mut source);
1903            if run_before {
1904                enters_image = selected.emitted && selected.at_end;
1905            }
1906            if enters_image {
1907                pending_images.push(image_markdown(image));
1908            } else {
1909                pending_images.clear();
1910            }
1911
1912            run.clear();
1913            offset = 0;
1914        }
1915
1916        let trailing = emit_run(&self.state, &run, &mut pending_images, &mut source);
1917        // Trailing images have no run after them to flush them.
1918        if !trailing.emitted && enters_image && !source.is_empty() {
1919            source.push_images(&pending_images);
1920        }
1921
1922        source
1923    }
1924
1925    pub(super) fn text(&self) -> String {
1926        let mut text = String::new();
1927        for node in self.children.iter() {
1928            text.push_str(&node.text);
1929        }
1930        text
1931    }
1932
1933    /// Synchronously clear the selection stored in every inline state.
1934    ///
1935    /// Mirrors the [`selected_text`](Self::selected_text) traversal.
1936    pub(super) fn has_selection(&self) -> bool {
1937        self.children.iter().any(|c| {
1938            c.state.lock().is_ok_and(|state| state.selection.is_some())
1939                || c.custom_selection.lock().is_ok_and(|selected| *selected)
1940        }) || self
1941            .state
1942            .lock()
1943            .is_ok_and(|state| state.selection.is_some())
1944    }
1945
1946    pub(super) fn clear_selection(&self) {
1947        for c in self.children.iter() {
1948            if let Ok(mut selected) = c.custom_selection.lock() {
1949                *selected = false;
1950            }
1951            if let Ok(mut state) = c.state.lock() {
1952                state.selection = None;
1953            }
1954        }
1955
1956        if let Ok(mut state) = self.state.lock() {
1957            state.selection = None;
1958        }
1959    }
1960}
1961
1962/// A table: rows of cell paragraphs and per-column alignment.
1963#[derive(Debug, Clone, Default, PartialEq)]
1964pub struct Table {
1965    pub(crate) children: Vec<TableRow>,
1966    pub(crate) column_aligns: Vec<ColumnumnAlign>,
1967    pub(crate) span: Option<Span>,
1968    /// The [`TableData`] handed to the `table_actions` hook, kept between
1969    /// frames; see [`Table::cached_table_data`].
1970    pub(crate) table_data_cache: TableDataCache,
1971    pub(crate) column_widths_cache: TableColumnWidthsCache,
1972}
1973
1974/// Derived state, invisible to `Debug` and equality.
1975///
1976/// A clone carries what was cached so far: a parsed table does not change
1977/// after parsing (cell mutations only touch selection state, which
1978/// [`TableData`] does not read), so a clone has the same data. Streaming
1979/// reparses deep-clone the document on every append, and an empty clone made
1980/// every table rebuild.
1981#[derive(Default)]
1982pub(crate) struct TableDataCache(Mutex<Option<Arc<TableData>>>);
1983
1984impl Clone for TableDataCache {
1985    fn clone(&self) -> Self {
1986        let cached = self.0.lock().ok().and_then(|cache| cache.clone());
1987        Self(Mutex::new(cached))
1988    }
1989}
1990
1991impl PartialEq for TableDataCache {
1992    fn eq(&self, _: &Self) -> bool {
1993        true
1994    }
1995}
1996
1997impl std::fmt::Debug for TableDataCache {
1998    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1999        f.write_str("TableDataCache")
2000    }
2001}
2002
2003/// Like `TableDataCache`, derived widths survive clones of an unchanged
2004/// parsed table. A newly parsed table starts with an empty cache.
2005#[derive(Default)]
2006pub(crate) struct TableColumnWidthsCache(Mutex<Option<Arc<TableColumnWidths>>>);
2007
2008impl Clone for TableColumnWidthsCache {
2009    fn clone(&self) -> Self {
2010        let cached = self.0.lock().ok().and_then(|cache| cache.clone());
2011        Self(Mutex::new(cached))
2012    }
2013}
2014
2015impl PartialEq for TableColumnWidthsCache {
2016    fn eq(&self, _: &Self) -> bool {
2017        true
2018    }
2019}
2020
2021impl std::fmt::Debug for TableColumnWidthsCache {
2022    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2023        f.write_str("TableColumnWidthsCache")
2024    }
2025}
2026
2027fn text_system_id(window: &Window) -> usize {
2028    std::rc::Rc::as_ptr(window.text_system()) as usize
2029}
2030
2031struct TableColumnWidths {
2032    /// Identity of the window text system the widths were measured with.
2033    /// `WindowTextSystem` is `Rc`-shared and the cache must stay `Send`, so
2034    /// only its address is kept.
2035    text_system: usize,
2036    /// The styles the header row and the body rows render with.
2037    text_styles: TableTextStyles,
2038    rem_size: Pixels,
2039    mono_family: SharedString,
2040    inline_code: HighlightStyle,
2041    /// Maxima from ordinary text only. Custom cells may shrink after an update.
2042    widths: Vec<f32>,
2043    custom_cells: Vec<(usize, usize)>,
2044}
2045
2046/// The text styles table rows render with: the window's, refined by
2047/// `style.table` on the frame, `style.table_head` on the header row and
2048/// `style.table_cell` on every cell, in the order the elements apply them.
2049#[derive(Clone, PartialEq)]
2050struct TableTextStyles {
2051    head: gpui::TextStyle,
2052    body: gpui::TextStyle,
2053    head_refinement: TextStyleRefinement,
2054    body_refinement: TextStyleRefinement,
2055}
2056
2057impl TableTextStyles {
2058    fn new(style: &TextViewStyle, window: &Window) -> Self {
2059        let mut body_refinement = style.table().text.clone();
2060        let mut head_refinement = body_refinement.clone();
2061        head_refinement.refine(&style.table_head().text);
2062        head_refinement.refine(&style.table_cell().text);
2063        body_refinement.refine(&style.table_cell().text);
2064        let mut head = window.text_style();
2065        let mut body = head.clone();
2066        head.refine(&head_refinement);
2067        body.refine(&body_refinement);
2068        Self {
2069            head,
2070            body,
2071            head_refinement,
2072            body_refinement,
2073        }
2074    }
2075
2076    fn row(&self, row_ix: usize) -> &gpui::TextStyle {
2077        if row_ix == 0 { &self.head } else { &self.body }
2078    }
2079
2080    fn row_refinement(&self, row_ix: usize) -> &TextStyleRefinement {
2081        if row_ix == 0 {
2082            &self.head_refinement
2083        } else {
2084            &self.body_refinement
2085        }
2086    }
2087}
2088
2089/// Plain snapshot of a rendered Markdown table, passed to the
2090/// [`crate::text::TextView::table_actions`] hook.
2091#[derive(Debug, Clone, Default, PartialEq)]
2092pub struct TableData {
2093    /// First table row (header cells) as plain text.
2094    pub headers: Vec<String>,
2095    /// Rows after the header as plain text cells. May be ragged while
2096    /// a table is still streaming in.
2097    pub rows: Vec<Vec<String>>,
2098    /// The table serialized back to GFM pipe-table Markdown, alignments kept.
2099    pub markdown: String,
2100    /// Byte range of the table in the Markdown source, for callers that need
2101    /// to map the table back to the document.
2102    ///
2103    /// Not needed to keep element ids apart: the actions row is wrapped in its
2104    /// own identified element, so plain ids like `"copy"` are already scoped
2105    /// per table.
2106    pub span: Option<Range<usize>>,
2107}
2108
2109impl Table {
2110    /// A table of `rows` (one paragraph per cell) with the column alignments.
2111    pub fn new(rows: Vec<Vec<Paragraph>>, column_aligns: Vec<ColumnumnAlign>) -> Self {
2112        Self {
2113            children: rows
2114                .into_iter()
2115                .map(|row| TableRow {
2116                    children: row
2117                        .into_iter()
2118                        .map(|children| TableCell {
2119                            children,
2120                            width: None,
2121                        })
2122                        .collect(),
2123                })
2124                .collect(),
2125            column_aligns,
2126            ..Default::default()
2127        }
2128    }
2129
2130    /// The rows, each an iterator over its cells' paragraphs.
2131    pub fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = &Paragraph>> {
2132        self.children
2133            .iter()
2134            .map(|row| row.children.iter().map(|cell| &cell.children))
2135    }
2136
2137    /// The alignment of each column.
2138    pub fn column_aligns(&self) -> &[ColumnumnAlign] {
2139        &self.column_aligns
2140    }
2141
2142    pub(crate) fn column_align(&self, index: usize) -> ColumnumnAlign {
2143        self.column_aligns.get(index).copied().unwrap_or_default()
2144    }
2145
2146    /// Serialize the table back to GFM pipe-table Markdown (`| a | b |`),
2147    /// preserving column alignments. Cell newlines collapse to spaces and
2148    /// `|` is escaped so rows stay intact.
2149    ///
2150    /// Mirrors [`table_selected_source`], which does the same for the selected
2151    /// cells only.
2152    pub(crate) fn to_markdown(&self) -> String {
2153        let mut lines: Vec<String> = Vec::with_capacity(self.children.len() + 1);
2154
2155        for (row_ix, row) in self.children.iter().enumerate() {
2156            let cells: Vec<String> = row
2157                .children
2158                .iter()
2159                .map(|cell| {
2160                    cell.children
2161                        .to_markdown()
2162                        .trim()
2163                        .replace('\n', " ")
2164                        .replace('|', "\\|")
2165                })
2166                .collect();
2167            lines.push(format!("| {} |", cells.join(" | ")));
2168
2169            // The Markdown delimiter row carries the column alignments and must
2170            // follow the header row.
2171            if row_ix == 0 {
2172                let aligns: Vec<String> = (0..row.children.len())
2173                    .map(|ix| {
2174                        match self.column_align(ix) {
2175                            ColumnumnAlign::None | ColumnumnAlign::Left => ":--",
2176                            ColumnumnAlign::Center => ":-:",
2177                            ColumnumnAlign::Right => "--:",
2178                        }
2179                        .to_string()
2180                    })
2181                    .collect();
2182                lines.push(format!("| {} |", aligns.join(" | ")));
2183            }
2184        }
2185
2186        lines.join("\n")
2187    }
2188
2189    /// Snapshot of this table for the [`crate::text::TextView::table_actions`]
2190    /// hook.
2191    pub(crate) fn table_data(&self) -> TableData {
2192        let row_text = |row: &TableRow| {
2193            row.children
2194                .iter()
2195                .map(|cell| cell.children.text().trim().to_string())
2196                .collect::<Vec<_>>()
2197        };
2198
2199        TableData {
2200            headers: self.children.first().map(row_text).unwrap_or_default(),
2201            rows: self.children.iter().skip(1).map(row_text).collect(),
2202            markdown: self.to_markdown(),
2203            span: self.span.map(|span| span.start..span.end),
2204        }
2205    }
2206
2207    /// [`Self::table_data`], built on the first render and reused by later
2208    /// ones: serializing every cell and the whole table back to Markdown on
2209    /// every scroll or stream frame is wasted work, and a parsed table does
2210    /// not change after parsing (a reparse builds a new one).
2211    fn cached_table_data(&self) -> Arc<TableData> {
2212        if let Ok(cache) = self.table_data_cache.0.lock()
2213            && let Some(cached) = cache.as_ref()
2214        {
2215            return cached.clone();
2216        }
2217
2218        let data = Arc::new(self.table_data());
2219        if let Ok(mut cache) = self.table_data_cache.0.lock() {
2220            *cache = Some(data.clone());
2221        }
2222        data
2223    }
2224}
2225
2226/// The alignment of a table column.
2227#[derive(Debug, Default, Copy, Clone, PartialEq)]
2228pub enum ColumnumnAlign {
2229    /// No alignment in the delimiter row (`---`); renders like `Left`.
2230    #[default]
2231    None,
2232    Left,
2233    Center,
2234    Right,
2235}
2236
2237impl From<mdast::AlignKind> for ColumnumnAlign {
2238    fn from(value: mdast::AlignKind) -> Self {
2239        match value {
2240            mdast::AlignKind::None => ColumnumnAlign::None,
2241            mdast::AlignKind::Left => ColumnumnAlign::Left,
2242            mdast::AlignKind::Center => ColumnumnAlign::Center,
2243            mdast::AlignKind::Right => ColumnumnAlign::Right,
2244        }
2245    }
2246}
2247
2248#[derive(Debug, Clone, Default, PartialEq)]
2249pub(crate) struct TableRow {
2250    pub children: Vec<TableCell>,
2251}
2252
2253#[derive(Debug, Clone, Default, PartialEq)]
2254pub(crate) struct TableCell {
2255    pub children: Paragraph,
2256    pub width: Option<DefiniteLength>,
2257}
2258
2259impl Paragraph {
2260    pub(crate) fn take(&mut self) -> Paragraph {
2261        std::mem::replace(
2262            self,
2263            Paragraph {
2264                span: None,
2265                children: vec![],
2266                link_refs: Default::default(),
2267                state: Arc::new(Mutex::new(InlineState::default())),
2268                render_cache: ParagraphRenderCache::default(),
2269            },
2270        )
2271    }
2272
2273    pub(crate) fn is_image(&self) -> bool {
2274        false
2275    }
2276
2277    pub(crate) fn set_span(&mut self, span: Span) {
2278        self.span = Some(span);
2279    }
2280
2281    pub(crate) fn push_str(&mut self, text: &str) {
2282        self.children.push(
2283            InlineNode::new(text.to_string()).marks(vec![(0..text.len(), TextMark::default())]),
2284        );
2285        self.invalidate_render_cache();
2286    }
2287
2288    /// Appends an inline run.
2289    pub fn push(&mut self, text: InlineNode) {
2290        self.children.push(text);
2291        self.invalidate_render_cache();
2292    }
2293
2294    /// The inline runs.
2295    pub fn children(&self) -> &[InlineNode] {
2296        &self.children
2297    }
2298
2299    /// Marks this paragraph as a leaf of an external editor: while a
2300    /// [`collect_leaf_geometry`](super::editor_host::collect_leaf_geometry)
2301    /// scope is active, it reports its geometry under `key` and paints the
2302    /// decorations given for `key`. Rekeying detaches runtime state from clones.
2303    pub fn with_leaf_key(mut self, key: u64) -> Self {
2304        set_leaf_key(&mut self.state, key);
2305        self
2306    }
2307
2308    pub(crate) fn push_image(&mut self, image: ImageNode) {
2309        self.children.push(InlineNode::image(image));
2310        self.invalidate_render_cache();
2311    }
2312
2313    /// The children changed, so what was derived from them is stale.
2314    fn invalidate_render_cache(&mut self) {
2315        self.render_cache = ParagraphRenderCache::default();
2316    }
2317
2318    pub(crate) fn is_empty(&self) -> bool {
2319        self.children.is_empty()
2320            || self
2321                .children
2322                .iter()
2323                .all(|node| node.text.is_empty() && node.image.is_none())
2324    }
2325
2326    /// Return length of children text.
2327    pub(crate) fn text_len(&self) -> usize {
2328        self.children
2329            .iter()
2330            .map(|node| node.text.len())
2331            .sum::<usize>()
2332    }
2333
2334    pub(crate) fn merge(&mut self, other: Self) {
2335        self.children.extend(other.children);
2336        self.invalidate_render_cache();
2337    }
2338}
2339
2340#[derive(Debug, Clone)]
2341pub struct CodeBlock {
2342    lang: Option<SharedString>,
2343    meta: Option<SharedString>,
2344    state: Arc<Mutex<InlineState>>,
2345    highlight_cache: Arc<Mutex<Option<CachedCodeBlockHighlights>>>,
2346    /// Rendered UTF-8 byte spans of the code paired with their exact Markdown
2347    /// source spans.
2348    pub(crate) source_segments: Vec<SourceSegment>,
2349    pub span: Option<Span>,
2350}
2351
2352struct CachedCodeBlockHighlights {
2353    highlighter: Arc<CodeBlockHighlighterFn>,
2354    styles: Vec<(Range<usize>, HighlightStyle)>,
2355}
2356
2357impl std::fmt::Debug for CachedCodeBlockHighlights {
2358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2359        f.debug_struct("CachedCodeBlockHighlights")
2360            .field("styles", &self.styles)
2361            .finish_non_exhaustive()
2362    }
2363}
2364
2365impl PartialEq for CodeBlock {
2366    fn eq(&self, other: &Self) -> bool {
2367        self.lang == other.lang
2368            && self.meta == other.meta
2369            && self.code() == other.code()
2370            && self.span == other.span
2371    }
2372}
2373
2374impl CodeBlock {
2375    /// Get the language of the code block.
2376    pub fn lang(&self) -> Option<SharedString> {
2377        self.lang.clone()
2378    }
2379
2380    /// The rest of the fence's info string after the language, when any.
2381    pub fn meta(&self) -> Option<SharedString> {
2382        self.meta.clone()
2383    }
2384
2385    /// Sets the fence's info string after the language. It is kept for
2386    /// round trips and is not rendered.
2387    pub fn with_meta(mut self, meta: Option<SharedString>) -> Self {
2388        self.meta = meta;
2389        self
2390    }
2391
2392    /// Marks this code block as a leaf of an external editor, like
2393    /// [`Paragraph::with_leaf_key`]. Rekeying detaches runtime state from clones.
2394    pub fn with_leaf_key(mut self, key: u64) -> Self {
2395        set_leaf_key(&mut self.state, key);
2396        self
2397    }
2398
2399    /// Get the code content of the code block.
2400    pub fn code(&self) -> SharedString {
2401        self.state
2402            .lock()
2403            .map(|state| state.text.clone())
2404            .unwrap_or_default()
2405    }
2406
2407    /// Builds a code block that is not tied to a parsed document.
2408    ///
2409    /// [`crate::TextView::code_block_highlighter`] hands a `&CodeBlock` to the
2410    /// highlighter it is given, so anyone writing one needs a way to build a
2411    /// block to exercise it against.
2412    pub fn from_code(code: impl Into<SharedString>, lang: Option<impl Into<SharedString>>) -> Self {
2413        Self::new(code.into(), lang.map(Into::into), None::<Span>)
2414    }
2415
2416    /// A code block of `code` in `lang`.
2417    pub fn new(
2418        code: SharedString,
2419        lang: Option<SharedString>,
2420        span: Option<impl Into<Span>>,
2421    ) -> Self {
2422        let state = Arc::new(Mutex::new(InlineState::default()));
2423        if let Ok(mut state) = state.lock() {
2424            state.set_text(code);
2425        }
2426
2427        Self {
2428            lang,
2429            meta: None,
2430            state,
2431            highlight_cache: Arc::new(Mutex::new(None)),
2432            source_segments: vec![],
2433            span: span.map(|s| s.into()),
2434        }
2435    }
2436
2437    pub(crate) fn source_segments(mut self, source_segments: Vec<SourceSegment>) -> Self {
2438        self.source_segments = source_segments;
2439        self
2440    }
2441
2442    #[cfg(test)]
2443    pub(crate) fn set_selection(&self, selection: Range<usize>) {
2444        if let Ok(mut state) = self.state.lock() {
2445            state.selection = Some(selection.into());
2446        }
2447    }
2448
2449    pub(super) fn selected_source_range(&self) -> SourceRangeSelection {
2450        let Ok(state) = self.state.lock() else {
2451            return SourceRangeSelection::Unmapped;
2452        };
2453        let Some(selection) = state.selection else {
2454            return SourceRangeSelection::Unselected;
2455        };
2456        if selection.start >= selection.end {
2457            return SourceRangeSelection::Unselected;
2458        }
2459        source_range_for_segments(&self.source_segments, selection.start..selection.end)
2460            .map(SourceRangeSelection::Mapped)
2461            .unwrap_or(SourceRangeSelection::Unmapped)
2462    }
2463
2464    fn highlighted_styles(
2465        &self,
2466        highlighter: &Arc<CodeBlockHighlighterFn>,
2467    ) -> Vec<(Range<usize>, HighlightStyle)> {
2468        if let Ok(cache) = self.highlight_cache.lock()
2469            && let Some(cache) = cache.as_ref()
2470            && Arc::ptr_eq(&cache.highlighter, highlighter)
2471        {
2472            return cache.styles.clone();
2473        }
2474
2475        let code_len = self.code().len();
2476        let styles = highlighter(self)
2477            .into_iter()
2478            .filter(|(range, _)| range.start <= range.end && range.end <= code_len)
2479            .collect::<Vec<_>>();
2480        if let Ok(mut cache) = self.highlight_cache.lock() {
2481            *cache = Some(CachedCodeBlockHighlights {
2482                highlighter: highlighter.clone(),
2483                styles: styles.clone(),
2484            });
2485        }
2486        styles
2487    }
2488
2489    pub(super) fn selected_text(&self) -> String {
2490        let mut text = String::new();
2491        if let Ok(state) = self.state.lock()
2492            && let Some(selection) = &state.selection
2493        {
2494            text.push_str(&state.text[selection.start..selection.end]);
2495        }
2496        text
2497    }
2498
2499    /// Markdown source for the current selection.
2500    ///
2501    /// The selected code is wrapped in a fenced code block carrying the block's
2502    /// language, so a selected code block round-trips as Markdown (e.g.
2503    /// ```` ```rust\n…\n``` ````) instead of pasting as bare, unfenced text.
2504    /// A partial selection is still emitted as a valid fenced block.
2505    pub(super) fn selected_source(&self) -> String {
2506        let code = self.selected_text();
2507        if code.is_empty() {
2508            return String::new();
2509        }
2510        let lang = self.lang.clone().unwrap_or_default();
2511        // Trim trailing newlines so the closing fence sits on its own line
2512        // directly after the last code line (no blank line before it).
2513        let code = code.trim_end_matches('\n');
2514        format!("```{}\n{}\n```", lang, code)
2515    }
2516
2517    pub(super) fn text(&self) -> String {
2518        self.state
2519            .lock()
2520            .map(|state| state.text.to_string())
2521            .unwrap_or_default()
2522    }
2523
2524    /// Synchronously clear the selection stored in the inline state.
2525    ///
2526    /// Mirrors the [`selected_text`](Self::selected_text) traversal.
2527    pub(super) fn has_selection(&self) -> bool {
2528        self.state
2529            .lock()
2530            .is_ok_and(|state| state.selection.is_some())
2531    }
2532
2533    pub(super) fn clear_selection(&self) {
2534        if let Ok(mut state) = self.state.lock() {
2535            state.selection = None;
2536        }
2537    }
2538
2539    fn render(
2540        &self,
2541        options: &NodeRenderOptions,
2542        node_cx: &NodeContext,
2543        window: &mut Window,
2544        cx: &mut App,
2545    ) -> AnyElement {
2546        let style = &node_cx.style;
2547        let leaf_key = self.span.map(|span| TextLeafKey::block(span.start));
2548        // The language sits quietly in the top-right corner, where custom
2549        // actions go instead when the caller provides them; the top padding
2550        // keeps the first line clear of it. Like the actions, it stays out of
2551        // the scrolled content so it holds its corner.
2552        let lang_label = self
2553            .lang()
2554            .filter(|lang| !lang.is_empty() && node_cx.code_block_actions.is_none())
2555            .map(|lang| {
2556                div()
2557                    .absolute()
2558                    .top(px(7.))
2559                    .right_3()
2560                    .font_family(cx.theme().tokens.typography.sans.clone())
2561                    .text_size(cx.theme().tokens.typography.xs.size)
2562                    .line_height(px(16.))
2563                    .text_color(style.muted_foreground())
2564                    .child(lang)
2565            });
2566
2567        let block = div()
2568            .w_full()
2569            .min_w_0()
2570            .px_4()
2571            .pt(if lang_label.is_some() {
2572                px(28.)
2573            } else {
2574                px(14.)
2575            })
2576            .pb(px(14.))
2577            .bg(style.code_background())
2578            .border_1()
2579            .border_color(style.border())
2580            .font_family(cx.theme().tokens.typography.mono.clone())
2581            .text_size(cx.theme().tokens.typography.mono_md.size)
2582            .line_height(relative(1.6))
2583            .relative()
2584            .refine_style(&style.code_block())
2585            .child(
2586                Inline::new(
2587                    self.state.clone(),
2588                    vec![],
2589                    fade_highlights(
2590                        node_cx
2591                            .code_block_highlighter
2592                            .as_ref()
2593                            .map(|highlighter| self.highlighted_styles(highlighter))
2594                            .unwrap_or_default()
2595                            .into_iter()
2596                            .map(|(range, style)| (range, InlineHighlight::from(style)))
2597                            .collect(),
2598                        node_cx.stream_fades(leaf_key),
2599                    ),
2600                    node_cx.link_click_handler.clone(),
2601                )
2602                .range_backgrounds(node_cx.range_backgrounds(leaf_key).to_vec())
2603                .reveal(node_cx.reveal_at(leaf_key, 0, self.code().len())),
2604            );
2605        let actions = node_cx.code_block_actions.clone().map(|actions| {
2606            div()
2607                .id("actions")
2608                .absolute()
2609                .top_2()
2610                .right_2()
2611                .bg(style.code_background())
2612                .rounded(cx.theme().tokens.radius.md)
2613                .child(actions(&self, window, cx))
2614        });
2615        // The id scopes the caller's action ids per code block, so plain ids
2616        // like `"copy"` don't collide across blocks.
2617        let id = block_element_id("codeblock", self.span, options.ix);
2618        let block = if matches!(style.code_block().overflow.y, Some(Overflow::Scroll)) {
2619            let scroll_handle = window
2620                .use_keyed_state(
2621                    block_element_id("codeblock-scroll", self.span, options.ix),
2622                    cx,
2623                    |_, _| ScrollHandle::default(),
2624                )
2625                .read(cx)
2626                .clone();
2627            // Scroll mode is opted in by setting `style.code_block`'s
2628            // `overflow.y` to `Overflow::Scroll`.
2629            // The mask consumes the wheel in the capture phase, so an ancestor
2630            // `gpui::list` doesn't scroll on the same event. Mask, scrollbar
2631            // and actions are siblings of the scrolled block, so they stay
2632            // pinned to the viewport instead of moving with the code.
2633            div()
2634                .id(id.clone())
2635                .w_full()
2636                .min_w_0()
2637                .relative()
2638                .child(block.id("scroll").track_scroll(&scroll_handle))
2639                .child(ScrollableMask::new(Axis::Vertical, &scroll_handle).id(id))
2640                .child(
2641                    div()
2642                        .absolute()
2643                        .inset_0()
2644                        .child(Scrollbar::vertical(&scroll_handle)),
2645                )
2646                .children(actions)
2647                .children(lang_label)
2648                .into_any_element()
2649        } else {
2650            // Without actions nothing under the block needs element state.
2651            match actions {
2652                Some(actions) => block.id(id).child(actions).into_any_element(),
2653                None => block.children(lang_label).into_any_element(),
2654            }
2655        };
2656
2657        gapped(
2658            block,
2659            if options.is_last {
2660                rems(0.)
2661            } else {
2662                style.paragraph_gap()
2663            },
2664        )
2665    }
2666}
2667
2668/// A context for rendering nodes, contains link references.
2669#[derive(Default, Clone)]
2670pub(crate) struct NodeContext {
2671    /// The byte offset of the node in the original markdown text.
2672    /// Used for incremental updates.
2673    pub(crate) offset: usize,
2674    pub(crate) link_refs: HashMap<SharedString, LinkMark>,
2675    pub(crate) style: Arc<TextViewStyle>,
2676    pub(crate) code_block_actions: Option<Arc<CodeBlockActionsFn>>,
2677    pub(crate) code_block_highlighter: Option<Arc<CodeBlockHighlighterFn>>,
2678    pub(crate) table_actions: Option<Arc<TableActionsFn>>,
2679    pub(crate) image_source: Option<Arc<super::text_view::ImageSourceFn>>,
2680    pub(crate) link_click_handler: Option<Arc<LinkClickHandlerFn>>,
2681    pub(crate) markdown_extensions: Arc<MarkdownExtensions>,
2682    /// This frame's streamed fade-in, when any text is still fading.
2683    pub(crate) stream_fade: Option<Arc<StreamFadeFrame>>,
2684    /// The application's range highlights, when there are any.
2685    pub(crate) range_highlights: Option<Arc<RangeHighlightFrame>>,
2686    /// The line being scrolled into view, when there is one.
2687    pub(crate) reveal: Option<RevealRequest>,
2688    /// The font size the text view inherits this frame, or zero when not
2689    /// resolved; read it through `body_font_size`.
2690    pub(crate) body_font_size: Pixels,
2691}
2692
2693impl NodeContext {
2694    fn image_source(&self, image: &ImageNode) -> ImageSource {
2695        match &self.image_source {
2696            Some(resolve) => resolve(&image.url),
2697            None => image.source(),
2698        }
2699    }
2700
2701    pub(super) fn add_ref(&mut self, identifier: SharedString, link: LinkMark) {
2702        self.link_refs.insert(identifier, link);
2703    }
2704
2705    /// The fade ranges of the text leaf `key`, in its rendered byte space.
2706    fn stream_fades(&self, key: Option<TextLeafKey>) -> &[(Range<usize>, f32)] {
2707        match (&self.stream_fade, key) {
2708            (Some(frame), Some(key)) => frame.fades(key).unwrap_or_default(),
2709            _ => &[],
2710        }
2711    }
2712
2713    /// The range highlight backgrounds of the text leaf `key`, in its
2714    /// rendered byte space.
2715    fn range_backgrounds(&self, key: Option<TextLeafKey>) -> &[(Range<usize>, Hsla)] {
2716        match (&self.range_highlights, key) {
2717            (Some(frame), Some(key)) => frame.backgrounds(key),
2718            _ => &[],
2719        }
2720    }
2721
2722    /// The pending reveal, when it starts in the text leaf `key` between
2723    /// `start` and `end`, rebased to `start`.
2724    fn reveal_at(&self, key: Option<TextLeafKey>, start: usize, end: usize) -> Option<RevealAt> {
2725        self.reveal.as_ref()?.at(key, start, end)
2726    }
2727}
2728
2729impl PartialEq for NodeContext {
2730    fn eq(&self, other: &Self) -> bool {
2731        self.link_refs == other.link_refs && self.style == other.style
2732        // Note: code_block_actions, table_actions and markdown_extensions are
2733        // intentionally not compared (closures can't be compared)
2734    }
2735}
2736
2737/// The text color with more contrast against a highlight composited over the
2738/// theme background (WCAG relative luminance).
2739fn mark_text_color(highlight: Hsla, background: Hsla) -> Hsla {
2740    fn channel(value: f32) -> f32 {
2741        if value <= 0.04045 {
2742            value / 12.92
2743        } else {
2744            ((value + 0.055) / 1.055).powf(2.4)
2745        }
2746    }
2747    let rgb = background.to_rgb().blend(highlight.to_rgb());
2748    let luminance = 0.2126 * channel(rgb.r) + 0.7152 * channel(rgb.g) + 0.0722 * channel(rgb.b);
2749    // Contrast with black is (L + 0.05) / 0.05 and with white 1.05 / (L + 0.05);
2750    // they are equal where (L + 0.05)^2 = 0.0525.
2751    if (luminance + 0.05) * (luminance + 0.05) > 0.0525 {
2752        gpui::black()
2753    } else {
2754        gpui::white()
2755    }
2756}
2757
2758/// The highlight a text mark renders with. The link decoration is applied by
2759/// the caller, which also has to record the link range.
2760fn mark_highlight(mark: &TextMark, node_cx: &NodeContext, cx: &App) -> InlineHighlight {
2761    let mut highlight = HighlightStyle::default();
2762    if mark.bold {
2763        highlight.font_weight = Some(FontWeight::BOLD);
2764    }
2765    if mark.italic {
2766        highlight.font_style = Some(FontStyle::Italic);
2767    }
2768    if mark.strikethrough {
2769        highlight.strikethrough = Some(gpui::StrikethroughStyle {
2770            thickness: gpui::px(1.),
2771            ..Default::default()
2772        });
2773    }
2774    if mark.underline {
2775        highlight.underline = Some(gpui::UnderlineStyle {
2776            thickness: gpui::px(1.),
2777            ..Default::default()
2778        });
2779    }
2780    let mut font_family = None;
2781    if mark.code {
2782        highlight = highlight.highlight(node_cx.style.inline_code_highlight());
2783        let style = node_cx.style.inline_code_style();
2784        highlight = highlight.highlight(HighlightStyle {
2785            color: style.text.color,
2786            font_weight: style.text.font_weight,
2787            font_style: style.text.font_style,
2788            underline: style.text.underline,
2789            strikethrough: style.text.strikethrough,
2790            ..Default::default()
2791        });
2792        font_family = Some(
2793            style
2794                .text
2795                .font_family
2796                .clone()
2797                .unwrap_or_else(|| cx.theme().tokens.typography.mono.clone()),
2798        );
2799    }
2800    if let Some(color) = mark.highlight {
2801        highlight.background_color = Some(color);
2802        // Set the text color with the background, as the HTML standard's
2803        // `mark { background: yellow; color: black }` does, so a light
2804        // highlight stays readable in a dark theme.
2805        // A transparent mark leaves the underlying surface unchanged. Keep
2806        // its inherited foreground, including on custom-colored surfaces.
2807        if color.a > 0.0 {
2808            highlight.color = Some(mark_text_color(color, cx.theme().tokens.colors.background));
2809        }
2810    }
2811    InlineHighlight {
2812        style: highlight,
2813        font_family,
2814        font_size_scale: mark.code.then_some(0.875),
2815        code_style: mark.code.then(|| node_cx.style.inline_code_style().clone()),
2816    }
2817}
2818
2819impl Paragraph {
2820    /// The highlights over [`Self::text`], for measuring the paragraph with
2821    /// the runs it renders with. Link colors are left out: they do not move
2822    /// glyphs.
2823    fn inline_highlights(
2824        &self,
2825        node_cx: &NodeContext,
2826        cx: &App,
2827    ) -> Vec<(Range<usize>, InlineHighlight)> {
2828        let mut highlights = vec![];
2829        let mut offset = 0;
2830        for inline_node in &self.children {
2831            let node_highlights = inline_node
2832                .marks
2833                .iter()
2834                .map(|(range, mark)| {
2835                    (
2836                        (offset + range.start)..(offset + range.end),
2837                        mark_highlight(mark, node_cx, cx),
2838                    )
2839                })
2840                .collect::<Vec<_>>();
2841            highlights = combine_highlights(highlights, node_highlights);
2842            offset += inline_node.text.len();
2843        }
2844        highlights
2845    }
2846
2847    /// `fade_key` names this paragraph's text for the streamed fade-in and
2848    /// for range highlights; the owning block supplies it because a heading
2849    /// or table cell paragraph carries no span of its own.
2850    fn render(
2851        &self,
2852        fade_key: Option<TextLeafKey>,
2853        node_cx: &NodeContext,
2854        _window: &mut Window,
2855        cx: &mut App,
2856    ) -> AnyElement {
2857        let children = &self.children;
2858        let fades = node_cx.stream_fades(fade_key);
2859        let backgrounds = node_cx.range_backgrounds(fade_key);
2860
2861        if self.should_render_inline_flow() {
2862            return InlineFlow::new(
2863                leaf_element_id(fade_key),
2864                self.inline_flow_items(fade_key, fades, backgrounds, node_cx, cx),
2865                node_cx.link_click_handler.clone(),
2866            )
2867            .editor_key(self.leaf_key())
2868            .into_any_element();
2869        }
2870
2871        let has_image = children.iter().any(|child| child.image.is_some());
2872        // Text alone is one `Inline`, which needs no box of its own, and its
2873        // text, highlights and links are cached across frames.
2874        if !has_image {
2875            let (text, highlights, mut links) = self.plain_render(node_cx, cx);
2876            // An editor leaf is laid out even when empty, so the editor gets
2877            // a line box for the caret.
2878            if text.is_empty() && self.leaf_key().is_none() {
2879                return div().into_any_element();
2880            }
2881            for (_, link_mark) in &mut links {
2882                if let Some(identifier) = link_mark.identifier.as_ref()
2883                    && let Some(mark) = node_cx.link_refs.get(identifier)
2884                {
2885                    *link_mark = mark.clone();
2886                }
2887            }
2888            let highlights = fade_highlights(highlights, &slice_fades(fades, 0, text.len()));
2889            let backgrounds = slice_backgrounds(backgrounds, 0, text.len());
2890            let reveal = node_cx.reveal_at(fade_key, 0, text.len());
2891            if let Ok(mut state) = self.state.lock() {
2892                state.set_text(text);
2893            }
2894            return Inline::new(
2895                self.state.clone(),
2896                links,
2897                highlights,
2898                node_cx.link_click_handler.clone(),
2899            )
2900            .range_backgrounds(backgrounds)
2901            .reveal(reveal)
2902            .into_any_element();
2903        }
2904
2905        let mut child_nodes: Vec<AnyElement> = vec![];
2906
2907        let mut text = String::new();
2908        let mut highlights: Vec<(Range<usize>, InlineHighlight)> = vec![];
2909        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];
2910        let mut offset = 0;
2911        // Where `text` starts in the paragraph's whole rendered text, which
2912        // is the byte space the fade ranges use.
2913        let mut consumed = 0;
2914
2915        for (ix, inline_node) in children.iter().enumerate() {
2916            let text_len = inline_node.text.len();
2917            text.push_str(&inline_node.text);
2918
2919            if let Some(image) = &inline_node.image {
2920                if text.len() > 0 {
2921                    if let Ok(mut state) = inline_node.state.lock() {
2922                        state.set_text(text.clone().into());
2923                    }
2924                    child_nodes.push(
2925                        Inline::new(
2926                            inline_node.state.clone(),
2927                            links.clone(),
2928                            fade_highlights(
2929                                highlights.clone(),
2930                                &slice_fades(fades, consumed, consumed + text.len()),
2931                            ),
2932                            node_cx.link_click_handler.clone(),
2933                        )
2934                        .range_backgrounds(slice_backgrounds(
2935                            backgrounds,
2936                            consumed,
2937                            consumed + text.len(),
2938                        ))
2939                        .reveal(node_cx.reveal_at(fade_key, consumed, consumed + text.len()))
2940                        .into_any_element(),
2941                    );
2942                }
2943                let link_click_handler = node_cx.link_click_handler.clone();
2944                child_nodes.push(
2945                    img(node_cx.image_source(image))
2946                        .id(ix)
2947                        .object_fit(ObjectFit::Contain)
2948                        .max_w(relative(1.))
2949                        .when_some(image.width, |this, width| this.w(width))
2950                        .when_some(image.link.clone(), |this, link| {
2951                            let link_click_handler = link_click_handler.clone();
2952                            let aux_link = link.clone();
2953                            let aux_link_click_handler = link_click_handler.clone();
2954                            this.cursor_pointer()
2955                                .on_click(move |event, window, cx| {
2956                                    crate::TextSelection::end(window, cx);
2957                                    cx.stop_propagation();
2958                                    handle_link_click(
2959                                        &link_click_handler,
2960                                        link.url.clone(),
2961                                        event.clone(),
2962                                        window,
2963                                        cx,
2964                                    );
2965                                })
2966                                .on_aux_click(move |event, window, cx| {
2967                                    crate::TextSelection::end(window, cx);
2968                                    cx.stop_propagation();
2969                                    handle_link_click(
2970                                        &aux_link_click_handler,
2971                                        aux_link.url.clone(),
2972                                        event.clone(),
2973                                        window,
2974                                        cx,
2975                                    );
2976                                })
2977                        })
2978                        .into_any_element(),
2979                );
2980
2981                consumed += text.len();
2982                text.clear();
2983                links.clear();
2984                highlights.clear();
2985                offset = 0;
2986            } else {
2987                let mut node_highlights = vec![];
2988                for (range, style) in &inline_node.marks {
2989                    let inner_range = (offset + range.start)..(offset + range.end);
2990                    let mut highlight = mark_highlight(style, node_cx, cx);
2991
2992                    if let Some(mut link_mark) = style.link.clone() {
2993                        highlight.style.color = Some(node_cx.style.link());
2994                        highlight.style.underline = Some(link_underline(&node_cx.style));
2995
2996                        // convert link references, replace link
2997                        if let Some(identifier) = link_mark.identifier.as_ref() {
2998                            if let Some(mark) = node_cx.link_refs.get(identifier) {
2999                                link_mark = mark.clone();
3000                            }
3001                        }
3002
3003                        links.push((inner_range.clone(), link_mark));
3004                    }
3005
3006                    node_highlights.push((inner_range, highlight));
3007                }
3008
3009                highlights = combine_highlights(highlights, node_highlights);
3010                offset += text_len;
3011            }
3012        }
3013
3014        // Add the last text node
3015        if text.len() > 0 {
3016            let text_end = consumed + text.len();
3017            let highlights = fade_highlights(highlights, &slice_fades(fades, consumed, text_end));
3018            if let Ok(mut state) = self.state.lock() {
3019                state.set_text(text.into());
3020            }
3021            child_nodes.push(
3022                Inline::new(
3023                    self.state.clone(),
3024                    links,
3025                    highlights,
3026                    node_cx.link_click_handler.clone(),
3027                )
3028                .range_backgrounds(slice_backgrounds(backgrounds, consumed, text_end))
3029                .reveal(node_cx.reveal_at(fade_key, consumed, text_end))
3030                .into_any_element(),
3031            );
3032        }
3033
3034        // Text alone is one `Inline`, which needs no box of its own. Images
3035        // keep an identified box: an image's element state is its animation,
3036        // and the box scopes that state per paragraph.
3037        if !has_image {
3038            return child_nodes
3039                .pop()
3040                .unwrap_or_else(|| div().into_any_element());
3041        }
3042
3043        div()
3044            .id(leaf_element_id(fade_key))
3045            .children(child_nodes)
3046            .into_any_element()
3047    }
3048
3049    /// The key an external editor gave this leaf, if any.
3050    fn leaf_key(&self) -> Option<u64> {
3051        self.state.lock().ok().and_then(|state| state.editor_key)
3052    }
3053
3054    fn should_render_inline_flow(&self) -> bool {
3055        let has_image = self.children.iter().any(|child| child.image.is_some());
3056        let has_text = self.children.iter().any(|child| !child.text.is_empty());
3057        // An editor leaf with an image always uses the flow, which reports
3058        // its pieces to the editor (an image-only paragraph would otherwise
3059        // take the plain image path, which reports nothing).
3060        if has_image && self.leaf_key().is_some() {
3061            return true;
3062        }
3063        self.children.iter().any(|child| child.custom.is_some())
3064            || (has_image && has_text)
3065            || self
3066                .children
3067                .iter()
3068                .any(|child| child.marks.iter().any(|(_, mark)| mark.code))
3069    }
3070
3071    fn inline_flow_items(
3072        &self,
3073        leaf_key: Option<TextLeafKey>,
3074        fades: &[(Range<usize>, f32)],
3075        backgrounds: &[(Range<usize>, Hsla)],
3076        node_cx: &NodeContext,
3077        cx: &mut App,
3078    ) -> Vec<InlineFlowItem> {
3079        let mut items = Vec::new();
3080        let mut text = String::new();
3081        let mut highlights: Vec<(Range<usize>, InlineHighlight)> = vec![];
3082        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];
3083        let mut offset = 0;
3084        // Where `text` starts in the paragraph's whole rendered text, which
3085        // is the byte space the fade and background ranges use.
3086        let mut consumed = 0;
3087
3088        for inline_node in &self.children {
3089            if let Some(node) = &inline_node.custom {
3090                if let Ok(mut state) = inline_node.state.lock() {
3091                    state.set_text(text.clone().into());
3092                }
3093                if !text.is_empty() {
3094                    let item_fades = slice_fades(fades, consumed, consumed + text.len());
3095                    let item_backgrounds =
3096                        slice_backgrounds(backgrounds, consumed, consumed + text.len());
3097                    let item_reveal = node_cx.reveal_at(leaf_key, consumed, consumed + text.len());
3098                    consumed += text.len();
3099                    items.push(InlineFlowItem::Text {
3100                        state: inline_node.state.clone(),
3101                        text: std::mem::take(&mut text).into(),
3102                        links: std::mem::take(&mut links),
3103                        highlights: fade_highlights(std::mem::take(&mut highlights), &item_fades),
3104                        backgrounds: item_backgrounds,
3105                        reveal: item_reveal,
3106                    });
3107                }
3108                let mut object_style = HighlightStyle::default();
3109                let mut object_link = None;
3110                for (_, mark) in &inline_node.marks {
3111                    object_style = object_style.highlight(mark_highlight(mark, node_cx, cx).style);
3112                    if let Some(link) = &mark.link {
3113                        object_link = Some(
3114                            link.identifier
3115                                .as_ref()
3116                                .and_then(|id| node_cx.link_refs.get(id))
3117                                .unwrap_or(link)
3118                                .clone(),
3119                        );
3120                        object_style.color = Some(node_cx.style.link());
3121                        object_style.underline = Some(link_underline(&node_cx.style));
3122                    }
3123                }
3124                let rendered_node = node.clone();
3125                let extensions = node_cx.markdown_extensions.clone();
3126                items.push(InlineFlowItem::Object {
3127                    text: node.shared_text(),
3128                    accessibility_label: node.shared_accessibility_name(),
3129                    id: node.source_range().map_or(items.len(), |range| range.start),
3130                    renderer: Arc::new(move |context, window, cx| {
3131                        extensions.render_inline(&rendered_node, context, window, cx)
3132                    }),
3133                    selected: inline_node.custom_selection.clone(),
3134                    style: object_style,
3135                    link: object_link,
3136                });
3137                consumed += inline_node.text.len();
3138                offset = 0;
3139                continue;
3140            }
3141            let text_len = inline_node.text.len();
3142            text.push_str(&inline_node.text);
3143
3144            if let Some(image) = &inline_node.image {
3145                if !text.is_empty() {
3146                    if let Ok(mut state) = inline_node.state.lock() {
3147                        state.set_text(text.clone().into());
3148                    }
3149                    items.push(InlineFlowItem::Text {
3150                        state: inline_node.state.clone(),
3151                        text: text.clone().into(),
3152                        links: links.clone(),
3153                        highlights: fade_highlights(
3154                            highlights.clone(),
3155                            &slice_fades(fades, consumed, consumed + text.len()),
3156                        ),
3157                        backgrounds: slice_backgrounds(
3158                            backgrounds,
3159                            consumed,
3160                            consumed + text.len(),
3161                        ),
3162                        reveal: node_cx.reveal_at(leaf_key, consumed, consumed + text.len()),
3163                    });
3164                }
3165
3166                items.push(InlineFlowItem::Image {
3167                    source: node_cx.image_source(image),
3168                    link: image.link.clone(),
3169                    title: image.title(),
3170                    width: image.width,
3171                    height: image.height,
3172                });
3173
3174                consumed += text.len();
3175                text.clear();
3176                links.clear();
3177                highlights.clear();
3178                offset = 0;
3179            } else {
3180                let mut node_highlights = vec![];
3181                for (range, style) in &inline_node.marks {
3182                    let inner_range = (offset + range.start)..(offset + range.end);
3183                    let mut highlight = mark_highlight(style, node_cx, cx);
3184
3185                    if let Some(mut link_mark) = style.link.clone() {
3186                        highlight.style.color = Some(node_cx.style.link());
3187                        highlight.style.underline = Some(link_underline(&node_cx.style));
3188
3189                        if let Some(identifier) = link_mark.identifier.as_ref()
3190                            && let Some(mark) = node_cx.link_refs.get(identifier)
3191                        {
3192                            link_mark = mark.clone();
3193                        }
3194
3195                        links.push((inner_range.clone(), link_mark));
3196                    }
3197
3198                    node_highlights.push((inner_range, highlight));
3199                }
3200
3201                highlights = combine_highlights(highlights, node_highlights);
3202                offset += text_len;
3203            }
3204        }
3205
3206        if !text.is_empty() {
3207            if let Ok(mut state) = self.state.lock() {
3208                state.set_text(text.clone().into());
3209            }
3210            let highlights = fade_highlights(
3211                highlights,
3212                &slice_fades(fades, consumed, consumed + text.len()),
3213            );
3214            let backgrounds = slice_backgrounds(backgrounds, consumed, consumed + text.len());
3215            let reveal = node_cx.reveal_at(leaf_key, consumed, consumed + text.len());
3216            items.push(InlineFlowItem::Text {
3217                state: self.state.clone(),
3218                text: text.into(),
3219                links,
3220                highlights,
3221                backgrounds,
3222                reveal,
3223            });
3224        }
3225
3226        items
3227    }
3228}
3229
3230/// The element id of a block that needs one, from `kind` and the block's
3231/// source start, which is unique across a parsed Markdown document. The HTML
3232/// parser records no spans, so its blocks fall back to their index among
3233/// their siblings: unique among them, though not across nesting levels.
3234fn block_element_id(kind: &'static str, span: Option<Span>, ix: usize) -> ElementId {
3235    (kind, span.map_or(ix, |span| span.start)).into()
3236}
3237
3238/// The element id of a paragraph that needs one: an inline flow, whose
3239/// objects are accessibility nodes, or an image, whose element state is its
3240/// animation. The leaf key is unique per paragraph in a parsed Markdown
3241/// document; the HTML parser records no spans, so its paragraphs share one.
3242fn leaf_element_id(fade_key: Option<TextLeafKey>) -> ElementId {
3243    fade_key.map_or_else(|| ElementId::from("p"), ElementId::from)
3244}
3245
3246/// A link's underline: a hairline in a translucent link color, so a run of
3247/// links reads as text first and as links second.
3248fn link_underline(style: &TextViewStyle) -> gpui::UnderlineStyle {
3249    gpui::UnderlineStyle {
3250        thickness: px(1.),
3251        color: Some(style.link().opacity(0.4)),
3252        ..Default::default()
3253    }
3254}
3255
3256/// The font size the blocks inherit, which heading sizes and the spacing of
3257/// lists, tables and rules are proportional to (like CSS `em`), so a compact
3258/// 14px view and a 16px document keep the same rhythm.
3259///
3260/// Resolved once per frame into the context; a context built without it
3261/// (as in tests) resolves it from the window.
3262fn body_font_size(node_cx: &NodeContext, window: &Window) -> Pixels {
3263    if node_cx.body_font_size > Pixels::ZERO {
3264        node_cx.body_font_size
3265    } else {
3266        window.text_style().font_size.to_pixels(window.rem_size())
3267    }
3268}
3269
3270/// A heading's `(size scale, line height, section gap)`, the size relative to
3271/// the body text and the gap above it relative to the heading itself.
3272/// Hierarchy comes from weight rather than size: every level is semibold and
3273/// the sizes step down gently from the body.
3274fn heading_metrics(level: u8) -> (f32, f32, f32) {
3275    match level {
3276        1 => (1.8, 1.25, 1.1),
3277        2 => (4. / 3., 1.35, 1.6),
3278        3 => (17. / 15., 1.35, 1.4),
3279        4 => (1., 1.35, 1.4),
3280        5 => (14. / 15., 1.35, 1.4),
3281        _ => (13. / 15., 1.35, 1.4),
3282    }
3283}
3284
3285/// The gap below a heading, relative to the heading's size.
3286const HEADING_BOTTOM_GAP: f32 = 0.35;
3287
3288/// The size a heading renders at, including one a caller's heading
3289/// refinement pins, which its gaps follow.
3290fn heading_size(level: u8, node_cx: &NodeContext, window: &Window) -> Pixels {
3291    node_cx.style.heading(level).text.font_size.map_or_else(
3292        || body_font_size(node_cx, window) * heading_metrics(level).0,
3293        |size| size.to_pixels(window.rem_size()),
3294    )
3295}
3296
3297/// The room above and below a horizontal rule.
3298fn rule_space(body_font_size: Pixels) -> Pixels {
3299    body_font_size * 1.6
3300}
3301
3302/// The gap below a block: a paragraph gap, unless nothing follows it.
3303fn block_gap(options: &NodeRenderOptions, style: &TextViewStyle) -> Rems {
3304    if options.in_list || options.is_last {
3305        rems(0.)
3306    } else {
3307        style.paragraph_gap()
3308    }
3309}
3310
3311/// `block` with `gap` below it. The box only exists to hold the padding,
3312/// so a block with no gap below it is returned as is.
3313fn gapped(block: AnyElement, gap: Rems) -> AnyElement {
3314    if gap.is_zero() {
3315        block
3316    } else {
3317        div().pb(gap).child(block).into_any_element()
3318    }
3319}
3320
3321/// The fade ranges overlapping `start..end`, rebased to start at `start`.
3322fn slice_fades(
3323    fades: &[(Range<usize>, f32)],
3324    start: usize,
3325    end: usize,
3326) -> Vec<(Range<usize>, f32)> {
3327    slice_ranges(fades, start, end, |range, fade_out| (range, *fade_out))
3328}
3329
3330/// The range highlight backgrounds overlapping `start..end`, rebased to
3331/// start at `start`.
3332fn slice_backgrounds(
3333    backgrounds: &[(Range<usize>, Hsla)],
3334    start: usize,
3335    end: usize,
3336) -> Vec<(Range<usize>, Hsla)> {
3337    slice_ranges(backgrounds, start, end, |range, color| (range, *color))
3338}
3339
3340/// A table column's cell widths for layout, excluding the divider after it.
3341struct TableColumn {
3342    /// The width that fits the content on its widest line.
3343    max: f32,
3344    /// The width below which the column stops shrinking.
3345    min: f32,
3346    divider: f32,
3347}
3348
3349/// Horizontal padding of a table cell: `px_3` unless `style.table_cell`
3350/// sets it.
3351fn table_cell_padding_x(style: &TextViewStyle, rem_size: Pixels) -> f32 {
3352    let padding = &style.table_cell().padding;
3353    let side = |length: Option<DefiniteLength>| match length {
3354        Some(DefiniteLength::Absolute(length)) => f32::from(length.to_pixels(rem_size)),
3355        _ => CELL_PAD_PX / 2.,
3356    };
3357    side(padding.left) + side(padding.right)
3358}
3359
3360const CELL_PAD_PX: f32 = 24.0; // px_3 horizontal padding
3361const CELL_MIN_PX: f32 = 48.0;
3362const CELL_BORDER_PX: f32 = 1.0; // border_r_1 drawn by every column but the last
3363
3364/// The max-content width of every table column: the widest cell line,
3365/// shaped with the runs the cell renders with, plus the cell's padding and
3366/// border. Never capped: a cap would clip overflowing text *and* leave it
3367/// outside the scrollable width, making it unreachable.
3368fn measure_table_columns(
3369    table: &Table,
3370    col_count: usize,
3371    node_cx: &NodeContext,
3372    window: &mut Window,
3373    cx: &mut App,
3374) -> Vec<f32> {
3375    let text_styles = TableTextStyles::new(&node_cx.style, window);
3376    let rem_size = window.rem_size();
3377    let mono_family = cx.theme().tokens.typography.mono.clone();
3378    let inline_code = node_cx.style.inline_code_highlight();
3379    let cached = table.column_widths_cache.0.lock().ok().and_then(|cache| {
3380        cache
3381            .as_ref()
3382            .filter(|cached| {
3383                cached.widths.len() == col_count
3384                    && cached.text_styles == text_styles
3385                    && cached.rem_size == rem_size
3386                    && cached.mono_family == mono_family
3387                    && cached.inline_code == inline_code
3388                    && cached.text_system == text_system_id(window)
3389            })
3390            .cloned()
3391    });
3392    let measured = cached.unwrap_or_else(|| {
3393        let (widths, custom_cells) =
3394            measure_table_text_columns(table, col_count, &text_styles, node_cx, window, cx);
3395        let measured = Arc::new(TableColumnWidths {
3396            text_system: text_system_id(window),
3397            text_styles: text_styles.clone(),
3398            rem_size,
3399            mono_family,
3400            inline_code,
3401            widths,
3402            custom_cells,
3403        });
3404        if let Ok(mut cache) = table.column_widths_cache.0.lock() {
3405            *cache = Some(measured.clone());
3406        }
3407        measured
3408    });
3409
3410    // Resource-backed cells must be measured again, even when their text is
3411    // unchanged. Keep callbacks outside the cache lock and do not retain their
3412    // widths: a loaded image or a plugin can get narrower as well as wider.
3413    let mut col_w = measured.widths.clone();
3414    for &(row_ix, ix) in &measured.custom_cells {
3415        let cell = &table.children[row_ix].children[ix];
3416        let items = cell.children.inline_flow_items(None, &[], &[], node_cx, cx);
3417        let style = text_styles.row_refinement(row_ix).clone();
3418        let width = window.with_text_style(Some(style), |window| {
3419            super::inline_flow::intrinsic_width(&items, window, cx)
3420        });
3421        let border = if ix + 1 < col_count {
3422            CELL_BORDER_PX
3423        } else {
3424            0.
3425        };
3426        col_w[ix] = col_w[ix].max(f32::from(width) + CELL_PAD_PX + border);
3427    }
3428    col_w
3429}
3430
3431// The first measurement still visits all ordinary cells. This cache avoids
3432// repeating that work; it is not a budget for the initial table layout.
3433fn measure_table_text_columns(
3434    table: &Table,
3435    col_count: usize,
3436    text_styles: &TableTextStyles,
3437    node_cx: &NodeContext,
3438    window: &mut Window,
3439    cx: &mut App,
3440) -> (Vec<f32>, Vec<(usize, usize)>) {
3441    let mut col_w = vec![CELL_MIN_PX; col_count];
3442    let mut custom_cells = Vec::new();
3443    for (row_ix, row) in table.children.iter().enumerate() {
3444        let text_style = text_styles.row(row_ix);
3445        let font_size = text_style.font_size.to_pixels(window.rem_size());
3446        for (ix, cell) in row.children.iter().enumerate() {
3447            let Some(slot) = col_w.get_mut(ix) else {
3448                continue;
3449            };
3450            if cell
3451                .children
3452                .children
3453                .iter()
3454                .any(|node| node.custom.is_some())
3455            {
3456                custom_cells.push((row_ix, ix));
3457                continue;
3458            }
3459            let text = cell.children.text();
3460            let highlights = cell.children.inline_highlights(node_cx, cx);
3461            let mut w = 0.0_f32;
3462            let mut line_start = 0;
3463            for line in text.split('\n') {
3464                let start = line_start + (line.len() - line.trim_start().len());
3465                let line_end = line_start + line.len();
3466                line_start = line_end + 1;
3467                let line = line.trim();
3468                if line.is_empty() {
3469                    continue;
3470                }
3471                let end = start + line.len();
3472                let line_highlights = highlights
3473                    .iter()
3474                    .filter_map(|(range, highlight)| {
3475                        let clipped = range.start.max(start)..range.end.min(end);
3476                        (clipped.start < clipped.end).then(|| {
3477                            (
3478                                clipped.start - start..clipped.end - start,
3479                                highlight.clone(),
3480                            )
3481                        })
3482                    })
3483                    .collect::<Vec<_>>();
3484                let mut line_w = gpui::Pixels::ZERO;
3485                for (range, scale) in
3486                    text_size_ranges(line.len(), &line_highlights, font_size, window.rem_size())
3487                {
3488                    let highlights = slice_ranges(
3489                        &line_highlights,
3490                        range.start,
3491                        range.end,
3492                        |range, highlight| (range, highlight.clone()),
3493                    );
3494                    let insets = code_insets(&highlights, font_size * scale, window.rem_size());
3495                    line_w += insets.left + insets.right;
3496                    let runs = text_runs(range.len(), text_style, &highlights);
3497                    line_w += window
3498                        .text_system()
3499                        .layout_line(&line[range], font_size * scale, &runs, None)
3500                        .width;
3501                }
3502                w = w.max(f32::from(line_w));
3503            }
3504            // Border-box widths, so the padding and border the cell draws
3505            // must leave the measured text its full width.
3506            let border = if ix + 1 < col_count {
3507                CELL_BORDER_PX
3508            } else {
3509                0.
3510            };
3511            *slot = slot.max(w + CELL_PAD_PX + border);
3512        }
3513    }
3514    (col_w, custom_cells)
3515}
3516
3517impl Paragraph {
3518    fn to_markdown(&self) -> String {
3519        if self.children.iter().any(|node| node.custom.is_some()) {
3520            let mut source = MarkdownSource::default();
3521            for node in &self.children {
3522                if node.custom.is_some() {
3523                    source.push_object(node);
3524                } else {
3525                    source.push_text(&node.text, &node.marks, 0..node.text.len());
3526                    if let Some(image) = &node.image {
3527                        source.push_str(&image_markdown(image));
3528                    }
3529                }
3530            }
3531            let mut text = source.finish();
3532            text.push_str("\n\n");
3533            return text;
3534        }
3535        let mut text = self
3536            .children
3537            .iter()
3538            .map(|text_node| {
3539                let mut text = text_node.text.to_string();
3540                for (range, style) in &text_node.marks {
3541                    if style.bold {
3542                        text = format!("**{}**", &text_node.text[range.clone()]);
3543                    }
3544                    if style.italic {
3545                        text = format!("*{}*", &text_node.text[range.clone()]);
3546                    }
3547                    if style.strikethrough {
3548                        text = format!("~~{}~~", &text_node.text[range.clone()]);
3549                    }
3550                    if style.code {
3551                        text = format!("`{}`", &text_node.text[range.clone()]);
3552                    }
3553                    if style.highlight.is_some() {
3554                        text = format!("=={}==", &text_node.text[range.clone()]);
3555                    }
3556                    if let Some(link) = &style.link {
3557                        text = format!("[{}]({})", &text_node.text[range.clone()], link.url);
3558                    }
3559                }
3560
3561                if let Some(image) = &text_node.image {
3562                    let alt = image.alt.clone().unwrap_or_default();
3563                    let title = image
3564                        .title
3565                        .clone()
3566                        .map_or(String::new(), |t| format!(" \"{}\"", t));
3567                    text.push_str(&format!("![{}]({}{})", alt, image.url, title))
3568                }
3569
3570                text
3571            })
3572            .collect::<Vec<_>>()
3573            .join("");
3574
3575        text.push_str("\n\n");
3576        text
3577    }
3578}
3579
3580impl BlockNode {
3581    /// Converts the node to markdown format.
3582    ///
3583    /// This is used to generate markdown for test.
3584    #[allow(dead_code)]
3585    pub(crate) fn to_markdown(&self) -> String {
3586        match self {
3587            BlockNode::Root { children, .. } => children
3588                .iter()
3589                .map(|child| child.to_markdown())
3590                .collect::<Vec<_>>()
3591                .join("\n\n"),
3592            BlockNode::Paragraph(paragraph) => paragraph.to_markdown(),
3593            BlockNode::Heading {
3594                level, children, ..
3595            } => {
3596                let hashes = "#".repeat(*level as usize);
3597                format!("{} {}", hashes, children.to_markdown())
3598            }
3599            BlockNode::Blockquote { children, .. } => {
3600                let content = children
3601                    .iter()
3602                    .map(|child| child.to_markdown())
3603                    .collect::<Vec<_>>()
3604                    .join("\n\n");
3605
3606                content
3607                    .lines()
3608                    .map(|line| format!("> {}", line))
3609                    .collect::<Vec<_>>()
3610                    .join("\n")
3611            }
3612            BlockNode::List {
3613                children,
3614                ordered,
3615                start,
3616                ..
3617            } => children
3618                .iter()
3619                .enumerate()
3620                .map(|(i, child)| {
3621                    let prefix = if *ordered {
3622                        format!("{}. ", ordered_list_ordinal(*start, i))
3623                    } else {
3624                        "- ".to_string()
3625                    };
3626                    format!("{}{}", prefix, child.to_markdown())
3627                })
3628                .collect::<Vec<_>>()
3629                .join("\n"),
3630            BlockNode::ListItem {
3631                children, checked, ..
3632            } => {
3633                let checkbox = if let Some(checked) = checked {
3634                    if *checked { "[x] " } else { "[ ] " }
3635                } else {
3636                    ""
3637                };
3638                format!(
3639                    "{}{}",
3640                    checkbox,
3641                    children
3642                        .iter()
3643                        .map(|child| child.to_markdown())
3644                        .collect::<Vec<_>>()
3645                        .join("\n")
3646                )
3647            }
3648            BlockNode::CodeBlock(code_block) => {
3649                format!(
3650                    "```{}\n{}\n```",
3651                    code_block.lang.clone().unwrap_or_default(),
3652                    code_block.code()
3653                )
3654            }
3655            BlockNode::Table(table) => table.to_markdown(),
3656            BlockNode::Break { html, .. } => {
3657                if *html {
3658                    "<br>".to_string()
3659                } else {
3660                    "\n".to_string()
3661                }
3662            }
3663            BlockNode::HorizontalRule { .. } => "---".to_string(),
3664            BlockNode::Custom { node, .. } => node.to_markdown(),
3665            BlockNode::Definition {
3666                identifier,
3667                url,
3668                title,
3669                ..
3670            } => {
3671                if let Some(title) = title {
3672                    format!("[{}]: {} \"{}\"", identifier, url, title)
3673                } else {
3674                    format!("[{}]: {}", identifier, url)
3675                }
3676            }
3677            BlockNode::Unknown { .. } => "".to_string(),
3678        }
3679        .trim()
3680        .to_string()
3681    }
3682}
3683
3684impl BlockNode {
3685    fn render_list_item_row(
3686        content: AnyElement,
3687        ix: usize,
3688        options: NodeRenderOptions,
3689        checked: Option<bool>,
3690        style: &TextViewStyle,
3691        line_height: Pixels,
3692        indent: Pixels,
3693    ) -> Div {
3694        h_flex()
3695            .w_full()
3696            .min_w_0()
3697            .relative()
3698            .items_start()
3699            .content_start()
3700            .when(!options.todo && checked.is_none(), |this| {
3701                // The marker hangs in an indent column, right-aligned against
3702                // the text like an outside list marker, and steps back in
3703                // color so the item text leads.
3704                this.child(
3705                    div()
3706                        .flex_none()
3707                        .min_w(indent)
3708                        .pr(indent * 0.3)
3709                        .text_right()
3710                        .text_color(style.muted_foreground())
3711                        .refine_style(style.list_style())
3712                        .child(
3713                            list_item_prefix(
3714                                ix,
3715                                options.list_start,
3716                                options.ordered,
3717                                options.depth,
3718                            )
3719                            .trim_end()
3720                            .to_string(),
3721                        ),
3722                )
3723            })
3724            .when_some(checked, |this, checked| {
3725                // Todo list checkbox
3726                let check_svg = if style.is_dark() {
3727                    CHECK_SVG_DARK
3728                } else {
3729                    CHECK_SVG_LIGHT
3730                };
3731                this.child(
3732                    div()
3733                        .flex()
3734                        .mr_2()
3735                        .h(line_height)
3736                        .flex_none()
3737                        .items_center()
3738                        .justify_center()
3739                        .child(
3740                            div()
3741                                .flex()
3742                                .size(rems(0.9375))
3743                                .items_center()
3744                                .justify_center()
3745                                .rounded(px(4.))
3746                                .border(px(1.5))
3747                                .border_color(
3748                                    style
3749                                        .list_style()
3750                                        .text
3751                                        .color
3752                                        .unwrap_or(style.muted_foreground()),
3753                                )
3754                                .when(checked, |this| {
3755                                    this.bg(style.link()).border_color(style.link()).child(
3756                                        img(Arc::new(Image::from_bytes(
3757                                            ImageFormat::Svg,
3758                                            check_svg.to_vec(),
3759                                        )))
3760                                        .size(rems(0.75)),
3761                                    )
3762                                }),
3763                        ),
3764                )
3765            })
3766            .child(
3767                div()
3768                    // Include the body in intrinsic width measurement, while
3769                    // still letting it shrink and wrap in a constrained row.
3770                    .flex_auto()
3771                    .min_w_0()
3772                    .overflow_hidden()
3773                    // A done task steps back so the open ones stand out.
3774                    .when(checked == Some(true), |this| {
3775                        this.text_color(style.muted_foreground())
3776                    })
3777                    .child(content),
3778            )
3779    }
3780
3781    fn render_list_item(
3782        item: &BlockNode,
3783        ix: usize,
3784        options: NodeRenderOptions,
3785        node_cx: &NodeContext,
3786        window: &mut Window,
3787        cx: &mut App,
3788    ) -> AnyElement {
3789        match item {
3790            BlockNode::ListItem {
3791                children,
3792                spread,
3793                checked,
3794                ..
3795            } => {
3796                let body = body_font_size(node_cx, window);
3797                let indent = body * 1.4;
3798                div()
3799                    .w_full()
3800                    .min_w_0()
3801                    // Items breathe a little apart, less than paragraphs do.
3802                    .when(ix > 0, |this| this.pt(body * 0.25))
3803                    .when(*spread, |this| this.child(div()))
3804                    .children({
3805                        let mut items: Vec<Div> = Vec::with_capacity(children.len());
3806
3807                        for (child_ix, child) in children.iter().enumerate() {
3808                            match child {
3809                                BlockNode::Paragraph { .. } => {
3810                                    let last_not_list = child_ix > 0
3811                                        && !matches!(
3812                                            children[child_ix - 1],
3813                                            BlockNode::List { .. }
3814                                        );
3815
3816                                    let text = child.render_block(
3817                                        NodeRenderOptions {
3818                                            depth: options.depth + 1,
3819                                            todo: checked.is_some(),
3820                                            is_last: true,
3821                                            ..options
3822                                        },
3823                                        node_cx,
3824                                        window,
3825                                        cx,
3826                                    );
3827
3828                                    // Continuation paragraph — stack vertically below
3829                                    // the previous row, indented to align with the text
3830                                    // column (past bullet/number prefix).
3831                                    if last_not_list {
3832                                        if let Some(preceding_row) = items.pop() {
3833                                            items.push(
3834                                                div().child(preceding_row).child(
3835                                                    div()
3836                                                        .w_full()
3837                                                        .pl(indent)
3838                                                        .overflow_hidden()
3839                                                        .child(text),
3840                                                ),
3841                                            );
3842                                            continue;
3843                                        }
3844                                    }
3845
3846                                    items.push(Self::render_list_item_row(
3847                                        text,
3848                                        ix,
3849                                        options,
3850                                        *checked,
3851                                        &node_cx.style,
3852                                        window.line_height(),
3853                                        indent,
3854                                    ));
3855                                }
3856                                BlockNode::List { .. } => {
3857                                    items.push(div().ml(indent).child(child.render_block(
3858                                        NodeRenderOptions {
3859                                            depth: options.depth + 1,
3860                                            todo: checked.is_some(),
3861                                            is_last: true,
3862                                            ..options
3863                                        },
3864                                        node_cx,
3865                                        window,
3866                                        cx,
3867                                    )));
3868                                }
3869                                BlockNode::Root { .. }
3870                                | BlockNode::Heading { .. }
3871                                | BlockNode::Blockquote { .. }
3872                                | BlockNode::CodeBlock(_)
3873                                | BlockNode::Custom { .. }
3874                                | BlockNode::Table(_)
3875                                | BlockNode::HorizontalRule { .. } => {
3876                                    let block = child.render_block(
3877                                        NodeRenderOptions {
3878                                            depth: options.depth + 1,
3879                                            todo: checked.is_some(),
3880                                            is_last: true,
3881                                            ..options
3882                                        },
3883                                        node_cx,
3884                                        window,
3885                                        cx,
3886                                    );
3887
3888                                    if child_ix == 0 {
3889                                        items.push(Self::render_list_item_row(
3890                                            block,
3891                                            ix,
3892                                            options,
3893                                            *checked,
3894                                            &node_cx.style,
3895                                            window.line_height(),
3896                                            indent,
3897                                        ));
3898                                    } else {
3899                                        // Indent continuation blocks to align with a
3900                                        // nested sub-list (`ml(indent)`) and with
3901                                        // continuation paragraphs.
3902                                        items.push(
3903                                            div()
3904                                                .w_full()
3905                                                .min_w_0()
3906                                                .pl(indent)
3907                                                .overflow_hidden()
3908                                                .child(block),
3909                                        );
3910                                    }
3911                                }
3912                                BlockNode::ListItem { .. }
3913                                | BlockNode::Break { .. }
3914                                | BlockNode::Definition { .. }
3915                                | BlockNode::Unknown => {}
3916                            }
3917                        }
3918                        items
3919                    })
3920                    .into_any_element()
3921            }
3922            _ => div().into_any_element(),
3923        }
3924    }
3925
3926    /// Render a Markdown table.
3927    ///
3928    /// Columns are sized from the measured max-content width of their cells,
3929    /// so they line up and fit their content on proportional fonts and mixed
3930    /// scripts alike. The layout adapts to the frame:
3931    ///
3932    /// - Wider than the content: every column fits its content and the spare
3933    ///   width is shared in proportion to it.
3934    /// - Narrower: the narrow columns keep their full content width and only
3935    ///   the widest ones shrink and wrap, all down to one common width (see
3936    ///   [`Self::render_table_rows`]). Shrinking every column by the same
3937    ///   ratio would wrap the last word or character of a column that nearly
3938    ///   fits onto a line of its own.
3939    /// - Narrower than the column floors: the default layout clips, while
3940    ///   `style.table` overflow-x: scroll keeps the floors and scrolls
3941    ///   horizontally, so no content ever becomes unreachable.
3942    ///
3943    /// `white_space: nowrap` on `style.table_cell` composes like in CSS: the
3944    /// refinement keeps cell text on a single line, and the scroll layout
3945    /// raises the floors to the full content widths so the single-line
3946    /// columns never shrink.
3947    fn render_table(
3948        item: &BlockNode,
3949        options: &NodeRenderOptions,
3950        node_cx: &NodeContext,
3951        text_size: Pixels,
3952        window: &mut Window,
3953        cx: &mut App,
3954    ) -> impl IntoElement {
3955        // A scrolling column stops shrinking (and the table starts to scroll)
3956        // at a floor scaled to its content: roughly the width at which the
3957        // text wraps to `CELL_WRAP_MAX_LINES` lines, clamped between the two
3958        // bounds so moderate columns can still wrap meaningfully while one
3959        // huge column cannot push the scroll threshold arbitrarily high.
3960        const CELL_WRAP_MAX_LINES: f32 = 2.0;
3961        const CELL_WRAP_MIN_PX: f32 = 160.0;
3962        const CELL_WRAP_MAX_PX: f32 = 480.0;
3963        const TABLE_BORDER_PX: f32 = 2.0; // the frame's border_1, left + right
3964
3965        let table = match item {
3966            BlockNode::Table(table) => table,
3967            _ => return div().into_any_element(),
3968        };
3969
3970        let style = &node_cx.style;
3971        let col_count = table
3972            .children
3973            .iter()
3974            .map(|row| row.children.len())
3975            .max()
3976            .unwrap_or(0);
3977        let col_w = measure_table_columns(table, col_count, node_cx, window, cx);
3978        let scroll = matches!(style.table().overflow.x, Some(Overflow::Scroll));
3979        let nowrap = style.table_cell().text.white_space == Some(WhiteSpace::Nowrap);
3980        let rem_size = window.rem_size();
3981        let wrap_min_px = f32::from(rems(4.).to_pixels(rem_size));
3982        // Widths were measured with the default cell padding.
3983        let padding_delta = table_cell_padding_x(style, rem_size) - CELL_PAD_PX;
3984        // Lengths are rounded to device pixels for layout. Round the content
3985        // widths up, or a column can lose the fraction of a pixel that keeps
3986        // its text on one line.
3987        let scale_factor = window.scale_factor();
3988        let device_ceil = |width: f32| (width * scale_factor).ceil() / scale_factor;
3989        let columns: Vec<TableColumn> = col_w
3990            .iter()
3991            .enumerate()
3992            .map(|(ix, &w)| {
3993                let w = w + padding_delta;
3994                let floor = if scroll && nowrap {
3995                    w
3996                } else if scroll {
3997                    (w / CELL_WRAP_MAX_LINES)
3998                        .clamp(CELL_WRAP_MIN_PX, CELL_WRAP_MAX_PX)
3999                        .min(w)
4000                } else {
4001                    w.min(wrap_min_px)
4002                };
4003                let divider = if ix + 1 < col_count {
4004                    CELL_BORDER_PX
4005                } else {
4006                    0.
4007                };
4008                let max = device_ceil(w - divider);
4009                TableColumn {
4010                    min: device_ceil(floor - divider).min(max),
4011                    max,
4012                    divider,
4013                }
4014            })
4015            .collect();
4016
4017        let rows = Self::render_table_rows(table, &columns, node_cx, window, cx);
4018        let frame = StyleRefinement::default()
4019            .text_size(text_size)
4020            .bg(style
4021                .table_background()
4022                .unwrap_or(cx.theme().tokens.colors.surface))
4023            .border_1()
4024            .border_color(style.border())
4025            .refine_style(style.table());
4026        let body = if scroll {
4027            let scroll_handle = window
4028                .use_keyed_state(
4029                    block_element_id("table-scroll", table.span, options.ix),
4030                    cx,
4031                    |_, _| ScrollHandle::default(),
4032                )
4033                .read(cx)
4034                .clone();
4035            let min_total_w = columns
4036                .iter()
4037                .map(|column| column.min + column.divider)
4038                .sum::<f32>()
4039                + TABLE_BORDER_PX;
4040            // Scroll viewport owns the visible frame, including any
4041            // caller-provided radius. Keeping the border here makes the
4042            // rounded frame stable while the wider row track moves below it.
4043            //
4044            // `horizontal_scroll_area` clips with `overflow_hidden` and
4045            // delegates the wheel to a sibling `ScrollableMask`, so the
4046            // gesture is locked to its starting axis and a horizontal swipe
4047            // is consumed before an ancestor scroller (`gpui::list` under
4048            // `TextView::scrollable`) can take its vertical component.
4049            horizontal_scroll_area(
4050                block_element_id("table", table.span, options.ix),
4051                &scroll_handle,
4052                &frame,
4053                // Row track sized to `max(viewport, column floors)`:
4054                // `min_w_full` fills the frame while the columns can still
4055                // shrink-to-fit (their text wrapping), the definite
4056                // `w(min_total_w)` keeps the floors once they are reached,
4057                // letting the track exceed the viewport and scroll.
4058                div().min_w_full().w(px(min_total_w)).children(rows),
4059            )
4060            .into_any_element()
4061        } else {
4062            div()
4063                .w_full()
4064                .overflow_hidden()
4065                .refine_style(&frame)
4066                .children(rows)
4067                .into_any_element()
4068        };
4069
4070        div()
4071            .pb(block_gap(options, style))
4072            .w_full()
4073            .child(body)
4074            // Custom actions row (e.g. copy / download) rendered below the
4075            // table. The hook's element spans full width; alignment is up to
4076            // the caller (e.g. `h_flex().justify_end()`). The gap keeps hover
4077            // backgrounds of the action buttons off the table border, and the
4078            // id scopes the caller's element ids per table, so plain ids like
4079            // `"copy"` don't collide across tables (same as code blocks).
4080            .children(node_cx.table_actions.clone().map(|f| {
4081                div()
4082                    .id(block_element_id("table-actions", table.span, options.ix))
4083                    .mt_1()
4084                    .child(f(&table.cached_table_data(), window, cx))
4085            }))
4086            .into_any_element()
4087    }
4088
4089    /// Corner radii for the first and last table rows, derived from the
4090    /// frame radius in `style.table()`.
4091    ///
4092    /// GPUI clips children with a rectangular content mask, so a square row
4093    /// background (e.g. the header fill) pokes out of a rounded table frame
4094    /// at the corners. Rounding the first row's top and the last row's
4095    /// bottom corners to the frame radius — inset by the frame's 1px border
4096    /// sitting between frame and rows — keeps the row fills inside the
4097    /// frame. (A horizontally scrolled track can still meet the viewport
4098    /// corner with a square edge mid-scroll; only a rounded content mask
4099    /// could clip that, and gpui masks are rectangular.)
4100    fn table_row_corner_radii(style: &TextViewStyle, window: &Window) -> [Option<Pixels>; 4] {
4101        let rem_size = window.rem_size();
4102        let inset = |radius: Option<gpui::AbsoluteLength>| {
4103            radius.map(|radius| (radius.to_pixels(rem_size) - px(1.)).max(px(0.)))
4104        };
4105        let radii = &style.table().corner_radii;
4106        [
4107            inset(radii.top_left),
4108            inset(radii.top_right),
4109            inset(radii.bottom_left),
4110            inset(radii.bottom_right),
4111        ]
4112    }
4113
4114    /// The table rows, each a flex row laid out on the same column grid.
4115    ///
4116    /// Every column is a cell plus slack beside it. A cell grows from zero up
4117    /// to its content width `max` and never below its floor `min`:
4118    /// flex layout grows all cells equally and freezes each one that reaches
4119    /// its content width, so the narrow columns fit their content and the
4120    /// widest ones share what is left. The slack grows only after every cell
4121    /// is frozen, so a frame wider than the content spreads the extra width
4122    /// in proportion to the columns, placed after, around or before the cell
4123    /// to follow the column alignment.
4124    ///
4125    /// A row with fewer cells than the table keeps empty cells in their
4126    /// place, so its columns line up with the other rows.
4127    fn render_table_rows(
4128        table: &Table,
4129        columns: &[TableColumn],
4130        node_cx: &NodeContext,
4131        window: &mut Window,
4132        cx: &mut App,
4133    ) -> Vec<AnyElement> {
4134        // The slack's share of the frame while any cell is still growing is
4135        // `SLACK_GROW / (cells * CELL_GROW)`, a negligible fraction of a
4136        // pixel. The slack factors still add up to `SLACK_GROW`: flex layout
4137        // hands out only that fraction of the free space when the factors
4138        // sum to less than one, and the slack must take all of it.
4139        const CELL_GROW: f32 = 1e6;
4140        const SLACK_GROW: f32 = 1.0;
4141
4142        let style = &node_cx.style;
4143        let col_count = columns.len();
4144        let total_w = columns.iter().map(|column| column.max).sum::<f32>().max(1.);
4145        // The slack is part of the column, so it carries the cell's fill and
4146        // the column divider in the cell's border color.
4147        let mut slack_style = StyleRefinement::default();
4148        slack_style.background = style.table_cell().background.clone();
4149        slack_style.border_color = style.table_cell().border_color;
4150        let slack = |grow: f32| {
4151            div()
4152                .flex_basis(px(0.))
4153                .flex_grow(grow)
4154                .flex_shrink(0.)
4155                .border_color(style.border())
4156                .refine_style(&slack_style)
4157        };
4158
4159        let row_count = table.children.len();
4160        let [top_left, top_right, bottom_left, bottom_right] =
4161            Self::table_row_corner_radii(style, window);
4162        let mut rows = Vec::with_capacity(row_count);
4163        let mut cell_ordinal = 0;
4164        for (row_ix, row) in table.children.iter().enumerate() {
4165            let mut cells = Vec::with_capacity(col_count * 2);
4166            for (ix, column) in columns.iter().enumerate() {
4167                let is_last_col = ix + 1 == col_count;
4168                let align = table.column_align(ix);
4169                let content = row.children.get(ix).map(|cell| {
4170                    let fade_key = table
4171                        .span
4172                        .map(|span| TextLeafKey::table_cell(span.start, cell_ordinal));
4173                    cell_ordinal += 1;
4174                    cell.children.render(fade_key, node_cx, window, cx)
4175                });
4176
4177                let grow = SLACK_GROW * column.max / total_w;
4178                let (lead, trail) = match align {
4179                    ColumnumnAlign::None | ColumnumnAlign::Left => (0., grow),
4180                    ColumnumnAlign::Center => (grow / 2., grow / 2.),
4181                    ColumnumnAlign::Right => (grow, 0.),
4182                };
4183                if lead > 0. {
4184                    cells.push(slack(lead).into_any_element());
4185                }
4186                cells.push(
4187                    TableCellElement(
4188                        div()
4189                            .flex_basis(px(0.))
4190                            .flex_grow(CELL_GROW)
4191                            .min_w(px(column.min))
4192                            .max_w(px(column.max))
4193                            .overflow_hidden()
4194                            .when(align == ColumnumnAlign::Center, |this| this.text_center())
4195                            .when(align == ColumnumnAlign::Right, |this| this.text_right())
4196                            .px_3()
4197                            .py(px(7.))
4198                            .refine_style(&style.table_cell())
4199                            .children(content)
4200                            .into_any_element(),
4201                    )
4202                    .into_any_element(),
4203                );
4204                if trail > 0. || !is_last_col {
4205                    cells.push(
4206                        slack(trail)
4207                            .when(!is_last_col, |this| this.border_r_1())
4208                            .into_any_element(),
4209                    );
4210                }
4211            }
4212            rows.push(
4213                div()
4214                    .w_full()
4215                    .when(row_ix < row_count - 1, |this| this.border_b_1())
4216                    .border_color(style.border())
4217                    .flex()
4218                    .flex_row()
4219                    // The first row is the header, as everywhere else that
4220                    // reads a table (`table_data`, `to_markdown`). The
4221                    // refinement comes last so it can override the defaults.
4222                    .when(row_ix == 0, |this| {
4223                        this.bg(style.code_background())
4224                            .text_color(style.foreground())
4225                            .when_some(top_left, |this, radius| this.rounded_tl(radius))
4226                            .when_some(top_right, |this, radius| this.rounded_tr(radius))
4227                            .refine_style(&style.table_head())
4228                    })
4229                    .when(row_ix + 1 == row_count, |this| {
4230                        this.when_some(bottom_left, |this, radius| this.rounded_bl(radius))
4231                            .when_some(bottom_right, |this, radius| this.rounded_br(radius))
4232                    })
4233                    .children(cells)
4234                    .into_any_element(),
4235            );
4236        }
4237        rows
4238    }
4239
4240    pub(crate) fn render_block(
4241        &self,
4242        options: NodeRenderOptions,
4243        node_cx: &NodeContext,
4244        window: &mut Window,
4245        cx: &mut App,
4246    ) -> AnyElement {
4247        let mb = block_gap(&options, &node_cx.style);
4248
4249        match self {
4250            BlockNode::Root { children, .. } => div()
4251                .children(children.into_iter().enumerate().map(move |(ix, node)| {
4252                    // A nested root (an HTML block container) continues the
4253                    // flow around it: until it shows a block of its own, it
4254                    // stands where the container stands.
4255                    let (is_first, prev) = match flow_position(children, ix) {
4256                        (true, _) => (options.is_first, options.prev),
4257                        (false, prev) => (false, prev),
4258                    };
4259                    node.render_block(
4260                        NodeRenderOptions {
4261                            ix,
4262                            is_first,
4263                            prev,
4264                            is_last: options.is_last && ix + 1 == children.len(),
4265                            ..options
4266                        },
4267                        node_cx,
4268                        window,
4269                        cx,
4270                    )
4271                }))
4272                .into_any_element(),
4273            BlockNode::Paragraph(paragraph) => gapped(
4274                paragraph.render(
4275                    paragraph.span.map(|span| TextLeafKey::block(span.start)),
4276                    node_cx,
4277                    window,
4278                    cx,
4279                ),
4280                mb,
4281            ),
4282            BlockNode::Heading {
4283                level,
4284                children,
4285                span,
4286            } => {
4287                let (_, line_height, section_gap) = heading_metrics(*level);
4288                let refinement = node_cx.style.heading(*level);
4289                let text_size = heading_size(*level, node_cx, window);
4290                // The previous block already leaves a gap below itself, so
4291                // only the rest of the section gap is added here -- the two
4292                // collapse like CSS margins. A heading right after another one
4293                // belongs to the same section and stays close. Nested in a
4294                // list item, a heading sits flush with the item.
4295                let top = if options.is_first || options.depth > 0 {
4296                    px(0.)
4297                } else {
4298                    match options.prev {
4299                        PrevBlock::Heading(_) => text_size * 0.25,
4300                        PrevBlock::Rule => (text_size * section_gap
4301                            - rule_space(body_font_size(node_cx, window)))
4302                        .max(px(0.)),
4303                        PrevBlock::Other => (text_size * section_gap
4304                            - node_cx.style.paragraph_gap().to_pixels(window.rem_size()))
4305                        .max(px(0.)),
4306                    }
4307                };
4308                let bottom = if options.is_last {
4309                    px(0.)
4310                } else {
4311                    text_size * HEADING_BOTTOM_GAP
4312                };
4313
4314                div()
4315                    .pt(top)
4316                    .pb(bottom)
4317                    .whitespace_normal()
4318                    .text_size(text_size)
4319                    .line_height(relative(line_height))
4320                    .font_weight(FontWeight::SEMIBOLD)
4321                    .when(*level >= 6, |this| {
4322                        this.text_color(node_cx.style.muted_foreground())
4323                    })
4324                    .refine_style(&refinement)
4325                    .child(children.render(
4326                        span.map(|span| TextLeafKey::block(span.start)),
4327                        node_cx,
4328                        window,
4329                        cx,
4330                    ))
4331                    .into_any_element()
4332            }
4333            BlockNode::Blockquote { children, .. } => gapped(
4334                div()
4335                    .w_full()
4336                    .text_color(node_cx.style.muted_foreground())
4337                    .border_l_2()
4338                    .border_color(node_cx.style.border())
4339                    .pl(body_font_size(node_cx, window) * 0.9)
4340                    .refine_style(node_cx.style.blockquote_style())
4341                    // Nested tables measure while rendering, before this div applies its text style.
4342                    .children({
4343                        let children_len = children.len();
4344                        children.into_iter().enumerate().map(move |(index, c)| {
4345                            let is_last = index == children_len - 1;
4346                            let (is_first, prev) = flow_position(children, index);
4347                            window.with_text_style(
4348                                Some(node_cx.style.blockquote_style().text.clone()),
4349                                |window| {
4350                                    c.render_block(
4351                                        NodeRenderOptions {
4352                                            is_first,
4353                                            prev,
4354                                            ..options.is_last(is_last)
4355                                        },
4356                                        node_cx,
4357                                        window,
4358                                        cx,
4359                                    )
4360                                },
4361                            )
4362                        })
4363                    })
4364                    .into_any_element(),
4365                mb,
4366            ),
4367            BlockNode::List {
4368                children,
4369                ordered,
4370                start,
4371                ..
4372            } => div()
4373                .w_full()
4374                .min_w_0()
4375                .pb(mb)
4376                .children({
4377                    let mut items = Vec::with_capacity(children.len());
4378                    let mut item_index = 0;
4379                    for (ix, item) in children.into_iter().enumerate() {
4380                        let is_item = item.is_list_item();
4381
4382                        items.push(Self::render_list_item(
4383                            item,
4384                            item_index,
4385                            NodeRenderOptions {
4386                                ix,
4387                                ordered: *ordered,
4388                                list_start: *start,
4389                                ..options
4390                            },
4391                            node_cx,
4392                            window,
4393                            cx,
4394                        ));
4395
4396                        if is_item {
4397                            item_index += 1;
4398                        }
4399                    }
4400                    items
4401                })
4402                .into_any_element(),
4403            BlockNode::CodeBlock(code_block) => code_block.render(&options, node_cx, window, cx),
4404            BlockNode::Custom { node, state } => {
4405                let inner = match node_cx.markdown_extensions.render_block(node, window, cx) {
4406                    Some(rendered) => rendered,
4407                    None => div().child(node.as_text().to_string()).into_any_element(),
4408                };
4409
4410                div()
4411                    .pb(mb)
4412                    .child(CustomBlockElement {
4413                        content: inner,
4414                        state: state.clone(),
4415                    })
4416                    .into_any_element()
4417            }
4418            BlockNode::Table { .. } => {
4419                // Tables are data and read a step denser than the body. The
4420                // columns are measured while rendering, so they are measured
4421                // at the same size the cells are laid out with; the actions
4422                // row below keeps the body size.
4423                let text_size = body_font_size(node_cx, window) * (14. / 15.);
4424                window.with_text_style(
4425                    Some(TextStyleRefinement {
4426                        font_size: Some(text_size.into()),
4427                        ..Default::default()
4428                    }),
4429                    |window| {
4430                        Self::render_table(self, &options, node_cx, text_size, window, cx)
4431                            .into_any_element()
4432                    },
4433                )
4434            }
4435            BlockNode::HorizontalRule { .. } => {
4436                // A section break: a hairline with more room around it than
4437                // a paragraph gap. The previous block's own gap counts toward
4438                // the space above, as with headings.
4439                let space = rule_space(body_font_size(node_cx, window));
4440                let top = if options.is_first || options.depth > 0 {
4441                    px(0.)
4442                } else {
4443                    let above = match options.prev {
4444                        PrevBlock::Rule => space,
4445                        PrevBlock::Heading(level) => {
4446                            heading_size(level, node_cx, window) * HEADING_BOTTOM_GAP
4447                        }
4448                        PrevBlock::Other => {
4449                            node_cx.style.paragraph_gap().to_pixels(window.rem_size())
4450                        }
4451                    };
4452                    (space - above).max(px(0.))
4453                };
4454                div()
4455                    .pt(top)
4456                    .when(!options.is_last, |this| this.pb(space))
4457                    .child(div().bg(node_cx.style.border()).h(px(1.)))
4458                    .into_any_element()
4459            }
4460            BlockNode::Break { .. } => div().into_any_element(),
4461            BlockNode::Unknown { .. } | BlockNode::Definition { .. } => div().into_any_element(),
4462            _ => {
4463                if cfg!(debug_assertions) {
4464                    tracing::warn!("unknown implementation: {:?}", self);
4465                }
4466
4467                div().into_any_element()
4468            }
4469        }
4470    }
4471}
4472
4473#[cfg(test)]
4474mod tests {
4475    use super::*;
4476
4477    #[test]
4478    fn block_plugin_selection_copies_both_formats_and_clears_synchronously() {
4479        let source = "$$\nx\n$$";
4480        let mut node = MarkdownNode::new("math", ()).text("x").markdown(source);
4481        node.set_span(Some(Span {
4482            start: 0,
4483            end: source.len(),
4484        }));
4485        let state = BlockState::default();
4486        let same_content = BlockNode::Custom {
4487            node: node.clone(),
4488            state: BlockState::default(),
4489        };
4490        let block = BlockNode::Custom {
4491            node,
4492            state: state.clone(),
4493        };
4494        let document = crate::text::document::ParsedDocument {
4495            source: source.into(),
4496            blocks: Arc::new(vec![block.clone()]),
4497        };
4498        assert!(!block.has_selection());
4499        state.set_selection(Some(BlockSelection::Full));
4500        // Runtime selection does not affect parsed content equality.
4501        assert_eq!(block, same_content);
4502        for blocks in [None, Some(0..=0)] {
4503            assert_eq!(
4504                document.selected_text(SelectionFormat::Plain, blocks.clone()),
4505                "x\n"
4506            );
4507            assert_eq!(
4508                document.selected_text(SelectionFormat::Source, blocks),
4509                source
4510            );
4511        }
4512        assert_eq!(document.selected_source_range(), Some(0..source.len()));
4513
4514        // A clone shares the retained state, including clearing before repaint.
4515        block.clear_selection();
4516        assert!(!document.blocks[0].has_selection());
4517        assert_eq!(
4518            document.selected_text(SelectionFormat::Plain, Some(0..=0)),
4519            ""
4520        );
4521        assert_eq!(document.selected_source_range(), None);
4522    }
4523
4524    #[test]
4525    fn block_plugin_empty_endpoint_suppresses_virtualized_copy_fallback() {
4526        let state = BlockState::default();
4527        let block = BlockNode::Custom {
4528            node: MarkdownNode::new("math", ())
4529                .text("formula")
4530                .markdown("$$formula$$"),
4531            state: state.clone(),
4532        };
4533        let after = CodeBlock::from_code("after", None::<SharedString>);
4534        after.set_selection(0..5);
4535        let document = crate::text::document::ParsedDocument {
4536            blocks: Arc::new(vec![block, BlockNode::CodeBlock(after)]),
4537            ..Default::default()
4538        };
4539        assert!(
4540            document
4541                .selected_text(SelectionFormat::Plain, Some(0..=1))
4542                .contains("formula")
4543        );
4544        state.set_selection(Some(BlockSelection::Empty));
4545        assert!(document.blocks[0].has_selection());
4546        for format in [SelectionFormat::Plain, SelectionFormat::Source] {
4547            let text = document.selected_text(format, Some(0..=1));
4548            assert!(text.contains("after"));
4549            assert!(!text.contains("formula"));
4550        }
4551    }
4552
4553    #[test]
4554    fn block_plugin_geometry_selects_whole_blocks_in_both_drag_directions() {
4555        let bounds = Bounds::new(gpui::point(px(10.), px(20.)), gpui::size(px(100.), px(40.)));
4556        let inside = bounds.center();
4557        let above = gpui::point(px(0.), px(0.));
4558        let below = gpui::point(px(0.), px(80.));
4559        for (start, end) in [(above, inside), (inside, below), (above, below)] {
4560            assert!(custom_block_is_selected(bounds, start, end));
4561            assert!(custom_block_is_selected(bounds, end, start));
4562        }
4563        assert!(!custom_block_is_selected(bounds, inside, inside));
4564        // Pointer travel during a click, and any drag that never leaves the
4565        // block, select nothing.
4566        for (start, end) in [
4567            (inside, inside + gpui::point(px(1.), px(0.))),
4568            (
4569                bounds.origin,
4570                bounds.bottom_right() - gpui::point(px(1.), px(1.)),
4571            ),
4572        ] {
4573            assert!(!custom_block_is_selected(bounds, start, end));
4574            assert!(!custom_block_is_selected(bounds, end, start));
4575        }
4576        assert!(!custom_block_is_selected(
4577            bounds,
4578            gpui::point(px(150.), px(30.)),
4579            gpui::point(px(160.), px(40.))
4580        ));
4581    }
4582
4583    #[test]
4584    fn selected_inline_objects_coalesce_surrounding_emphasis() {
4585        for (object_mark, expected) in [
4586            (TextMark::default().italic(), "*fore $x$ aft*"),
4587            (TextMark::default().italic().bold(), "*fore **$x$** aft*"),
4588        ] {
4589            let italic = TextMark::default().italic();
4590            let before = InlineNode::new("before ").marks(vec![(0..7, italic.clone())]);
4591            let formula =
4592                InlineNode::custom(MarkdownNode::new("math", ()).text("x").markdown("$x$"))
4593                    .marks(vec![(0..1, object_mark)]);
4594            let after = InlineNode::new(" after").marks(vec![(0..6, italic)]);
4595            {
4596                let mut state = formula.state.lock().unwrap();
4597                state.text = "before ".into();
4598                state.selection = Some((2..7).into());
4599            }
4600            *formula.custom_selection.lock().unwrap() = true;
4601            let paragraph = Paragraph {
4602                children: vec![before, formula, after],
4603                ..Default::default()
4604            };
4605            {
4606                let mut state = paragraph.state.lock().unwrap();
4607                state.text = " after".into();
4608                state.selection = Some((0..4).into());
4609            }
4610            assert_eq!(paragraph.selected_source(), expected);
4611        }
4612    }
4613
4614    #[test]
4615    fn consecutive_inline_objects_copy_atomically_without_neighboring_text() {
4616        let first =
4617            InlineNode::custom(MarkdownNode::new("math", ()).text("甲²").markdown("$甲^2$"));
4618        let second = InlineNode::custom(MarkdownNode::new("math", ()).text("b").markdown("$b$"));
4619        *first.custom_selection.lock().unwrap() = true;
4620        *second.custom_selection.lock().unwrap() = true;
4621        let paragraph = Paragraph {
4622            children: vec![first, second],
4623            ..Default::default()
4624        };
4625        assert_eq!(paragraph.selected_text(), "甲²b");
4626        assert_eq!(paragraph.selected_source(), "$甲^2$$b$");
4627        assert!(paragraph.has_selection());
4628        paragraph.clear_selection();
4629        assert_eq!(paragraph.selected_text(), "");
4630        assert_eq!(paragraph.selected_source(), "");
4631        assert!(!paragraph.has_selection());
4632    }
4633
4634    #[test]
4635    fn selected_inline_object_interleaves_runs_and_preserves_enclosing_mark() {
4636        let before = InlineNode::new("中文 ");
4637        let formula =
4638            InlineNode::custom(MarkdownNode::new("math", ()).text("x²").markdown("$x^2$"))
4639                .marks(vec![(0..3, TextMark::default().bold())]);
4640        let after = InlineNode::new(" English");
4641        {
4642            let mut preceding = formula.state.lock().unwrap();
4643            preceding.text = "中文 ".into();
4644            preceding.selection = Some((3..7).into());
4645        }
4646        *formula.custom_selection.lock().unwrap() = true;
4647        let paragraph = Paragraph {
4648            children: vec![before, formula, after],
4649            ..Default::default()
4650        };
4651        {
4652            let mut trailing = paragraph.state.lock().unwrap();
4653            trailing.text = " English".into();
4654            trailing.selection = Some((0..4).into());
4655        }
4656        assert_eq!(paragraph.selected_text(), "文 x² Eng");
4657        assert_eq!(paragraph.selected_source(), "文 **$x^2$** Eng");
4658    }
4659
4660    #[test]
4661    fn mark_sets_a_readable_text_color_with_its_background() {
4662        use gpui::{Empty, TestApp};
4663        let mut app = TestApp::new();
4664        let mut window = app.open_window(|_, _| Empty);
4665        window.update(|_, _, cx| {
4666            cx.set_global(crate::Theme::default());
4667            let node_cx = NodeContext::default();
4668
4669            // The default `<mark>` yellow needs dark text, whatever the theme's
4670            // text color is (light in a dark theme).
4671            let yellow: Hsla = gpui::rgb(0xfef08a).into();
4672            let style = mark_highlight(&TextMark::default().highlight(yellow), &node_cx, cx).style;
4673            assert_eq!(style.background_color, Some(yellow));
4674            assert_eq!(style.color, Some(gpui::black()));
4675
4676            // A dark custom highlight gets light text instead.
4677            let navy: Hsla = gpui::rgb(0x1e3a8a).into();
4678            let style = mark_highlight(&TextMark::default().highlight(navy), &node_cx, cx).style;
4679            assert_eq!(style.color, Some(gpui::white()));
4680
4681            // A translucent mark is composited with the theme surface before
4682            // choosing the text color. Fully transparent marks keep inherited ink.
4683            let translucent_black: Hsla = gpui::rgba(0x00000080).into();
4684            let translucent_white: Hsla = gpui::rgba(0xffffff4d).into();
4685            assert_eq!(
4686                mark_text_color(translucent_black, gpui::white()),
4687                gpui::black()
4688            );
4689            assert_eq!(
4690                mark_text_color(translucent_white, gpui::black()),
4691                gpui::white()
4692            );
4693            let transparent: Hsla = gpui::rgba(0x00000000).into();
4694            let style =
4695                mark_highlight(&TextMark::default().highlight(transparent), &node_cx, cx).style;
4696            assert_eq!(style.color, None);
4697
4698            // Text that is not marked keeps the inherited color.
4699            let style = mark_highlight(&TextMark::default().bold(), &node_cx, cx).style;
4700            assert_eq!(style.color, None);
4701        });
4702    }
4703
4704    #[test]
4705    fn translucent_mark_cache_tracks_theme_background() {
4706        use gpui::{Empty, TestApp};
4707        let mut app = TestApp::new();
4708        let mut window = app.open_window(|_, _| Empty);
4709        window.update(|_, _, cx| {
4710            cx.set_global(crate::Theme::default());
4711            let paragraph = Paragraph {
4712                children: vec![InlineNode::new("mark").marks(vec![(
4713                    0..4,
4714                    TextMark::default().highlight(gpui::rgba(0x00000033).into()),
4715                )])],
4716                ..Default::default()
4717            };
4718            let context = NodeContext::default();
4719            crate::Theme::global_mut(cx).tokens.colors.background = gpui::white();
4720            let (_, light, _) = paragraph.plain_render(&context, cx);
4721            assert_eq!(light[0].1.style.color, Some(gpui::black()));
4722            crate::Theme::global_mut(cx).tokens.colors.background = gpui::black();
4723            let (_, dark, _) = paragraph.plain_render(&context, cx);
4724            assert_eq!(dark[0].1.style.color, Some(gpui::white()));
4725        });
4726    }
4727
4728    #[test]
4729    fn custom_inline_inherits_marks_and_resolves_link_references() {
4730        use gpui::{Empty, TestApp};
4731        let mut app = TestApp::new();
4732        let mut window = app.open_window(|_, _| Empty);
4733        window.update(|_, _, cx| {
4734            cx.set_global(crate::Theme::default());
4735            let mut node_cx = NodeContext::default();
4736            let mut mark = TextMark::default().bold();
4737            mark.italic = true;
4738            mark.strikethrough = true;
4739            mark.link = Some(LinkMark {
4740                identifier: Some("ref".into()),
4741                ..Default::default()
4742            });
4743            node_cx.link_refs.insert(
4744                "ref".into(),
4745                LinkMark {
4746                    url: "https://example.com".into(),
4747                    ..Default::default()
4748                },
4749            );
4750            let paragraph = Paragraph {
4751                children: vec![
4752                    InlineNode::custom(MarkdownNode::new("test", ()).text("x"))
4753                        .marks(vec![(0..1, mark)]),
4754                ],
4755                ..Default::default()
4756            };
4757            let items = paragraph.inline_flow_items(None, &[], &[], &node_cx, cx);
4758            let InlineFlowItem::Object { style, link, .. } = &items[0] else {
4759                panic!()
4760            };
4761            assert_eq!(style.font_weight, Some(FontWeight::BOLD));
4762            assert_eq!(style.font_style, Some(FontStyle::Italic));
4763            assert!(style.strikethrough.is_some());
4764            assert_eq!(link.as_ref().unwrap().url.as_ref(), "https://example.com");
4765        });
4766    }
4767
4768    #[test]
4769    fn table_column_uses_prepared_inline_metrics_after_resource_update() {
4770        use crate::text::InlineElement;
4771        use crate::text::inline::test_draw::in_prepaint;
4772        use gpui::{Image, ImageFormat, TestApp};
4773        let mut app = TestApp::new();
4774        let width = Arc::new(std::sync::atomic::AtomicUsize::new(400));
4775        let render_width = width.clone();
4776        let mut node_cx = NodeContext::default();
4777        node_cx.markdown_extensions = Arc::new(MarkdownExtensions::default().plugin(
4778            crate::text::markdown_ext::TestInlinePlugin::new("test").render_with(
4779                move |_, _, _, _| {
4780                    Some(
4781                        InlineElement::new(
4782                            gpui::img(Arc::new(Image::from_bytes(
4783                                ImageFormat::Svg,
4784                                b"<svg/>".to_vec(),
4785                            )))
4786                            .w(px(
4787                                render_width.load(std::sync::atomic::Ordering::Relaxed) as f32
4788                            ))
4789                            .h(px(20.)),
4790                        )
4791                        .with_baseline(px(15.)),
4792                    )
4793                },
4794            ),
4795        ));
4796        let table = table_of(
4797            vec![
4798                vec![plain_cell("ordinary text")],
4799                vec![TableCell {
4800                    children: Paragraph {
4801                        children: vec![InlineNode::custom(MarkdownNode::new("test", ()).text("x"))],
4802                        ..Default::default()
4803                    },
4804                    width: None,
4805                }],
4806            ],
4807            vec![],
4808        );
4809        in_prepaint(&mut app, move |window, cx| {
4810            let mut cached = None;
4811            for expected in [400, 600, 10, 400] {
4812                width.store(expected, std::sync::atomic::Ordering::Relaxed);
4813                let measured = measure_table_columns(&table, 1, &node_cx, window, cx)[0];
4814                let current = cached_column_widths(&table);
4815                assert_eq!(
4816                    measured,
4817                    current.widths[0].max(expected as f32 + CELL_PAD_PX)
4818                );
4819                assert_eq!(current.custom_cells, vec![(1, 0)]);
4820                if let Some(cached) = cached.replace(current.clone()) {
4821                    assert!(Arc::ptr_eq(&cached, &current));
4822                }
4823            }
4824        });
4825    }
4826
4827    fn cached_column_widths(table: &Table) -> Arc<TableColumnWidths> {
4828        table.column_widths_cache.0.lock().unwrap().clone().unwrap()
4829    }
4830
4831    #[test]
4832    fn blockquote_typography_reaches_nested_table_measurement() {
4833        use crate::text::inline::test_draw::in_prepaint;
4834        use crate::text::inline::test_fonts::{BODY, MONO, WideMonoTextSystem};
4835        use gpui::TestApp;
4836
4837        let quote = BlockNode::Blockquote {
4838            children: vec![BlockNode::Table(table_of(
4839                vec![vec![plain_cell("0123456789")]],
4840                vec![],
4841            ))],
4842            span: None,
4843        };
4844        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
4845        in_prepaint(&mut app, move |window, cx| {
4846            cx.set_global(crate::Theme::default());
4847            window.with_text_style(
4848                Some(
4849                    StyleRefinement::default()
4850                        .font_family(BODY)
4851                        .text_size(px(15.))
4852                        .text,
4853                ),
4854                |window| {
4855                    for (refinement, expected_width) in [
4856                        (StyleRefinement::default(), 94.),
4857                        (
4858                            StyleRefinement::default()
4859                                .font_family(MONO)
4860                                .text_size(px(30.))
4861                                .font_weight(FontWeight::BOLD),
4862                            199.,
4863                        ),
4864                    ] {
4865                        let node_cx = NodeContext {
4866                            style: Arc::new(
4867                                TextViewStyle::default().with_blockquote_style(refinement),
4868                            ),
4869                            body_font_size: px(15.),
4870                            ..Default::default()
4871                        };
4872                        quote.render_block(NodeRenderOptions::default(), &node_cx, window, cx);
4873                        let BlockNode::Blockquote { children, .. } = &quote else {
4874                            unreachable!()
4875                        };
4876                        let BlockNode::Table(table) = &children[0] else {
4877                            unreachable!()
4878                        };
4879                        // Ten glyphs at the table's 14px size, plus 24px cell padding.
4880                        assert_eq!(cached_column_widths(table).widths, vec![expected_width]);
4881                    }
4882                },
4883            );
4884        });
4885    }
4886
4887    #[test]
4888    fn table_columns_cache_preserves_plain_widths_and_clones() {
4889        use crate::text::inline::test_draw::in_prepaint;
4890        use crate::text::inline::test_fonts::{BODY, WideMonoTextSystem, record_shaped_lines};
4891        use gpui::{Refineable as _, TestApp, TextStyle};
4892
4893        let long = "é界".repeat(2048);
4894        let table = table_of(
4895            vec![
4896                vec![plain_cell(&format!(" \t{long} \nshort\n ")), plain_cell("")],
4897                vec![plain_cell("short")],
4898                vec![],
4899                vec![plain_cell(""), plain_cell("a\nabcdefghijklmnop")],
4900            ],
4901            vec![],
4902        );
4903        let uncached = table.clone();
4904        let node_cx = NodeContext::default();
4905        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
4906        let (table, node_cx, cached) = in_prepaint(&mut app, move |window, cx| {
4907            window.set_rem_size(px(20.));
4908            let style = TextStyle {
4909                font_family: BODY.into(),
4910                font_size: rems(1.).into(),
4911                ..Default::default()
4912            };
4913            let cached =
4914                window.with_text_style(Some(style.subtract(&Default::default())), |window| {
4915                    let (widths, shaped) = record_shaped_lines(|| {
4916                        measure_table_columns(&table, 2, &node_cx, window, cx)
4917                    });
4918                    assert_eq!(widths, vec![40960. + CELL_PAD_PX + CELL_BORDER_PX, 184.]);
4919                    assert!(shaped.iter().any(|line| line == &long));
4920                    assert_eq!(
4921                        table, uncached,
4922                        "derived widths do not change table equality"
4923                    );
4924                    assert!(uncached.column_widths_cache.0.lock().unwrap().is_none());
4925
4926                    let cached = cached_column_widths(&table);
4927                    let cloned = table.clone();
4928                    for same in [&table, &cloned] {
4929                        let (again, shaped) = record_shaped_lines(|| {
4930                            measure_table_columns(same, 2, &node_cx, window, cx)
4931                        });
4932                        assert_eq!(again, widths);
4933                        assert!(shaped.is_empty());
4934                        assert!(Arc::ptr_eq(&cached, &cached_column_widths(same)));
4935                    }
4936
4937                    // A different column count changes which cells draw borders.
4938                    assert_eq!(
4939                        measure_table_columns(&cloned, 3, &node_cx, window, cx),
4940                        vec![widths[0], widths[1] + CELL_BORDER_PX, CELL_MIN_PX]
4941                    );
4942                    assert!(!Arc::ptr_eq(&cached, &cached_column_widths(&cloned)));
4943                    assert!(Arc::ptr_eq(&cached, &cached_column_widths(&table)));
4944                    assert!(
4945                        measure_table_columns(&Table::default(), 0, &node_cx, window, cx)
4946                            .is_empty()
4947                    );
4948                    cached
4949                });
4950            (table, node_cx, cached)
4951        });
4952        // A parsed table can be rendered in another window. Do not reuse a
4953        // pixel-width cache from a different window's text system.
4954        in_prepaint(&mut app, move |window, cx| {
4955            window.set_rem_size(cached.rem_size);
4956            window.with_text_style(
4957                Some(cached.text_styles.body.subtract(&Default::default())),
4958                |window| {
4959                    measure_table_columns(&table, 2, &node_cx, window, cx);
4960                    assert!(!Arc::ptr_eq(&cached, &cached_column_widths(&table)));
4961                },
4962            );
4963        });
4964    }
4965
4966    #[test]
4967    fn table_columns_cache_tracks_typography() {
4968        use crate::text::inline::test_draw::in_prepaint;
4969        use crate::text::inline::test_fonts::{BODY, MONO, WideMonoTextSystem};
4970        use gpui::{Refineable as _, TestApp, TextStyle};
4971
4972        let code = "0123456789";
4973        let mut paragraph = Paragraph::default();
4974        paragraph
4975            .push(InlineNode::new(code).marks(vec![(0..code.len(), TextMark::default().code())]));
4976        let table = table_of(
4977            vec![vec![
4978                plain_cell(code),
4979                TableCell {
4980                    children: paragraph,
4981                    width: None,
4982                },
4983            ]],
4984            vec![],
4985        );
4986        let base = TextStyle {
4987            font_family: BODY.into(),
4988            font_size: rems(1.).into(),
4989            ..Default::default()
4990        };
4991        let mut larger = base.clone();
4992        larger.font_size = rems(2.).into();
4993        let mut bold = base.clone();
4994        bold.font_weight = FontWeight::BOLD;
4995        let mut mono = base.clone();
4996        mono.font_family = MONO.into();
4997        let normal_code = HighlightStyle::default();
4998        let bold_code = HighlightStyle {
4999            font_weight: Some(FontWeight::BOLD),
5000            ..Default::default()
5001        };
5002        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
5003        in_prepaint(&mut app, move |window, cx| {
5004            let mut previous = None;
5005            for (style, rem_size, family, inline_code, expected) in [
5006                (base.clone(), 20., MONO, normal_code, [125., 207.]),
5007                (base.clone(), 30., MONO, normal_code, [175., 294.5]),
5008                (larger, 20., MONO, normal_code, [225., 382.]),
5009                (bold, 20., MONO, normal_code, [175., 250.75]),
5010                (mono, 20., MONO, normal_code, [225., 207.]),
5011                (base.clone(), 20., MONO, normal_code, [125., 207.]),
5012                (base.clone(), 20., BODY, normal_code, [125., 119.5]),
5013                (base.clone(), 20., MONO, normal_code, [125., 207.]),
5014                (base, 20., MONO, bold_code, [125., 250.75]),
5015            ] {
5016                let mut theme = crate::Theme::default();
5017                theme.tokens.typography.mono = family.into();
5018                cx.set_global(theme);
5019                window.set_rem_size(px(rem_size));
5020                let node_cx = NodeContext {
5021                    style: Arc::new(TextViewStyle::default().with_inline_code(inline_code)),
5022                    ..Default::default()
5023                };
5024                window.with_text_style(Some(style.subtract(&Default::default())), |window| {
5025                    let widths = measure_table_columns(&table, 2, &node_cx, window, cx);
5026                    for (width, expected) in widths.iter().zip(expected) {
5027                        assert!((width - expected).abs() < 0.01, "{width} != {expected}");
5028                    }
5029                    let cached = cached_column_widths(&table);
5030                    if let Some(previous) = previous.replace(cached.clone()) {
5031                        assert!(!Arc::ptr_eq(&previous, &cached));
5032                    }
5033                    assert_eq!(
5034                        measure_table_columns(&table, 2, &node_cx, window, cx),
5035                        widths
5036                    );
5037                    assert!(Arc::ptr_eq(&cached, &cached_column_widths(&table)));
5038                });
5039            }
5040        });
5041    }
5042
5043    #[test]
5044    fn table_columns_cache_is_rebuilt_for_reparsed_tables() {
5045        use crate::text::format::markdown;
5046        use crate::text::inline::test_draw::in_prepaint;
5047        use crate::text::inline::test_fonts::{BODY, WideMonoTextSystem};
5048        use gpui::{Refineable as _, TestApp, TextStyle};
5049
5050        let mut node_cx = NodeContext::default();
5051        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
5052        in_prepaint(&mut app, move |window, cx| {
5053            let style = TextStyle {
5054                font_family: BODY.into(),
5055                font_size: px(20.).into(),
5056                ..Default::default()
5057            };
5058            window.with_text_style(Some(style.subtract(&Default::default())), |window| {
5059                let mut previous = None;
5060                for (text, expected) in [("longer cell", 134.), ("x", CELL_MIN_PX)] {
5061                    let source = format!("| h |\n| - |\n| {text} |\n");
5062                    let document = markdown::parse(&source, &mut node_cx).unwrap();
5063                    let BlockNode::Table(table) = &document.blocks[0] else {
5064                        panic!("expected a parsed table");
5065                    };
5066                    assert!(table.column_widths_cache.0.lock().unwrap().is_none());
5067                    assert_eq!(
5068                        measure_table_columns(table, 1, &node_cx, window, cx),
5069                        vec![expected]
5070                    );
5071                    let cached = cached_column_widths(table);
5072                    if let Some(previous) = previous.replace(cached.clone()) {
5073                        assert!(!Arc::ptr_eq(&previous, &cached));
5074                    }
5075                    assert_eq!(document.source.as_ref(), source);
5076                }
5077            });
5078        });
5079    }
5080
5081    /// Table columns are sized from shaped text, so a column of inline code
5082    /// has to be measured in the code family. Measured in the body font, the
5083    /// wide-mono test font makes `col_w` come out at half the rendered width.
5084    #[test]
5085    fn table_column_of_inline_code_cells_fits_the_mono_width() {
5086        use crate::text::inline::test_fonts::{MONO, WideMonoTextSystem};
5087        use gpui::{Empty, TestApp};
5088
5089        let code = "method_name()";
5090        let mut paragraph = Paragraph::default();
5091        paragraph
5092            .push(InlineNode::new(code).marks(vec![(0..code.len(), TextMark::default().code())]));
5093        let table = Table {
5094            children: vec![TableRow {
5095                children: vec![TableCell {
5096                    children: paragraph,
5097                    width: None,
5098                }],
5099            }],
5100            column_aligns: vec![],
5101            span: None,
5102            table_data_cache: TableDataCache::default(),
5103            column_widths_cache: TableColumnWidthsCache::default(),
5104        };
5105        let node_cx = NodeContext::default();
5106
5107        let mut app = TestApp::with_text_system(Arc::new(WideMonoTextSystem));
5108        let mut window = app.open_window(|_, _| Empty);
5109        let (col_w, font_size) = window.update(|_, window, cx| {
5110            let mut theme = crate::Theme::default();
5111            theme.tokens.typography.mono = MONO.into();
5112            cx.set_global(theme);
5113            let font_size = window.text_style().font_size.to_pixels(window.rem_size());
5114            (
5115                measure_table_columns(&table, 1, &node_cx, window, cx),
5116                font_size,
5117            )
5118        });
5119
5120        let mono_w = f32::from(WideMonoTextSystem::width_of(code, MONO, font_size * 0.875));
5121        assert!(
5122            (col_w[0]
5123                - (mono_w + CELL_PAD_PX + crate::text::inline_flow::INLINE_CODE_PADDING * 2.))
5124                .abs()
5125                < 0.01,
5126            "col_w {} must fit the mono width {} plus padding {}",
5127            col_w[0],
5128            mono_w,
5129            CELL_PAD_PX
5130        );
5131    }
5132
5133    #[test]
5134    fn code_block_highlights_are_cached_by_highlighter_identity() {
5135        use std::sync::atomic::{AtomicUsize, Ordering};
5136
5137        let calls = Arc::new(AtomicUsize::new(0));
5138        let calls_for_highlighter = calls.clone();
5139        let highlighter: Arc<CodeBlockHighlighterFn> = Arc::new(move |_| {
5140            calls_for_highlighter.fetch_add(1, Ordering::Relaxed);
5141            Vec::new()
5142        });
5143        let block = CodeBlock::new("fn main() {}".into(), Some("rust".into()), None::<Span>);
5144
5145        block.highlighted_styles(&highlighter);
5146        block.highlighted_styles(&highlighter);
5147        assert_eq!(calls.load(Ordering::Relaxed), 1);
5148
5149        let replacement: Arc<CodeBlockHighlighterFn> = Arc::new(|_| Vec::new());
5150        block.highlighted_styles(&replacement);
5151        assert!(Arc::ptr_eq(
5152            &block
5153                .highlight_cache
5154                .lock()
5155                .unwrap()
5156                .as_ref()
5157                .unwrap()
5158                .highlighter,
5159            &replacement
5160        ));
5161    }
5162
5163    #[test]
5164    fn a_new_highlighter_replaces_styles_instead_of_reusing_the_cache() {
5165        // Swapping the highlighter is how a theme change reaches a code block:
5166        // the parsed document is untouched, so the styles must come from the
5167        // new highlighter rather than from the styles cached for the old one.
5168        let light: Arc<CodeBlockHighlighterFn> = Arc::new(|_| {
5169            vec![(
5170                0..2,
5171                HighlightStyle {
5172                    color: Some(gpui::rgb(0x0000ff).into()),
5173                    ..Default::default()
5174                },
5175            )]
5176        });
5177        let dark: Arc<CodeBlockHighlighterFn> = Arc::new(|_| {
5178            vec![(
5179                0..2,
5180                HighlightStyle {
5181                    color: Some(gpui::rgb(0xffff00).into()),
5182                    ..Default::default()
5183                },
5184            )]
5185        });
5186        let block = CodeBlock::from_code("42", Some("json"));
5187
5188        let light_styles = block.highlighted_styles(&light);
5189        let dark_styles = block.highlighted_styles(&dark);
5190
5191        assert_eq!(light_styles[0].1.color, Some(gpui::rgb(0x0000ff).into()));
5192        assert_eq!(dark_styles[0].1.color, Some(gpui::rgb(0xffff00).into()));
5193        assert_eq!(block.code(), "42", "the document must survive the swap");
5194    }
5195
5196    #[test]
5197    fn reconstruct_markdown_wraps_marked_runs() {
5198        // "bold" fully covered by a bold mark.
5199        let marks = vec![(0..4, TextMark::default().bold())];
5200        assert_eq!(reconstruct_markdown("bold", &marks, 0..4), "**bold**");
5201        // Partial selection inside the bold run still wraps the slice.
5202        assert_eq!(reconstruct_markdown("bold", &marks, 1..3), "**ol**");
5203    }
5204
5205    #[test]
5206    fn reconstruct_markdown_emits_unmarked_text_verbatim() {
5207        // "a b c": plain, code, plain across three runs concatenated.
5208        let text = "a b c";
5209        let marks = vec![(2..3, TextMark::default().code())];
5210        assert_eq!(reconstruct_markdown(text, &marks, 0..5), "a `b` c");
5211        // Selecting only the plain tail.
5212        assert_eq!(reconstruct_markdown(text, &marks, 3..5), " c");
5213    }
5214
5215    #[test]
5216    fn reconstruct_markdown_handles_code_italic_strike_link() {
5217        assert_eq!(
5218            reconstruct_markdown("x", &[(0..1, TextMark::default().code())], 0..1),
5219            "`x`"
5220        );
5221        assert_eq!(
5222            reconstruct_markdown("x", &[(0..1, TextMark::default().italic())], 0..1),
5223            "*x*"
5224        );
5225        assert_eq!(
5226            reconstruct_markdown("x", &[(0..1, TextMark::default().strikethrough())], 0..1),
5227            "~~x~~"
5228        );
5229        let link = TextMark::default().link(LinkMark {
5230            url: "https://example.com".into(),
5231            ..Default::default()
5232        });
5233        assert_eq!(
5234            reconstruct_markdown("x", &[(0..1, link)], 0..1),
5235            "[x](https://example.com)"
5236        );
5237    }
5238
5239    #[test]
5240    fn reconstruct_markdown_nested_bold_italic() {
5241        // A single run marked both bold and italic (as produced by `**_x_**`).
5242        let mark = TextMark::default().bold().italic();
5243        // Inner (italic) is applied first, then bold: `***x***`.
5244        assert_eq!(reconstruct_markdown("x", &[(0..1, mark)], 0..1), "***x***");
5245    }
5246
5247    /// Build a paragraph whose combined `state.text` is the concatenation of
5248    /// its children (mirroring `Paragraph::render`), then set the paragraph
5249    /// selection so `selected_source` can be exercised without a real paint.
5250    fn paragraph_with_children(children: Vec<InlineNode>) -> Paragraph {
5251        let combined: String = children.iter().map(|c| c.text.to_string()).collect();
5252        let paragraph = Paragraph {
5253            span: None,
5254            children,
5255            link_refs: HashMap::new(),
5256            state: Arc::new(Mutex::new(InlineState::default())),
5257            render_cache: ParagraphRenderCache::default(),
5258        };
5259        if let Ok(mut state) = paragraph.state.lock() {
5260            state.set_text(combined.into());
5261        }
5262        paragraph
5263    }
5264
5265    fn set_paragraph_selection(paragraph: &Paragraph, range: Range<usize>) {
5266        if let Ok(mut state) = paragraph.state.lock() {
5267            state.selection = Some(range.into());
5268        }
5269    }
5270
5271    #[test]
5272    fn paragraph_selected_source_maps_partial_selection_across_runs() {
5273        // "This has **bold** text." rendered as ["This has ", "bold", " text."].
5274        let children = vec![
5275            InlineNode::new("This has ").marks(vec![(0..9, TextMark::default())]),
5276            InlineNode::new("bold").marks(vec![(0..4, TextMark::default().bold())]),
5277            InlineNode::new(" text.").marks(vec![(0..6, TextMark::default())]),
5278        ];
5279        let paragraph = paragraph_with_children(children);
5280
5281        // Select the whole paragraph: "This has bold text." -> source with **.
5282        set_paragraph_selection(&paragraph, 0..(9 + 4 + 6));
5283        assert_eq!(paragraph.selected_source(), "This has **bold** text.");
5284
5285        // Select only across the boundary "has **bold** te".
5286        // Rendered offsets: "has " starts at 5, "bold" at 9..13, " te" 13..16.
5287        set_paragraph_selection(&paragraph, 5..16);
5288        assert_eq!(paragraph.selected_source(), "has **bold** te");
5289
5290        // Select entirely inside the bold run -> still wrapped.
5291        set_paragraph_selection(&paragraph, 10..12);
5292        assert_eq!(paragraph.selected_source(), "**ol**");
5293    }
5294
5295    #[test]
5296    fn paragraph_selected_source_matches_text_when_no_marks() {
5297        let children =
5298            vec![InlineNode::new("plain words").marks(vec![(0..11, TextMark::default())])];
5299        let paragraph = paragraph_with_children(children);
5300        set_paragraph_selection(&paragraph, 0..11);
5301        assert_eq!(paragraph.selected_source(), "plain words");
5302        assert_eq!(paragraph.selected_text(), "plain words");
5303    }
5304
5305    /// Copy `blocks` the way a view in [`SelectionFormat::SourceAcrossStyles`]
5306    /// does, with every block painted.
5307    fn copy_across_styles(blocks: Vec<BlockNode>) -> String {
5308        let document = crate::text::document::ParsedDocument {
5309            blocks: blocks.into(),
5310            ..Default::default()
5311        };
5312        document.selected_text(SelectionFormat::SourceAcrossStyles, None)
5313    }
5314
5315    #[test]
5316    fn source_across_styles_copies_plain_text_within_one_style() {
5317        // "This has **bold** text." rendered as ["This has ", "bold", " text."].
5318        let paragraph = || {
5319            paragraph_with_children(vec![
5320                InlineNode::new("This has ").marks(vec![(0..9, TextMark::default())]),
5321                InlineNode::new("bold").marks(vec![(0..4, TextMark::default().bold())]),
5322                InlineNode::new(" text.").marks(vec![(0..6, TextMark::default())]),
5323            ])
5324        };
5325        let copy = |range: Range<usize>| {
5326            let paragraph = paragraph();
5327            set_paragraph_selection(&paragraph, range);
5328            copy_across_styles(vec![BlockNode::Paragraph(paragraph)])
5329        };
5330
5331        // Inside the bold run, and up to both of its boundaries.
5332        assert_eq!(copy(10..12), "ol\n");
5333        assert_eq!(copy(9..13), "bold\n");
5334        // Inside the plain text before it, up to the boundary.
5335        assert_eq!(copy(0..9), "This has \n");
5336        // Across the boundary, the selection takes in two styles.
5337        assert_eq!(copy(5..16), "has **bold** te");
5338        assert_eq!(copy(8..10), " **b**");
5339    }
5340
5341    #[test]
5342    fn source_across_styles_drops_the_url_of_a_link_selected_alone() {
5343        let link = TextMark::default().link(LinkMark {
5344            url: "https://example.com".into(),
5345            ..Default::default()
5346        });
5347        let paragraph = paragraph_with_children(vec![
5348            InlineNode::new("see ").marks(vec![(0..4, TextMark::default())]),
5349            InlineNode::new("the docs").marks(vec![(0..8, link)]),
5350        ]);
5351
5352        set_paragraph_selection(&paragraph, 4..12);
5353        assert_eq!(
5354            copy_across_styles(vec![BlockNode::Paragraph(paragraph.clone())]),
5355            "the docs\n"
5356        );
5357
5358        set_paragraph_selection(&paragraph, 0..12);
5359        assert_eq!(
5360            copy_across_styles(vec![BlockNode::Paragraph(paragraph)]),
5361            "see [the docs](https://example.com)"
5362        );
5363    }
5364
5365    #[test]
5366    fn source_across_styles_drops_the_markup_of_a_single_block() {
5367        let heading = BlockNode::Heading {
5368            level: 2,
5369            children: selected_paragraph("Title"),
5370            span: None,
5371        };
5372        assert_eq!(copy_across_styles(vec![heading]), "Title\n");
5373
5374        let code = CodeBlock::from_code("let a = 1;\nlet b = 2;", Some("rust"));
5375        code.set_selection(0..10);
5376        assert_eq!(
5377            copy_across_styles(vec![BlockNode::CodeBlock(code)]),
5378            "let a = 1;\n"
5379        );
5380
5381        let item = |text: &str| BlockNode::ListItem {
5382            children: vec![BlockNode::Paragraph(selected_paragraph(text))],
5383            spread: false,
5384            checked: None,
5385            span: None,
5386        };
5387        let unselected_item = BlockNode::ListItem {
5388            children: vec![BlockNode::Paragraph(paragraph_with_children(vec![
5389                InlineNode::new("two"),
5390            ]))],
5391            spread: false,
5392            checked: None,
5393            span: None,
5394        };
5395        let list = BlockNode::List {
5396            ordered: false,
5397            start: None,
5398            span: None,
5399            children: vec![item("one"), unselected_item],
5400        };
5401        assert_eq!(copy_across_styles(vec![list]), "one\n");
5402    }
5403
5404    #[test]
5405    fn source_across_styles_keeps_the_source_across_blocks() {
5406        let item = |text: &str| BlockNode::ListItem {
5407            children: vec![BlockNode::Paragraph(selected_paragraph(text))],
5408            spread: false,
5409            checked: None,
5410            span: None,
5411        };
5412        let list = BlockNode::List {
5413            ordered: false,
5414            start: None,
5415            span: None,
5416            children: vec![item("one"), item("two")],
5417        };
5418        assert_eq!(copy_across_styles(vec![list]), "- one\n- two");
5419
5420        assert_eq!(
5421            copy_across_styles(vec![
5422                BlockNode::Paragraph(selected_paragraph("start")),
5423                BlockNode::Paragraph(selected_paragraph("end")),
5424            ]),
5425            "start\n\nend"
5426        );
5427    }
5428
5429    #[test]
5430    fn source_across_styles_keeps_the_source_of_an_inline_object() {
5431        let formula = InlineNode::custom(MarkdownNode::new("math", ()).text("x").markdown("$x$"));
5432        *formula.custom_selection.lock().unwrap() = true;
5433        let paragraph = Paragraph {
5434            children: vec![formula],
5435            ..Default::default()
5436        };
5437        assert_eq!(
5438            copy_across_styles(vec![BlockNode::Paragraph(paragraph)]),
5439            "$x$"
5440        );
5441    }
5442
5443    fn selected_paragraph(text: &str) -> Paragraph {
5444        let len = text.len();
5445        let paragraph = paragraph_with_children(vec![
5446            InlineNode::new(text).marks(vec![(0..len, TextMark::default())]),
5447        ]);
5448        set_paragraph_selection(&paragraph, 0..len);
5449        paragraph
5450    }
5451
5452    #[test]
5453    fn heading_selected_source_prefixes_hashes() {
5454        let heading = BlockNode::Heading {
5455            level: 2,
5456            children: selected_paragraph("Title"),
5457            span: None,
5458        };
5459        assert_eq!(heading.selected_text(SelectionFormat::Source), "## Title\n");
5460        // Rendered text keeps no marker.
5461        assert_eq!(heading.selected_text(SelectionFormat::Plain), "Title\n");
5462    }
5463
5464    #[test]
5465    fn unordered_list_selected_source_prefixes_dash() {
5466        let list = BlockNode::List {
5467            ordered: false,
5468            start: None,
5469            span: None,
5470            children: vec![
5471                BlockNode::ListItem {
5472                    children: vec![BlockNode::Paragraph(selected_paragraph("one"))],
5473                    spread: false,
5474                    checked: None,
5475                    span: None,
5476                },
5477                BlockNode::ListItem {
5478                    children: vec![BlockNode::Paragraph(selected_paragraph("two"))],
5479                    spread: false,
5480                    checked: None,
5481                    span: None,
5482                },
5483            ],
5484        };
5485        assert_eq!(
5486            list.selected_text(SelectionFormat::Source),
5487            "- one\n- two\n"
5488        );
5489    }
5490
5491    #[test]
5492    fn ordered_list_selected_source_preserves_start() {
5493        let list = BlockNode::List {
5494            ordered: true,
5495            start: Some(3),
5496            span: None,
5497            children: vec![
5498                BlockNode::ListItem {
5499                    children: vec![BlockNode::Paragraph(selected_paragraph("three"))],
5500                    spread: false,
5501                    checked: None,
5502                    span: None,
5503                },
5504                BlockNode::ListItem {
5505                    children: vec![BlockNode::Paragraph(selected_paragraph("four"))],
5506                    spread: false,
5507                    checked: None,
5508                    span: None,
5509                },
5510            ],
5511        };
5512        assert_eq!(list.to_markdown(), "3. three\n4. four");
5513        assert_eq!(
5514            list.selected_text(SelectionFormat::Source),
5515            "3. three\n4. four\n"
5516        );
5517    }
5518
5519    #[test]
5520    fn ordered_list_selected_source_preserves_nested_starts_and_continuations() {
5521        let nested = BlockNode::List {
5522            ordered: true,
5523            start: Some(4),
5524            span: None,
5525            children: vec![
5526                BlockNode::ListItem {
5527                    children: vec![BlockNode::Paragraph(selected_paragraph("nested"))],
5528                    spread: false,
5529                    checked: None,
5530                    span: None,
5531                },
5532                BlockNode::ListItem {
5533                    children: vec![BlockNode::Paragraph(selected_paragraph("again"))],
5534                    spread: false,
5535                    checked: None,
5536                    span: None,
5537                },
5538            ],
5539        };
5540        let nested_list = BlockNode::List {
5541            ordered: true,
5542            start: Some(3),
5543            span: None,
5544            children: vec![
5545                BlockNode::ListItem {
5546                    children: vec![BlockNode::Paragraph(selected_paragraph("three")), nested],
5547                    spread: false,
5548                    checked: None,
5549                    span: None,
5550                },
5551                BlockNode::ListItem {
5552                    children: vec![BlockNode::Paragraph(selected_paragraph("four"))],
5553                    spread: false,
5554                    checked: None,
5555                    span: None,
5556                },
5557            ],
5558        };
5559        assert_eq!(
5560            nested_list.selected_text(SelectionFormat::Source),
5561            "3. three\n   4. nested\n   5. again\n4. four\n"
5562        );
5563
5564        let loose_list = BlockNode::List {
5565            ordered: true,
5566            start: Some(3),
5567            span: None,
5568            children: vec![
5569                BlockNode::ListItem {
5570                    children: vec![
5571                        BlockNode::Paragraph(selected_paragraph("loose")),
5572                        BlockNode::Paragraph(selected_paragraph("continuation")),
5573                    ],
5574                    spread: true,
5575                    checked: None,
5576                    span: None,
5577                },
5578                BlockNode::ListItem {
5579                    children: vec![BlockNode::Paragraph(selected_paragraph("four"))],
5580                    spread: false,
5581                    checked: None,
5582                    span: None,
5583                },
5584            ],
5585        };
5586        assert_eq!(
5587            loose_list.selected_text(SelectionFormat::Source),
5588            "3. loose\n   continuation\n4. four\n"
5589        );
5590    }
5591
5592    #[test]
5593    fn nested_list_selected_source_indents_sublists() {
5594        // - one
5595        //   - nested
5596        // - two
5597        let nested = BlockNode::List {
5598            ordered: false,
5599            start: None,
5600            span: None,
5601            children: vec![BlockNode::ListItem {
5602                children: vec![BlockNode::Paragraph(selected_paragraph("nested"))],
5603                spread: false,
5604                checked: None,
5605                span: None,
5606            }],
5607        };
5608        let list = BlockNode::List {
5609            ordered: false,
5610            start: None,
5611            span: None,
5612            children: vec![
5613                BlockNode::ListItem {
5614                    children: vec![BlockNode::Paragraph(selected_paragraph("one")), nested],
5615                    spread: false,
5616                    checked: None,
5617                    span: None,
5618                },
5619                BlockNode::ListItem {
5620                    children: vec![BlockNode::Paragraph(selected_paragraph("two"))],
5621                    spread: false,
5622                    checked: None,
5623                    span: None,
5624                },
5625            ],
5626        };
5627        assert_eq!(
5628            list.selected_text(SelectionFormat::Source),
5629            "- one\n  - nested\n- two\n"
5630        );
5631    }
5632
5633    #[test]
5634    fn task_list_selected_source_restores_checkboxes() {
5635        let list = BlockNode::List {
5636            ordered: false,
5637            start: None,
5638            span: None,
5639            children: vec![
5640                BlockNode::ListItem {
5641                    children: vec![BlockNode::Paragraph(selected_paragraph("done"))],
5642                    spread: false,
5643                    checked: Some(true),
5644                    span: None,
5645                },
5646                BlockNode::ListItem {
5647                    children: vec![BlockNode::Paragraph(selected_paragraph("todo"))],
5648                    spread: false,
5649                    checked: Some(false),
5650                    span: None,
5651                },
5652            ],
5653        };
5654        assert_eq!(
5655            list.selected_text(SelectionFormat::Source),
5656            "- [x] done\n- [ ] todo\n"
5657        );
5658    }
5659
5660    #[test]
5661    fn blockquote_selected_source_prefixes_gt() {
5662        let quote = BlockNode::Blockquote {
5663            span: None,
5664            children: vec![BlockNode::Paragraph(selected_paragraph("quoted text"))],
5665        };
5666        assert_eq!(
5667            quote.selected_text(SelectionFormat::Source),
5668            "> quoted text\n"
5669        );
5670    }
5671
5672    #[test]
5673    fn table_selected_source_pipes_cells_with_alignment_row() {
5674        let cell = |text: &str| TableCell {
5675            children: selected_paragraph(text),
5676            width: None,
5677        };
5678        let table = Table {
5679            children: vec![
5680                TableRow {
5681                    children: vec![cell("Name"), cell("Age")],
5682                },
5683                TableRow {
5684                    children: vec![cell("Alice"), cell("30")],
5685                },
5686            ],
5687            column_aligns: vec![ColumnumnAlign::Left, ColumnumnAlign::Right],
5688            span: None,
5689            table_data_cache: TableDataCache::default(),
5690            column_widths_cache: TableColumnWidthsCache::default(),
5691        };
5692        let block = BlockNode::Table(table);
5693        assert_eq!(
5694            block.selected_text(SelectionFormat::Source),
5695            "| Name | Age |\n| :-- | --: |\n| Alice | 30 |\n"
5696        );
5697    }
5698
5699    /// A drag that starts in a table cell stays in it, so the copy is the
5700    /// cell's text alone: no pipes, no delimiter row, no other cells or rows.
5701    #[test]
5702    fn a_selection_in_one_table_cell_copies_only_that_cell() {
5703        let table = table_of(
5704            vec![
5705                vec![plain_cell("Name"), plain_cell("Age")],
5706                vec![
5707                    TableCell {
5708                        children: selected_paragraph("Alice"),
5709                        width: None,
5710                    },
5711                    plain_cell("30"),
5712                ],
5713            ],
5714            vec![ColumnumnAlign::Left, ColumnumnAlign::Right],
5715        );
5716        let block = BlockNode::Table(table);
5717        assert_eq!(block.selected_text(SelectionFormat::Plain).trim(), "Alice");
5718        assert_eq!(block.selected_text(SelectionFormat::Source), "Alice\n");
5719    }
5720
5721    /// A cell holding plain text, as `Table::to_markdown` and
5722    /// `Table::table_data` see it (neither needs a selection).
5723    fn plain_cell(text: &str) -> TableCell {
5724        TableCell {
5725            children: Paragraph::new(text.to_string()),
5726            width: None,
5727        }
5728    }
5729
5730    fn table_of(rows: Vec<Vec<TableCell>>, column_aligns: Vec<ColumnumnAlign>) -> Table {
5731        Table {
5732            children: rows
5733                .into_iter()
5734                .map(|children| TableRow { children })
5735                .collect(),
5736            column_aligns,
5737            span: None,
5738            table_data_cache: TableDataCache::default(),
5739            column_widths_cache: TableColumnWidthsCache::default(),
5740        }
5741    }
5742
5743    #[test]
5744    fn table_to_markdown_pipes_cells_with_alignment_row() {
5745        let table = table_of(
5746            vec![
5747                vec![plain_cell("Name"), plain_cell("Age"), plain_cell("Score")],
5748                vec![plain_cell("Alice"), plain_cell("30"), plain_cell("9.5")],
5749            ],
5750            vec![
5751                ColumnumnAlign::Left,
5752                ColumnumnAlign::Center,
5753                ColumnumnAlign::Right,
5754            ],
5755        );
5756
5757        assert_eq!(
5758            table.to_markdown(),
5759            "| Name | Age | Score |\n| :-- | :-: | --: |\n| Alice | 30 | 9.5 |"
5760        );
5761        // The block arm delegates to it.
5762        assert_eq!(
5763            BlockNode::Table(table.clone()).to_markdown(),
5764            table.to_markdown()
5765        );
5766    }
5767
5768    #[test]
5769    fn table_to_markdown_keeps_outer_pipes_for_a_single_column() {
5770        let table = table_of(
5771            vec![vec![plain_cell("Symbol")], vec![plain_cell("TSLA.US")]],
5772            vec![ColumnumnAlign::Left],
5773        );
5774
5775        assert_eq!(table.to_markdown(), "| Symbol |\n| :-- |\n| TSLA.US |");
5776    }
5777
5778    #[test]
5779    fn table_to_markdown_escapes_pipes_and_keeps_inline_marks() {
5780        let bold = TableCell {
5781            children: paragraph_with_children(vec![
5782                InlineNode::new("bold").marks(vec![(0..4, TextMark::default().bold())]),
5783            ]),
5784            width: None,
5785        };
5786        let table = table_of(
5787            vec![
5788                vec![plain_cell("a | b"), plain_cell("plain")],
5789                vec![plain_cell("c"), bold],
5790            ],
5791            vec![ColumnumnAlign::Left, ColumnumnAlign::Left],
5792        );
5793
5794        assert_eq!(
5795            table.to_markdown(),
5796            "| a \\| b | plain |\n| :-- | :-- |\n| c | **bold** |"
5797        );
5798    }
5799
5800    #[test]
5801    fn table_data_snapshots_plain_cells_and_markdown() {
5802        let mut table = table_of(
5803            vec![
5804                vec![plain_cell("  Name  "), plain_cell("Age")],
5805                vec![plain_cell("Alice"), plain_cell("30")],
5806            ],
5807            vec![ColumnumnAlign::Left, ColumnumnAlign::Right],
5808        );
5809        table.span = Some(Span { start: 4, end: 42 });
5810
5811        let data = table.table_data();
5812        assert_eq!(data.headers, vec!["Name", "Age"]);
5813        assert_eq!(data.rows, vec![vec!["Alice", "30"]]);
5814        assert_eq!(data.markdown, table.to_markdown());
5815        assert_eq!(data.span, Some(4..42));
5816    }
5817
5818    #[test]
5819    fn cloned_table_keeps_cached_table_data() {
5820        // Streaming appends deep-clone every block; the clone must reuse the
5821        // cached snapshot instead of rebuilding it.
5822        let table = table_of(
5823            vec![vec![plain_cell("Name")], vec![plain_cell("Alice")]],
5824            vec![ColumnumnAlign::Left],
5825        );
5826        let data = table.cached_table_data();
5827
5828        let cloned = table.clone();
5829        assert!(Arc::ptr_eq(&data, &cloned.cached_table_data()));
5830    }
5831
5832    #[test]
5833    fn table_data_handles_tables_without_rows() {
5834        // Header only: still a valid table, with no data rows.
5835        let header_only = table_of(
5836            vec![vec![plain_cell("Name"), plain_cell("Age")]],
5837            vec![ColumnumnAlign::Left, ColumnumnAlign::Left],
5838        );
5839        let data = header_only.table_data();
5840        assert_eq!(data.headers, vec!["Name", "Age"]);
5841        assert!(data.rows.is_empty());
5842        assert_eq!(data.markdown, "| Name | Age |\n| :-- | :-- |");
5843
5844        // No rows at all (a table still streaming in): an empty snapshot.
5845        assert_eq!(Table::default().table_data(), TableData::default());
5846    }
5847
5848    #[test]
5849    fn test_image_node_source() {
5850        use gpui::{ImageFormat, ImageSource, Resource};
5851
5852        fn image_node(url: &str) -> ImageNode {
5853            ImageNode {
5854                url: url.into(),
5855                ..Default::default()
5856            }
5857        }
5858
5859        // Document-provided values stay URI-backed, including `file://` and
5860        // scheme-less strings, so the document never gets implicit
5861        // filesystem access through `Resource::Embedded`.
5862        fn assert_uri(url: &str) {
5863            match image_node(url).source() {
5864                ImageSource::Resource(Resource::Uri(uri)) => assert_eq!(uri.as_ref(), url),
5865                _ => panic!("expected Uri for {url:?}"),
5866            }
5867        }
5868        assert_uri("https://example.com/logo.png");
5869        assert_uri("http://example.com/logo.png");
5870        assert_uri("website/public/logo.svg");
5871        assert_uri("./images/a.png");
5872        assert_uri("../images/a.png");
5873        assert_uri("/absolute/path/logo.svg");
5874        assert_uri("file:///absolute/path/logo.svg");
5875        assert_uri(r"C:\images\logo.png");
5876        assert_uri("docs/a:b.png");
5877        assert_uri("data:text/plain;base64,aGVsbG8=");
5878
5879        // A `data:` image is decoded once and the same decoded image is
5880        // handed to every render.
5881        let node = image_node("data:image/png;base64,iVBORw0KGgo=");
5882        let ImageSource::Image(first) = node.source() else {
5883            panic!("expected an embedded image");
5884        };
5885        assert_eq!(first.format(), ImageFormat::Png);
5886        assert_eq!(first.bytes(), b"\x89PNG\r\n\x1a\n");
5887        let ImageSource::Image(second) = node.source() else {
5888            panic!("expected an embedded image");
5889        };
5890        assert!(Arc::ptr_eq(&first, &second));
5891    }
5892
5893    fn image_paragraph(alt: &str, url: &str) -> Paragraph {
5894        let image = ImageNode {
5895            url: url.into(),
5896            alt: Some(alt.into()),
5897            ..Default::default()
5898        };
5899        Paragraph {
5900            span: None,
5901            children: vec![InlineNode::image(image)],
5902            link_refs: HashMap::new(),
5903            state: Arc::new(Mutex::new(InlineState::default())),
5904            render_cache: ParagraphRenderCache::default(),
5905        }
5906    }
5907
5908    /// Every mark round-trips, including the two Markdown has no plain syntax
5909    /// for.
5910    #[test]
5911    fn marks_round_trip_through_reconstruction() {
5912        let wrap = |mark: TextMark| reconstruct_markdown("x", &[(0..1, mark)], 0..1);
5913
5914        assert_eq!(wrap(TextMark::default().bold()), "**x**");
5915        assert_eq!(wrap(TextMark::default().italic()), "*x*");
5916        assert_eq!(wrap(TextMark::default().code()), "`x`");
5917        assert_eq!(wrap(TextMark::default().strikethrough()), "~~x~~");
5918        assert_eq!(
5919            wrap(TextMark::default().highlight(gpui::rgb(0xfef08a).into())),
5920            "==x=="
5921        );
5922        // No Markdown syntax for underline, so it keeps the tag it came from.
5923        assert_eq!(wrap(TextMark::default().underline()), "<u>x</u>");
5924
5925        // A link keeps its title, which Markdown carries after the URL.
5926        assert_eq!(
5927            wrap(TextMark::default().link(LinkMark {
5928                url: "https://example.com".into(),
5929                title: Some("Tip".into()),
5930                ..Default::default()
5931            })),
5932            "[x](https://example.com \"Tip\")"
5933        );
5934    }
5935
5936    /// A block the selection covers whole comes straight from the source, so it
5937    /// keeps what the author wrote instead of a normalized reconstruction.
5938    #[test]
5939    fn document_selected_source_slices_covered_blocks_from_the_source() {
5940        use crate::text::document::ParsedDocument;
5941
5942        // `_italic_`, the `3.` start and the column padding all survive only
5943        // because the block is copied, not rebuilt.
5944        let source = "start\n\n3. _one_\n4. two\n\n---\n\nend";
5945        let list = "3. _one_\n4. two";
5946        let list_start = source.find(list).unwrap();
5947        let rule_start = source.find("---").unwrap();
5948
5949        let document = ParsedDocument {
5950            source: source.into(),
5951            blocks: vec![
5952                BlockNode::Paragraph(selected_paragraph("start")),
5953                BlockNode::List {
5954                    ordered: true,
5955                    start: Some(3),
5956                    children: vec![],
5957                    span: Some(Span {
5958                        start: list_start,
5959                        end: list_start + list.len(),
5960                    }),
5961                },
5962                BlockNode::HorizontalRule {
5963                    span: Some(Span {
5964                        start: rule_start,
5965                        end: rule_start + 3,
5966                    }),
5967                },
5968                BlockNode::Paragraph(selected_paragraph("end")),
5969            ]
5970            .into(),
5971        };
5972
5973        assert_eq!(
5974            document.selected_text(SelectionFormat::Source, None),
5975            "start\n\n3. _one_\n4. two\n\n---\n\nend"
5976        );
5977    }
5978
5979    #[test]
5980    fn document_selected_source_includes_enclosed_image() {
5981        use crate::text::document::ParsedDocument;
5982
5983        // A standalone image between two selected paragraphs is covered by the
5984        // selection, so it is copied whole even though it holds no selection of
5985        // its own — straight out of the source the parser located it in.
5986        let source = "before\n\n![alt](https://example.com/i.png)\n\nafter";
5987        let image_markdown = "![alt](https://example.com/i.png)";
5988        let start = source.find(image_markdown).unwrap();
5989        let mut image = image_paragraph("alt", "https://example.com/i.png");
5990        image.span = Some(Span {
5991            start,
5992            end: start + image_markdown.len(),
5993        });
5994
5995        let document = ParsedDocument {
5996            source: source.into(),
5997            blocks: vec![
5998                BlockNode::Paragraph(selected_paragraph("before")),
5999                BlockNode::Paragraph(image),
6000                BlockNode::Paragraph(selected_paragraph("after")),
6001            ]
6002            .into(),
6003        };
6004        assert_eq!(
6005            document.selected_text(SelectionFormat::Source, None),
6006            "before\n\n![alt](https://example.com/i.png)\n\nafter"
6007        );
6008    }
6009
6010    #[test]
6011    fn document_selected_source_drops_unenclosed_image() {
6012        use crate::text::document::ParsedDocument;
6013
6014        // An image after the only selected block, with nothing selected after
6015        // it, is not enclosed and is dropped.
6016        let document = ParsedDocument {
6017            source: String::new().into(),
6018            blocks: vec![
6019                BlockNode::Paragraph(selected_paragraph("before")),
6020                BlockNode::Paragraph(image_paragraph("alt", "u")),
6021            ]
6022            .into(),
6023        };
6024        assert_eq!(
6025            document.selected_text(SelectionFormat::Source, None),
6026            "before"
6027        );
6028    }
6029
6030    fn selected_code_block(code: &str, lang: Option<&str>) -> BlockNode {
6031        let block = CodeBlock::new(
6032            code.to_string().into(),
6033            lang.map(|l| l.to_string().into()),
6034            None::<Span>,
6035        );
6036        if let Ok(mut state) = block.state.lock() {
6037            let len = state.text.len();
6038            state.selection = Some((0..len).into());
6039        }
6040        BlockNode::CodeBlock(block)
6041    }
6042
6043    #[test]
6044    fn code_block_selected_source_wraps_in_fence_with_lang() {
6045        let block = selected_code_block("let x = 1;\n", Some("rust"));
6046        let code = block.selected_text(SelectionFormat::Plain);
6047        let code_trimmed = code.trim_end_matches('\n');
6048        // The source wraps the (trailing-newline-trimmed) selected code in a
6049        // fenced block carrying the language; the block arm adds one trailing
6050        // newline.
6051        assert_eq!(
6052            block.selected_text(SelectionFormat::Source),
6053            format!("```rust\n{}\n```\n", code_trimmed)
6054        );
6055        assert!(
6056            block
6057                .selected_text(SelectionFormat::Source)
6058                .starts_with("```rust\n")
6059        );
6060        assert!(
6061            block
6062                .selected_text(SelectionFormat::Source)
6063                .trim_end()
6064                .ends_with("\n```")
6065        );
6066    }
6067
6068    #[test]
6069    fn code_block_selected_source_without_lang() {
6070        let block = selected_code_block("plain\n", None);
6071        let code_trimmed = block.selected_text(SelectionFormat::Plain);
6072        let code_trimmed = code_trimmed.trim_end_matches('\n');
6073        assert_eq!(
6074            block.selected_text(SelectionFormat::Source),
6075            format!("```\n{}\n```\n", code_trimmed)
6076        );
6077    }
6078
6079    #[test]
6080    fn document_selected_source_joins_blocks_with_blank_line() {
6081        use crate::text::document::ParsedDocument;
6082
6083        // A heading, a paragraph, and a two-item ordered list, each fully
6084        // selected. Top-level blocks must be separated by a blank line so the
6085        // copied Markdown re-renders with the same structure.
6086        let document = ParsedDocument {
6087            source: String::new().into(),
6088            blocks: vec![
6089                BlockNode::Heading {
6090                    level: 1,
6091                    children: selected_paragraph("Title"),
6092                    span: None,
6093                },
6094                BlockNode::Paragraph(selected_paragraph("A paragraph.")),
6095                selected_code_block("let x = 1;\n", Some("rust")),
6096                BlockNode::List {
6097                    ordered: true,
6098                    start: Some(1),
6099                    span: None,
6100                    children: vec![
6101                        BlockNode::ListItem {
6102                            children: vec![BlockNode::Paragraph(selected_paragraph("one"))],
6103                            spread: false,
6104                            checked: None,
6105                            span: None,
6106                        },
6107                        BlockNode::ListItem {
6108                            children: vec![BlockNode::Paragraph(selected_paragraph("two"))],
6109                            spread: false,
6110                            checked: None,
6111                            span: None,
6112                        },
6113                    ],
6114                },
6115            ]
6116            .into(),
6117        };
6118
6119        assert_eq!(
6120            document.selected_text(SelectionFormat::Source, None),
6121            "# Title\n\nA paragraph.\n\n```rust\nlet x = 1;\n```\n\n1. one\n2. two"
6122        );
6123    }
6124
6125    #[test]
6126    fn code_block_equality_includes_code_content() {
6127        let first = CodeBlock::new("let value = 1;".into(), Some("rust".into()), None::<Span>);
6128        let second = CodeBlock::new("let value = 2;".into(), Some("rust".into()), None::<Span>);
6129
6130        assert_ne!(first, second);
6131    }
6132}