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