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