1use super::*;
4
5pub fn render_source(code: &str, editing: Editing, cx: &mut App) -> AnyElement {
15 let Editing {
16 selection,
17 caret_on,
18 layouts,
19 annotations,
20 typography,
21 keep,
22 scroll,
23 ..
24 } = editing;
25 let mut theme = Theme::of(cx).clone();
26 theme.font_mono = theme.font_body.clone();
27 let mut typography = typography.unwrap_or_else(|| cx.typography());
28 typography.code = typography.body;
29 let overlay = Overlay {
30 block: 0,
31 part: Part::Code,
32 selection,
33 caret_on,
34 caret_shape: cx.caret_shape(),
35 caret_height: cx.caret_height(),
36 caret_inactive: cx.inactive_caret(),
37 window_active: cx.active_window().is_some(),
39 layouts,
40 annotations,
41 placeholder: None,
42 caption: Caption::default(),
43 toggle: None,
45 image: None,
46 image_overlay: None,
47 image_overlay_corner: ImageOverlayCorner::BottomRight,
48 fence: None,
49 sizing: None,
50 table_controls: false,
51 copy: CopyButton::Hidden,
53 base: None,
54 highlight: crate::marks::highlight_paint_of(cx),
55 find: crate::find::find_paint_of(cx),
56 };
57 let style = cx.source_style();
58 let count = code.split('\n').count();
59 let gutter = Gutter::new(&style, count, &typography, &theme);
60
61 let Some(layouts) = layouts else {
62 let (underlay, lines) = code_lines(
63 Some(crate::source::LANGUAGES[0]),
64 code,
65 overlay,
66 &typography,
67 &theme,
68 cx,
69 );
70 let lines = lines
71 .into_iter()
72 .enumerate()
73 .map(|(index, line)| gutter.row(index, line))
74 .collect();
75 return div()
76 .flex()
77 .flex_col()
78 .child(code_body(0, underlay, lines, &typography, true))
79 .into_any_element();
80 };
81
82 let language = crate::source::LANGUAGES[0];
85 let spans: Option<Rc<[Span]>> = crate::highlight::spans(cx, Some(language), code)
86 .or_else(|| crate::source::is_markdown(language).then(|| crate::source::spans(code)))
87 .map(Into::into);
88 let mut ranges = Vec::with_capacity(count);
89 let mut offset = 0usize;
90 for line in code.split('\n') {
91 ranges.push(offset..offset + line.len());
92 offset += line.len() + 1;
93 }
94 let line_of = |at: usize| {
95 ranges
96 .partition_point(|range| range.end < at)
97 .min(count - 1)
98 };
99 let mut kept: Vec<usize> = keep.to_vec();
100 if let Some(selection) = selection {
101 kept.push(line_of(selection.head.offset));
102 kept.push(line_of(selection.anchor.offset));
103 }
104 let line = px(typography.code.line_height());
105 let keys: Vec<u64> = ranges
106 .iter()
107 .map(|range| {
108 let mut hasher = DefaultHasher::new();
109 "source".hash(&mut hasher);
110 code[range.clone()].hash(&mut hasher);
111 typography.code.size().to_bits().hash(&mut hasher);
112 hasher.finish()
113 })
114 .collect();
115 layouts.prune(&keys);
116 let indent = px(gutter.width() + 2.0 * CODE_PADDING_X);
117 let guesses: Vec<Guess> = ranges
118 .iter()
119 .map(|range| Guess {
120 chars: range.len(),
121 line,
122 rows: 0,
123 extra: px(0.0),
124 indent,
125 })
126 .collect();
127 let paint = RowPaint::new(&overlay, code, typography.code.size(), &theme);
128 let code: Rc<str> = code.into();
129 let sink = layouts.clone();
130 let ranges: Rc<[Range<usize>]> = ranges.into();
131 let column = Column {
132 layouts: layouts.clone(),
133 keys: keys.into(),
134 gaps: vec![px(0.0); count].into(),
135 guesses: guesses.into(),
136 keep: kept,
137 scroll: scroll.cloned(),
138 item_of: {
139 let ranges = ranges.clone();
140 Box::new(move |at| ranges.partition_point(|line| line.end < at.offset))
141 },
142 build: Box::new(move |index, window, _| {
143 let span = ranges[index].clone();
144 let paint = RowPaint {
145 hollow: !window.is_window_active(),
146 ..paint.clone()
147 };
148 let styled = code_line(
149 &code[span.clone()],
150 span.start,
151 spans.as_deref(),
152 paint.cut(),
153 &theme,
154 );
155 let layout = styled.layout().clone();
156 let sink = sink.clone();
157 let underlay = canvas(
158 |_, _, _| (),
159 move |_, _, window, _| {
160 sink.record(
161 0,
162 Part::Code,
163 span.clone(),
164 layout.clone(),
165 Shown::default(),
166 );
167 paint.paint(&span, &layout, window);
168 },
169 )
170 .absolute()
171 .size_full();
172 let text = div().relative().child(underlay).child(styled);
173 div()
174 .px(px(CODE_PADDING_X))
175 .child(gutter.row(index, text.into_any_element()))
176 .into_any_element()
177 }),
178 };
179 div()
180 .flex()
181 .flex_col()
182 .py(px(CODE_PADDING_Y))
183 .text_size(px(typography.code.size()))
184 .line_height(line)
185 .child(column)
186 .into_any_element()
187}
188
189#[derive(Clone)]
191struct Gutter {
192 shown: bool,
193 width: f32,
194 gap: f32,
195 color: Hsla,
196 font: SharedString,
197}
198
199impl Gutter {
200 fn new(
201 style: &crate::SourceStyle,
202 lines: usize,
203 typography: &Typography,
204 theme: &Theme,
205 ) -> Self {
206 let digits = lines.to_string().len().max(style.gutter_min_digits);
207 let gap = style.gutter_gap.max(0.0) * typography.code.size();
208 Self {
209 shown: style.line_numbers,
210 width: digits as f32 * typography.code.size() + gap,
211 gap,
212 color: style.gutter_color.unwrap_or(theme.text_faint),
213 font: theme.font_mono.clone(),
214 }
215 }
216
217 fn width(&self) -> f32 {
218 if self.shown { self.width } else { 0.0 }
219 }
220
221 fn row(&self, index: usize, line: AnyElement) -> AnyElement {
224 if !self.shown {
225 return line;
226 }
227 div()
228 .flex()
229 .items_start()
230 .child(
231 div()
232 .w(px(self.width))
233 .flex_shrink_0()
234 .pr(px(self.gap))
235 .font_family(self.font.clone())
236 .text_color(self.color)
237 .text_right()
238 .child((index + 1).to_string()),
239 )
240 .child(div().flex_1().min_w_0().child(line))
241 .into_any_element()
242 }
243}
244
245pub(super) fn code_lines(
248 language: Option<&str>,
249 code: &str,
250 overlay: Overlay,
251 typography: &Typography,
252 theme: &Theme,
253 cx: &App,
254) -> (AnyElement, Vec<AnyElement>) {
255 let ix = overlay.block;
256 let spans = crate::highlight::spans(cx, language, code).or_else(|| {
261 language
262 .filter(|language| crate::source::is_markdown(language))
263 .map(|_| crate::source::spans(code))
264 });
265 let paint = RowPaint::new(&overlay, code, typography.code.size(), theme);
270 let mut rows: Vec<(Range<usize>, TextLayout)> = Vec::new();
271 let mut offset = 0usize;
272 let lines: Vec<AnyElement> = code
273 .split('\n')
274 .map(|line| {
275 let start = offset;
276 offset += line.len() + 1;
277 let styled = code_line(line, start, spans.as_deref(), paint.cut(), theme);
278 rows.push((start..start + line.len(), styled.layout().clone()));
279 styled.into_any_element()
280 })
281 .collect();
282
283 let sink = overlay.layouts.cloned();
284 let underlay = canvas(
285 |_, _, _| (),
286 move |_, _, window, _| {
287 for (span, layout) in &rows {
288 if let Some(sink) = &sink {
289 sink.record(
290 ix,
291 Part::Code,
292 span.clone(),
293 layout.clone(),
294 Shown::default(),
295 );
296 }
297 paint.paint(span, layout, window);
298 }
299 },
300 )
301 .absolute()
302 .size_full();
303
304 (underlay.into_any_element(), lines)
305}
306
307type Span = (Range<usize>, theme::HighlightKind);
309
310fn code_line(
317 line: &str,
318 start: usize,
319 spans: Option<&[Span]>,
320 glyph: Option<&Range<usize>>,
321 theme: &Theme,
322) -> StyledText {
323 let mono = font(theme.font_mono.clone());
324 let run = |len: usize, color: Hsla| TextRun {
325 len,
326 font: mono.clone(),
327 color,
328 background_color: None,
329 underline: None,
330 strikethrough: None,
331 };
332 let mut runs = Vec::new();
333 let mut pos = 0usize;
334 if let Some(spans) = spans {
335 let end = start + line.len();
336 for (range, kind) in spans.iter().filter(|(r, _)| r.end > start && r.start < end) {
337 let s = range.start.clamp(start, end) - start;
338 let e = range.end.min(end) - start;
339 if s > pos {
340 runs.push(run(s - pos, theme.text));
341 }
342 runs.push(run(e - s, theme.syntax.color(*kind)));
343 pos = e;
344 }
345 }
346 if pos < line.len() {
347 runs.push(run(line.len() - pos, theme.text));
348 }
349 if runs.is_empty() {
350 runs.push(run(0, theme.text));
351 }
352 if let Some(glyph) =
353 glyph.filter(|glyph| glyph.start >= start && glyph.end <= start + line.len())
354 {
355 runs =
356 ui::input::caret::recoloured(runs, &(glyph.start - start..glyph.end - start), theme.bg);
357 }
358 StyledText::new(SharedString::from(line.to_string())).with_runs(runs)
359}
360
361#[derive(Clone)]
364struct RowPaint {
365 caret: Option<usize>,
366 glyph: Option<Range<usize>>,
368 shape: ui::input::CaretShape,
369 height: ui::input::CaretHeight,
370 hollow: bool,
371 font: SharedString,
372 selected: Option<Range<usize>>,
373 annotated: Vec<(Range<usize>, Hsla)>,
374 caret_color: Hsla,
375 selection_color: Hsla,
376 code_size: f32,
377}
378
379impl RowPaint {
380 fn new(overlay: &Overlay, code: &str, code_size: f32, theme: &Theme) -> Self {
381 let caret = overlay.caret_painted();
382 Self {
383 caret,
384 glyph: caret.and_then(|offset| glyph_at(code, offset)),
385 shape: overlay.caret_shape,
386 height: overlay.caret_height,
387 hollow: overlay.caret_hollow(),
388 font: theme.font_mono.clone(),
389 selected: overlay.selected(code.len()),
390 annotated: overlay.annotated(code.len(), theme),
391 caret_color: theme.caret,
392 selection_color: theme.selection,
393 code_size,
394 }
395 }
396
397 fn cut(&self) -> Option<&Range<usize>> {
399 self.glyph
400 .as_ref()
401 .filter(|_| self.shape.cuts_out(self.hollow))
402 }
403
404 fn paint(&self, span: &Range<usize>, layout: &TextLayout, window: &mut Window) {
405 let wash = |range: &Range<usize>, color: Hsla, window: &mut Window| {
406 let (from, to) = (range.start.max(span.start), range.end.min(span.end));
407 if from < to {
408 for rect in range_rects(layout, &(from - span.start..to - span.start), 0.0, 0.0) {
409 window.paint_quad(quad(
410 rect,
411 px(2.0),
412 color,
413 px(0.0),
414 gpui::transparent_black(),
415 BorderStyle::default(),
416 ));
417 }
418 }
419 };
420 for (range, color) in &self.annotated {
421 wash(range, *color, window);
422 }
423 if let Some(range) = &self.selected {
424 wash(range, self.selection_color, window);
425 }
426 if let Some(offset) = self.caret.filter(|at| span.contains(at) || *at == span.end) {
427 let glyph = self
428 .cut()
429 .map(|glyph| glyph.start - span.start..glyph.end - span.start);
430 paint_caret(
431 layout,
432 offset - span.start,
433 glyph.as_ref(),
434 CaretPaint {
435 shape: self.shape,
436 height: self.height,
437 hollow: self.hollow,
438 color: self.caret_color,
439 size: self.code_size,
440 face: font(self.font.clone()),
441 },
442 window,
443 );
444 }
445 }
446}
447
448pub(super) fn code_block(
449 language: Option<&str>,
450 code: &str,
451 overlay: Overlay,
452 typography: &Typography,
453 theme: &Theme,
454 window: &mut Window,
455 cx: &mut App,
456) -> AnyElement {
457 let ix = overlay.block;
458 let (underlay, lines) = code_lines(language, code, overlay, typography, theme, cx);
459 let body = code_body(
460 ix,
461 underlay,
462 lines,
463 typography,
464 cx.markdown_layout().wrap_code,
465 );
466
467 fence_panel(theme)
468 .child(
472 fence_band(theme)
473 .text_color(match language {
474 Some(_) => theme.text_muted,
475 None => theme.text_faint,
476 })
477 .child(
481 div()
482 .relative()
483 .children(overlay.layouts.map(|layouts| {
484 let layouts = layouts.clone();
485 canvas(
486 move |bounds, _, _| layouts.record_language(ix, bounds),
487 |_, _, _, _| (),
488 )
489 .absolute()
490 .size_full()
491 }))
492 .child(SharedString::from(
493 language.unwrap_or(PLAIN_LANGUAGE).to_string(),
494 )),
495 ),
496 )
497 .child(body)
498 .children(
499 (overlay.copy == CopyButton::Shown).then(|| copy_button(code, ix, theme, window, cx)),
500 )
501 .into_any_element()
502}
503
504pub fn fence_panel(theme: &Theme) -> gpui::Div {
506 div()
507 .rounded(px(Theme::panel_radius()))
508 .bg(theme.ink(0.035))
509 .border_1()
510 .border_color(theme.border)
511 .overflow_hidden()
512 .relative()
513}
514
515pub fn fence_band(theme: &Theme) -> gpui::Div {
518 div()
519 .relative()
520 .flex()
521 .flex_row()
522 .items_center()
523 .px(px(CODE_PADDING_X))
524 .py(px(5.0))
525 .border_b_1()
526 .border_color(theme.border)
527 .bg(theme.ink(0.02))
528 .text_style(TextStyle::Subheadline)
529}
530
531pub(super) fn code_body(
533 ix: usize,
534 underlay: AnyElement,
535 lines: Vec<AnyElement>,
536 typography: &Typography,
537 wrap: bool,
538) -> AnyElement {
539 let column = div()
540 .flex()
541 .flex_col()
542 .px(px(CODE_PADDING_X))
543 .children(lines);
544 let body = div()
545 .id(ElementId::named_usize("md-code", ix))
546 .relative()
547 .py(px(CODE_PADDING_Y))
548 .text_size(px(typography.code.size()))
549 .line_height(px(typography.code.line_height()))
550 .child(underlay);
551 if wrap {
552 body.child(column.w_full()).into_any_element()
555 } else {
556 ui::scroll::Viewport::new(
557 format!("md-code-scroll-{ix}"),
558 body.flex()
559 .flex_row()
560 .whitespace_nowrap()
561 .child(column.items_start()),
568 gpui::Axis::Horizontal,
569 )
570 .into_any_element()
571 }
572}
573
574pub(crate) fn copy_button(
581 code: &str,
582 ix: usize,
583 theme: &Theme,
584 window: &mut Window,
585 cx: &mut App,
586) -> AnyElement {
587 let copied = window.use_keyed_state(ElementId::named_usize("md-copied", ix), cx, |_, _| false);
588 let showing = *copied.read(cx);
589 let text: SharedString = code.to_string().into();
590
591 div()
592 .id(ElementId::named_usize("md-copy", ix))
593 .absolute()
594 .top(px(3.0))
595 .right(px(5.0))
596 .h(px(20.0))
597 .px(px(6.0))
598 .rounded(px(5.0))
599 .flex()
600 .items_center()
601 .cursor_pointer()
602 .text_style(TextStyle::Caption)
603 .text_color(theme.text_muted)
604 .hover(|el| el.bg(theme.element_hover))
605 .child(if showing { "Copied" } else { "Copy" })
606 .on_click({
607 let copied = copied.clone();
608 move |_, _, cx| {
609 cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.to_string()));
610 copied.update(cx, |state, cx| {
611 *state = true;
612 cx.notify();
613 });
614 }
615 })
616 .on_hover(move |hovering, _, cx| {
617 if !*hovering && *copied.read(cx) {
618 copied.update(cx, |state, cx| {
619 *state = false;
620 cx.notify();
621 });
622 }
623 })
624 .into_any_element()
625}