Skip to main content

markdown/render/
code.rs

1//! Code blocks, source mode, and the copy button.
2
3use super::*;
4
5/// A source text and what [`render_source`] reads off it every frame: its
6/// lines, a hash of each, and its highlight spans.
7///
8/// [`SourceText::new`] walks and highlights the whole text.
9/// [`SourceText::edit`] redoes only the lines an edit touched and shifts the
10/// spans around it; the [`SpanJob`] it answers highlights the new text in full,
11/// on any thread, for [`SourceText::set_spans`]. [`SourceText::rehighlight`]
12/// answers one when the installed highlighter has changed.
13pub struct SourceText {
14    code: Arc<str>,
15    ranges: Rc<[Range<usize>]>,
16    hashes: Rc<[u64]>,
17    spans: Option<Rc<[Span]>>,
18    /// Moved by every edit and rehighlight. A [`SpanJob`] carries the one it
19    /// was taken at.
20    generation: u64,
21    /// The highlighter generation the last [`SpanJob`], or the build, used.
22    highlighter: u64,
23    sized: RefCell<Option<Sized>>,
24}
25
26/// The per-line tables that also depend on what the text is set at.
27#[derive(Clone)]
28struct Sized {
29    /// The text size, line height and indent these were built for.
30    at: (u32, Pixels, Pixels),
31    keys: Rc<[u64]>,
32    gaps: Rc<[Pixels]>,
33    guesses: Rc<[Guess]>,
34}
35
36/// Highlighting a [`SourceText`] in full. `Send`, so it can run off the thread
37/// that paints.
38pub struct SpanJob {
39    code: Arc<str>,
40    highlighter: Option<crate::Highlighter>,
41    generation: u64,
42}
43
44/// What a [`SpanJob`] found, for [`SourceText::set_spans`].
45pub struct Spans {
46    spans: Option<Vec<Span>>,
47    generation: u64,
48}
49
50impl SpanJob {
51    pub fn run(self) -> Spans {
52        Spans {
53            spans: highlight_source(self.highlighter, &self.code),
54            generation: self.generation,
55        }
56    }
57}
58
59fn highlight_source(highlighter: Option<crate::Highlighter>, code: &str) -> Option<Vec<Span>> {
60    let language = crate::source::LANGUAGES[0];
61    highlighter
62        .and_then(|highlighter| highlighter(language, code))
63        .or_else(|| crate::source::is_markdown(language).then(|| crate::source::spans(code)))
64}
65
66impl SourceText {
67    pub fn new(code: impl Into<Arc<str>>, cx: &App) -> Self {
68        let code: Arc<str> = code.into();
69        let spans = highlight_source(crate::highlight::installed(cx), &code).map(Into::into);
70        let ranges = split_lines(&code, 0..code.len());
71        let hashes = ranges.iter().map(|range| hash_line(&code, range)).collect();
72        Self {
73            code,
74            ranges: ranges.into(),
75            hashes,
76            spans,
77            generation: 0,
78            highlighter: crate::highlight::generation(cx),
79            sized: RefCell::new(None),
80        }
81    }
82
83    pub fn text(&self) -> &str {
84        &self.code
85    }
86
87    /// Lines, counting the empty one after a trailing newline.
88    pub fn lines(&self) -> usize {
89        self.ranges.len()
90    }
91
92    /// The line holding byte `offset`; past the end, the last line.
93    pub fn line_of(&self, offset: usize) -> usize {
94        self.ranges
95            .partition_point(|range| range.end < offset)
96            .min(self.ranges.len() - 1)
97    }
98
99    /// The highlight spans, in bytes and in order. Between an [`Self::edit`]
100    /// and the [`Self::set_spans`] that follows it, the spans from before the
101    /// edit, shifted past it, and none over the bytes it replaced.
102    pub fn spans(&self) -> Option<&[(Range<usize>, theme::HighlightKind)]> {
103        self.spans.as_deref()
104    }
105
106    /// Make the text `code`. The change is taken to be the one run of bytes
107    /// between the longest prefix and suffix the two texts share. `None` when
108    /// the text is unchanged.
109    pub fn edit(&mut self, code: &str, cx: &App) -> Option<SpanJob> {
110        let old = self.code.as_bytes();
111        let new = code.as_bytes();
112        let prefix = old.iter().zip(new).take_while(|(a, b)| a == b).count();
113        if prefix == old.len() && prefix == new.len() {
114            return None;
115        }
116        let room = old.len().min(new.len()) - prefix;
117        let suffix = old
118            .iter()
119            .rev()
120            .zip(new.iter().rev())
121            .take(room)
122            .take_while(|(a, b)| a == b)
123            .count();
124        let old_end = old.len() - suffix;
125        let delta = (new.len() - suffix) as isize - old_end as isize;
126        let shift = |at: usize| at.wrapping_add_signed(delta);
127
128        let first = self.line_of(prefix);
129        let last = self.line_of(old_end);
130        let code: Arc<str> = code.into();
131        let lines = split_lines(
132            &code,
133            self.ranges[first].start..shift(self.ranges[last].end),
134        );
135        let hashes: Vec<u64> = lines.iter().map(|range| hash_line(&code, range)).collect();
136        let cut = first..last + 1;
137
138        if let Some(sized) = self.sized.get_mut() {
139            let (size, line, indent) = sized.at;
140            sized.keys = splice(
141                &sized.keys,
142                cut.clone(),
143                hashes.iter().map(|hash| key(*hash, size)),
144            );
145            sized.guesses = splice(
146                &sized.guesses,
147                cut.clone(),
148                lines.iter().map(|range| guess(range, line, indent)),
149            );
150            sized.gaps = vec![px(0.0); sized.keys.len()].into();
151        }
152        self.hashes = splice(&self.hashes, cut.clone(), hashes);
153        self.ranges = self.ranges[..first]
154            .iter()
155            .cloned()
156            .chain(lines)
157            .chain(
158                self.ranges[last + 1..]
159                    .iter()
160                    .map(|range| shift(range.start)..shift(range.end)),
161            )
162            .collect();
163        self.spans = self.spans.as_ref().map(|spans| {
164            let mut kept = Vec::with_capacity(spans.len());
165            for (range, kind) in spans.iter() {
166                if range.start < prefix {
167                    kept.push((range.start..range.end.min(prefix), *kind));
168                }
169                if range.end > old_end {
170                    kept.push((shift(range.start.max(old_end))..shift(range.end), *kind));
171                }
172            }
173            kept.into()
174        });
175        self.code = code;
176        Some(self.span_job(cx))
177    }
178
179    /// Highlight the text again if the installed highlighter has changed since
180    /// it was last highlighted. The spans stand until the job's land.
181    pub fn rehighlight(&mut self, cx: &App) -> Option<SpanJob> {
182        (self.highlighter != crate::highlight::generation(cx)).then(|| self.span_job(cx))
183    }
184
185    fn span_job(&mut self, cx: &App) -> SpanJob {
186        self.generation += 1;
187        self.highlighter = crate::highlight::generation(cx);
188        SpanJob {
189            code: self.code.clone(),
190            highlighter: crate::highlight::installed(cx),
191            generation: self.generation,
192        }
193    }
194
195    /// Take what a [`SpanJob`] found. `false`, and nothing taken, when the text
196    /// has been edited or rehighlighted since the job was made.
197    pub fn set_spans(&mut self, spans: Spans) -> bool {
198        if spans.generation != self.generation {
199            return false;
200        }
201        self.spans = spans.spans.map(Into::into);
202        true
203    }
204
205    fn sized(&self, size: f32, line: Pixels, indent: Pixels) -> Sized {
206        let at = (size.to_bits(), line, indent);
207        let mut held = self.sized.borrow_mut();
208        if let Some(sized) = held.as_ref()
209            && sized.at == at
210        {
211            return sized.clone();
212        }
213        let sized = Sized {
214            at,
215            keys: self.hashes.iter().map(|hash| key(*hash, at.0)).collect(),
216            gaps: vec![px(0.0); self.ranges.len()].into(),
217            guesses: self
218                .ranges
219                .iter()
220                .map(|range| guess(range, line, indent))
221                .collect(),
222        };
223        *held = Some(sized.clone());
224        sized
225    }
226}
227
228/// The lines of `code` within `within`, which starts at a line's start and
229/// ends at a line's end.
230fn split_lines(code: &str, within: Range<usize>) -> Vec<Range<usize>> {
231    let mut ranges = Vec::new();
232    let mut at = within.start;
233    for line in code[within].split('\n') {
234        ranges.push(at..at + line.len());
235        at += line.len() + 1;
236    }
237    ranges
238}
239
240fn hash_line(code: &str, range: &Range<usize>) -> u64 {
241    let mut hasher = DefaultHasher::new();
242    "source".hash(&mut hasher);
243    code[range.clone()].hash(&mut hasher);
244    hasher.finish()
245}
246
247fn key(hash: u64, size: u32) -> u64 {
248    let mut hasher = DefaultHasher::new();
249    hash.hash(&mut hasher);
250    size.hash(&mut hasher);
251    hasher.finish()
252}
253
254fn guess(range: &Range<usize>, line: Pixels, indent: Pixels) -> Guess {
255    Guess {
256        chars: range.len(),
257        line,
258        rows: 0,
259        extra: px(0.0),
260        indent,
261    }
262}
263
264/// `old` with `cut` replaced by `new`.
265fn splice<T: Clone>(old: &[T], cut: Range<usize>, new: impl IntoIterator<Item = T>) -> Rc<[T]> {
266    old[..cut.start]
267        .iter()
268        .cloned()
269        .chain(new)
270        .chain(old[cut.end..].iter().cloned())
271        .collect()
272}
273
274/// Paint a document's own markdown source: a fence's caret, selection and hit
275/// testing, without a fence's box, band or copy button.
276///
277/// The caret is a [`Cursor`] at block 0 in [`Part::Code`] — what a document
278/// held as one fence answers to, which is how an editor holds its source.
279/// Wrapping is not optional here: a paragraph is one line of markdown, and a
280/// source view that scrolled sideways would hide most of it.
281///
282/// Set at [`Typography::body`]'s size and leading, in [`Theme::font_body`].
283pub fn render_source(source: &SourceText, editing: Editing, cx: &mut App) -> AnyElement {
284    let code: &str = &source.code;
285    let Editing {
286        selection,
287        caret_on,
288        layouts,
289        annotations,
290        typography,
291        keep,
292        scroll,
293        ..
294    } = editing;
295    let mut theme = Theme::of(cx).clone();
296    theme.font_mono = theme.font_body.clone();
297    let mut typography = typography.unwrap_or_else(|| cx.typography());
298    typography.code = typography.body;
299    let overlay = Overlay {
300        block: 0,
301        part: Part::Code,
302        selection,
303        caret_on,
304        caret_shape: cx.caret_shape(),
305        caret_height: cx.caret_height(),
306        caret_inactive: cx.inactive_caret(),
307        // No window reaches here; a source view's rows read their own below.
308        window_active: cx.active_window().is_some(),
309        layouts,
310        annotations,
311        placeholder: None,
312        caption: Caption::default(),
313        // The source view is one fence and holds no task block.
314        toggle: None,
315        image: None,
316        image_menu: None,
317        image_overlay: None,
318        image_overlay_corner: ImageOverlayCorner::BottomRight,
319        fence: None,
320        sizing: None,
321        table_controls: false,
322        // It paints no band, so there is nowhere for the button to float.
323        copy: CopyButton::Hidden,
324        base: None,
325        highlight: crate::marks::highlight_paint_of(cx),
326        find: crate::find::find_paint_of(cx),
327        comment: crate::comment::comment_paint_of(cx),
328        jump: None,
329    };
330    let style = cx.source_style();
331    let count = source.ranges.len();
332    let gutter = Gutter::new(&style, count, &typography, &theme);
333
334    let Some(layouts) = layouts else {
335        let (underlay, over, lines) = code_lines(
336            Some(crate::source::LANGUAGES[0]),
337            code,
338            overlay,
339            &typography,
340            &theme,
341            cx,
342        );
343        let lines = lines
344            .into_iter()
345            .enumerate()
346            .map(|(index, line)| gutter.row(index, line))
347            .collect();
348        return div()
349            .flex()
350            .flex_col()
351            .child(code_body(0, underlay, over, lines, &typography, true))
352            .into_any_element();
353    };
354
355    // Built a line at a time by the same column the blocks are: a line is to
356    // this text what a block is to a document.
357    let spans = source.spans.clone();
358    let ranges = source.ranges.clone();
359    let mut kept: Vec<usize> = keep.to_vec();
360    if let Some(selection) = selection {
361        kept.push(source.line_of(selection.head.offset));
362        kept.push(source.line_of(selection.anchor.offset));
363    }
364    let line = px(typography.code.line_height());
365    let indent = px(gutter.width() + 2.0 * CODE_PADDING_X);
366    let sized = source.sized(typography.code.size(), line, indent);
367    layouts.prune(&sized.keys);
368    let paint = RowPaint::new(&overlay, code, typography.code.size(), &theme);
369    let code = source.code.clone();
370    let sink = layouts.clone();
371    let column = Column {
372        layouts: layouts.clone(),
373        keys: sized.keys,
374        gaps: sized.gaps,
375        guesses: sized.guesses,
376        keep: kept,
377        scroll: scroll.cloned(),
378        item_of: {
379            let ranges = ranges.clone();
380            Rc::new(move |at| ranges.partition_point(|line| line.end < at.offset))
381        },
382        build: Box::new(move |index, window, _| {
383            let span = ranges[index].clone();
384            let paint = RowPaint {
385                hollow: !window.is_window_active(),
386                ..paint.clone()
387            };
388            let (styled, runs) =
389                code_line(&code[span.clone()], span.start, spans.as_deref(), &theme);
390            let layout = styled.layout().clone();
391            let sink = sink.clone();
392            let over = {
393                let (span, layout, paint) = (span.clone(), layout.clone(), paint.clone());
394                canvas(
395                    |_, _, _| (),
396                    move |_, _, window, _| paint.paint_over(&span, &layout, &runs, window),
397                )
398                .absolute()
399                .size_full()
400            };
401            let underlay = canvas(
402                |_, _, _| (),
403                move |_, _, window, _| {
404                    sink.record(
405                        0,
406                        Part::Code,
407                        span.clone(),
408                        layout.clone(),
409                        Shown::default(),
410                    );
411                    paint.paint_under(&span, &layout, window);
412                },
413            )
414            .absolute()
415            .size_full();
416            let text = div().relative().child(underlay).child(styled).child(over);
417            div()
418                .px(px(CODE_PADDING_X))
419                .child(gutter.row(index, text.into_any_element()))
420                .into_any_element()
421        }),
422    };
423    div()
424        .flex()
425        .flex_col()
426        .py(px(CODE_PADDING_Y))
427        .text_size(px(typography.code.size()))
428        .line_height(line)
429        .child(column)
430        .into_any_element()
431}
432
433/// The line numbers beside a source view, when it shows them.
434#[derive(Clone)]
435struct Gutter {
436    shown: bool,
437    width: f32,
438    gap: f32,
439    color: Hsla,
440    font: SharedString,
441}
442
443impl Gutter {
444    fn new(
445        style: &crate::SourceStyle,
446        lines: usize,
447        typography: &Typography,
448        theme: &Theme,
449    ) -> Self {
450        let digits = lines.to_string().len().max(style.gutter_min_digits);
451        let gap = style.gutter_gap.max(0.0) * typography.code.size();
452        Self {
453            shown: style.line_numbers,
454            width: digits as f32 * typography.code.size() + gap,
455            gap,
456            color: style.gutter_color.unwrap_or(theme.text_faint),
457            font: theme.font_mono.clone(),
458        }
459    }
460
461    fn width(&self) -> f32 {
462        if self.shown { self.width } else { 0.0 }
463    }
464
465    /// Keeps each number beside its source line, including wrapped and empty
466    /// lines.
467    fn row(&self, index: usize, line: AnyElement) -> AnyElement {
468        if !self.shown {
469            return line;
470        }
471        div()
472            .flex()
473            .items_start()
474            .child(
475                div()
476                    .w(px(self.width))
477                    .flex_shrink_0()
478                    .pr(px(self.gap))
479                    .font_family(self.font.clone())
480                    .text_color(self.color)
481                    .text_right()
482                    .child((index + 1).to_string()),
483            )
484            .child(div().flex_1().min_w_0().child(line))
485            .into_any_element()
486    }
487}
488
489/// The shaped lines of a fence, the canvas that paints the selection and the
490/// annotations under them, and the one that paints the caret over them.
491pub(super) fn code_lines(
492    language: Option<&str>,
493    code: &str,
494    overlay: Overlay,
495    typography: &Typography,
496    theme: &Theme,
497    cx: &App,
498) -> (AnyElement, AnyElement, Vec<AnyElement>) {
499    let ix = overlay.block;
500    // Highlighting recolors runs only — layout does not move, so a build with
501    // no highlighter installed paints the same block in one plain run.
502    // Markdown is the one language this crate can colour on its own, which is
503    // what a source view is painted with where no highlighter reaches.
504    let spans = crate::highlight::spans(cx, language, code).or_else(|| {
505        language
506            .filter(|language| crate::source::is_markdown(language))
507            .map(|_| crate::source::spans(code))
508    });
509    // Each source line's own layout, with the slice of the code it covers —
510    // the caret and a click both resolve through these. A wrapped line is
511    // several rows of one layout, which is the case `range_rects` already
512    // walks for a paragraph.
513    let paint = RowPaint::new(&overlay, code, typography.code.size(), theme);
514    let mut rows: Vec<(Range<usize>, TextLayout, Vec<TextRun>)> = Vec::new();
515    let mut offset = 0usize;
516    let lines: Vec<AnyElement> = code
517        .split('\n')
518        .map(|line| {
519            let start = offset;
520            offset += line.len() + 1;
521            let (styled, runs) = code_line(line, start, spans.as_deref(), theme);
522            rows.push((start..start + line.len(), styled.layout().clone(), runs));
523            styled.into_any_element()
524        })
525        .collect();
526
527    let sink = overlay.layouts.cloned();
528    let over = {
529        let (rows, paint) = (rows.clone(), paint.clone());
530        canvas(
531            |_, _, _| (),
532            move |_, _, window, _| {
533                for (span, layout, runs) in &rows {
534                    paint.paint_over(span, layout, runs, window);
535                }
536            },
537        )
538        .absolute()
539        .size_full()
540    };
541    let underlay = canvas(
542        |_, _, _| (),
543        move |_, _, window, _| {
544            for (span, layout, _) in &rows {
545                if let Some(sink) = &sink {
546                    sink.record(
547                        ix,
548                        Part::Code,
549                        span.clone(),
550                        layout.clone(),
551                        Shown::default(),
552                    );
553                }
554                paint.paint_under(span, layout, window);
555            }
556        },
557    )
558    .absolute()
559    .size_full();
560
561    (underlay.into_any_element(), over.into_any_element(), lines)
562}
563
564/// A highlighted byte range of a text.
565type Span = (Range<usize>, theme::HighlightKind);
566
567/// One line of code, coloured by the spans over the text it came from, and
568/// its runs.
569///
570/// Runs are measured within the line; spans are byte ranges over the whole
571/// text, so every span is clipped to the line and rebased.
572fn code_line(
573    line: &str,
574    start: usize,
575    spans: Option<&[Span]>,
576    theme: &Theme,
577) -> (StyledText, Vec<TextRun>) {
578    let mono = font(theme.font_mono.clone());
579    let run = |len: usize, color: Hsla| TextRun {
580        len,
581        font: mono.clone(),
582        color,
583        background_color: None,
584        underline: None,
585        strikethrough: None,
586    };
587    let mut runs = Vec::new();
588    let mut pos = 0usize;
589    if let Some(spans) = spans {
590        let end = start + line.len();
591        for (range, kind) in spans.iter().filter(|(r, _)| r.end > start && r.start < end) {
592            let s = range.start.clamp(start, end) - start;
593            let e = range.end.min(end) - start;
594            if s > pos {
595                runs.push(run(s - pos, theme.text));
596            }
597            runs.push(run(e - s, theme.syntax.color(*kind)));
598            pos = e;
599        }
600    }
601    if pos < line.len() {
602        runs.push(run(line.len() - pos, theme.text));
603    }
604    if runs.is_empty() {
605        runs.push(run(0, theme.text));
606    }
607    let styled = StyledText::new(SharedString::from(line.to_string())).with_runs(runs.clone());
608    (styled, runs)
609}
610
611/// What paints with a line of code: the selection and the annotations under
612/// it, the caret and the text they leave unreadable over it, each clipped to the slice of the text the line
613/// covers.
614#[derive(Clone)]
615struct RowPaint {
616    caret: Option<usize>,
617    /// The grapheme after the caret, over the whole text.
618    glyph: Option<Range<usize>>,
619    affinity: Affinity,
620    shape: ui::input::CaretShape,
621    height: ui::input::CaretHeight,
622    hollow: bool,
623    font: SharedString,
624    selected: Option<Range<usize>>,
625    annotated: Vec<(Range<usize>, Hsla)>,
626    caret_color: Hsla,
627    cover: Hsla,
628    selection_color: Hsla,
629    /// What the code is painted on.
630    page: Hsla,
631    code_size: f32,
632}
633
634impl RowPaint {
635    fn new(overlay: &Overlay, code: &str, code_size: f32, theme: &Theme) -> Self {
636        let caret = overlay.caret_painted();
637        Self {
638            caret,
639            glyph: caret.and_then(|offset| overlay.covered(code, offset)),
640            affinity: overlay.affinity(),
641            shape: overlay.caret_shape,
642            height: overlay.caret_height,
643            hollow: overlay.caret_hollow(),
644            font: theme.font_mono.clone(),
645            selected: overlay.selected(code.len()),
646            annotated: overlay.annotated(code.len(), theme),
647            caret_color: theme.caret,
648            cover: theme::legible(theme.bg, theme.caret, theme::LEGIBLE),
649            selection_color: theme.selection,
650            page: theme.bg,
651            code_size,
652        }
653    }
654
655    /// The grapheme a solid block covers.
656    fn cut(&self) -> Option<&Range<usize>> {
657        self.glyph
658            .as_ref()
659            .filter(|_| self.shape.cuts_out(self.hollow))
660    }
661
662    fn paint_under(&self, span: &Range<usize>, layout: &TextLayout, window: &mut Window) {
663        let wash = |range: &Range<usize>, color: Hsla, window: &mut Window| {
664            let (from, to) = (range.start.max(span.start), range.end.min(span.end));
665            if from < to {
666                for rect in range_rects(layout, &(from - span.start..to - span.start), 0.0, 0.0) {
667                    window.paint_quad(quad(
668                        rect,
669                        px(2.0),
670                        color,
671                        px(0.0),
672                        gpui::transparent_black(),
673                        BorderStyle::default(),
674                    ));
675                }
676            }
677        };
678        for (range, color) in self.layers() {
679            wash(&range, color, window);
680        }
681    }
682
683    /// The selection, then the annotations, which are translucent: a
684    /// highlight shows through over a selection. Over the whole text, in the
685    /// order they are painted.
686    fn layers(&self) -> impl Iterator<Item = (Range<usize>, Hsla)> + '_ {
687        self.selected
688            .iter()
689            .map(|range| (range.clone(), self.selection_color))
690            .chain(self.annotated.iter().cloned())
691    }
692
693    /// `runs` are the line's, `span` its slice of the whole text.
694    fn paint_over(
695        &self,
696        span: &Range<usize>,
697        layout: &TextLayout,
698        runs: &[TextRun],
699        window: &mut Window,
700    ) {
701        let layers: Vec<_> = self
702            .layers()
703            .filter_map(|(range, color)| {
704                let (from, to) = (range.start.max(span.start), range.end.min(span.end));
705                (from < to).then(|| (from - span.start..to - span.start, color))
706            })
707            .collect();
708        ui::input::overdraw::paint(
709            layout.line_layouts().iter().map(|line| &**line),
710            layout.bounds().origin,
711            layout.line_height(),
712            px(2.0),
713            &ui::input::overdraw::overdraws(runs, self.page, &layers),
714            window,
715        );
716        if let Some(offset) = self.caret.filter(|at| span.contains(at) || *at == span.end) {
717            let glyph = self
718                .cut()
719                .map(|glyph| glyph.start - span.start..glyph.end - span.start);
720            paint_caret(
721                layout,
722                offset - span.start,
723                self.affinity,
724                glyph.as_ref(),
725                CaretPaint {
726                    shape: self.shape,
727                    height: self.height,
728                    hollow: self.hollow,
729                    color: self.caret_color,
730                    cover: self.cover,
731                    size: self.code_size,
732                    face: font(self.font.clone()),
733                },
734                window,
735            );
736        }
737    }
738}
739
740pub(super) fn code_block(
741    language: Option<&str>,
742    code: &str,
743    overlay: Overlay,
744    typography: &Typography,
745    theme: &Theme,
746    window: &mut Window,
747    cx: &mut App,
748) -> AnyElement {
749    let ix = overlay.block;
750    let (underlay, over, lines) = code_lines(language, code, overlay, typography, theme, cx);
751    let body = code_body(
752        ix,
753        underlay,
754        over,
755        lines,
756        typography,
757        cx.markdown_layout().wrap_code,
758    );
759
760    fence_panel(theme)
761        // The band is unconditional: it is where the copy button already floats,
762        // and where a host puts its language control — which needs somewhere to
763        // sit on a block that has no language yet.
764        .child(
765            fence_band(theme)
766                .text_color(match language {
767                    Some(_) => theme.text_muted,
768                    None => theme.text_faint,
769                })
770                // The label's own box, not the band's: a host hanging a picker
771                // here wants it around the word, and only the word knows how
772                // wide the word is.
773                .child(
774                    div()
775                        .relative()
776                        .children(overlay.layouts.map(|layouts| {
777                            let layouts = layouts.clone();
778                            canvas(
779                                move |bounds, _, _| layouts.record_language(ix, bounds),
780                                |_, _, _, _| (),
781                            )
782                            .absolute()
783                            .size_full()
784                        }))
785                        .child(SharedString::from(
786                            language.unwrap_or(PLAIN_LANGUAGE).to_string(),
787                        )),
788                ),
789        )
790        .child(body)
791        .children(
792            (overlay.copy == CopyButton::Shown).then(|| copy_button(code, ix, theme, window, cx)),
793        )
794        .into_any_element()
795}
796
797/// The box a fence is drawn in, painted or as code.
798pub fn fence_panel(theme: &Theme) -> gpui::Div {
799    div()
800        .rounded(px(Theme::panel_radius()))
801        .bg(theme.ink(0.035))
802        .border_1()
803        .border_color(theme.border)
804        .overflow_hidden()
805        .relative()
806}
807
808/// The band across the top of a [`fence_panel`], where its label and copy
809/// button sit.
810pub fn fence_band(theme: &Theme) -> gpui::Div {
811    div()
812        .relative()
813        .flex()
814        .flex_row()
815        .items_center()
816        .px(px(CODE_PADDING_X))
817        .py(px(5.0))
818        .border_b_1()
819        .border_color(theme.border)
820        .bg(theme.ink(0.02))
821        .text_style(TextStyle::Subheadline)
822}
823
824/// The lines of a fence, wrapped to the block or scrolling sideways under it.
825pub(super) fn code_body(
826    ix: usize,
827    underlay: AnyElement,
828    over: AnyElement,
829    lines: Vec<AnyElement>,
830    typography: &Typography,
831    wrap: bool,
832) -> AnyElement {
833    let column = div()
834        .flex()
835        .flex_col()
836        .px(px(CODE_PADDING_X))
837        .children(lines);
838    let body = div()
839        .id(ElementId::named_usize("md-code", ix))
840        .relative()
841        .py(px(CODE_PADDING_Y))
842        .text_size(px(typography.code.size()))
843        .line_height(px(typography.code.line_height()))
844        .child(underlay);
845    if wrap {
846        // The column is the block's width here rather than its widest line's,
847        // which is what gives the text something to wrap against.
848        body.child(column.w_full()).child(over).into_any_element()
849    } else {
850        ui::scroll::Viewport::new(
851            format!("md-code-scroll-{ix}"),
852            body.flex()
853                .flex_row()
854                .whitespace_nowrap()
855                // The padding belongs to the lines, not to the scroller: a scroll
856                // container's trailing padding is not part of what it will scroll
857                // to, so the last characters of a long line sit behind the right
858                // edge with nowhere left to go. As a row's only item this column is
859                // sized by its widest line, and the padding rides along inside that
860                // width.
861                .child(column.items_start())
862                .child(over),
863            gpui::Axis::Horizontal,
864        )
865        .into_any_element()
866    }
867}
868
869/// A copy button that owns its own feedback.
870///
871/// The state is the element's, not the caller's: a component library cannot ask
872/// every host to thread a handler and a "which block is showing Copied" index
873/// through its render tree just to put a button on a code block. It resets when
874/// the pointer leaves, which needs no clock.
875pub(crate) fn copy_button(
876    code: &str,
877    ix: usize,
878    theme: &Theme,
879    window: &mut Window,
880    cx: &mut App,
881) -> AnyElement {
882    let copied = window.use_keyed_state(ElementId::named_usize("md-copied", ix), cx, |_, _| false);
883    let showing = *copied.read(cx);
884    let text: SharedString = code.to_string().into();
885
886    div()
887        .id(ElementId::named_usize("md-copy", ix))
888        .absolute()
889        .top(px(3.0))
890        .right(px(5.0))
891        .h(px(20.0))
892        .px(px(6.0))
893        .rounded(px(5.0))
894        .flex()
895        .items_center()
896        .cursor_pointer()
897        .text_style(TextStyle::Caption)
898        .text_color(theme.text_muted)
899        .hover(|el| el.bg(theme.element_hover))
900        .child(if showing { "Copied" } else { "Copy" })
901        .on_click({
902            let copied = copied.clone();
903            move |_, _, cx| {
904                cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.to_string()));
905                copied.update(cx, |state, cx| {
906                    *state = true;
907                    cx.notify();
908                });
909            }
910        })
911        .on_hover(move |hovering, _, cx| {
912            if !*hovering && *copied.read(cx) {
913                copied.update(cx, |state, cx| {
914                    *state = false;
915                    cx.notify();
916                });
917            }
918        })
919        .into_any_element()
920}