1use super::*;
4
5pub struct SourceText {
13 code: Arc<str>,
14 ranges: Rc<[Range<usize>]>,
15 hashes: Rc<[u64]>,
16 spans: Option<Rc<[Span]>>,
17 generation: u64,
19 sized: RefCell<Option<Sized>>,
20}
21
22#[derive(Clone)]
24struct Sized {
25 at: (u32, Pixels, Pixels),
27 keys: Rc<[u64]>,
28 gaps: Rc<[Pixels]>,
29 guesses: Rc<[Guess]>,
30}
31
32pub struct SpanJob {
35 code: Arc<str>,
36 highlighter: Option<crate::Highlighter>,
37 generation: u64,
38}
39
40pub struct Spans {
42 spans: Option<Vec<Span>>,
43 generation: u64,
44}
45
46impl SpanJob {
47 pub fn run(self) -> Spans {
48 Spans {
49 spans: highlight_source(self.highlighter, &self.code),
50 generation: self.generation,
51 }
52 }
53}
54
55fn highlight_source(highlighter: Option<crate::Highlighter>, code: &str) -> Option<Vec<Span>> {
56 let language = crate::source::LANGUAGES[0];
57 highlighter
58 .and_then(|highlighter| highlighter(language, code))
59 .or_else(|| crate::source::is_markdown(language).then(|| crate::source::spans(code)))
60}
61
62impl SourceText {
63 pub fn new(code: impl Into<Arc<str>>, cx: &App) -> Self {
64 let code: Arc<str> = code.into();
65 let spans = highlight_source(crate::highlight::installed(cx), &code).map(Into::into);
66 let ranges = split_lines(&code, 0..code.len());
67 let hashes = ranges.iter().map(|range| hash_line(&code, range)).collect();
68 Self {
69 code,
70 ranges: ranges.into(),
71 hashes,
72 spans,
73 generation: 0,
74 sized: RefCell::new(None),
75 }
76 }
77
78 pub fn text(&self) -> &str {
79 &self.code
80 }
81
82 pub fn lines(&self) -> usize {
84 self.ranges.len()
85 }
86
87 pub fn line_of(&self, offset: usize) -> usize {
89 self.ranges
90 .partition_point(|range| range.end < offset)
91 .min(self.ranges.len() - 1)
92 }
93
94 pub fn spans(&self) -> Option<&[(Range<usize>, theme::HighlightKind)]> {
98 self.spans.as_deref()
99 }
100
101 pub fn edit(&mut self, code: &str, cx: &App) -> Option<SpanJob> {
105 let old = self.code.as_bytes();
106 let new = code.as_bytes();
107 let prefix = old.iter().zip(new).take_while(|(a, b)| a == b).count();
108 if prefix == old.len() && prefix == new.len() {
109 return None;
110 }
111 let room = old.len().min(new.len()) - prefix;
112 let suffix = old
113 .iter()
114 .rev()
115 .zip(new.iter().rev())
116 .take(room)
117 .take_while(|(a, b)| a == b)
118 .count();
119 let old_end = old.len() - suffix;
120 let delta = (new.len() - suffix) as isize - old_end as isize;
121 let shift = |at: usize| at.wrapping_add_signed(delta);
122
123 let first = self.line_of(prefix);
124 let last = self.line_of(old_end);
125 let code: Arc<str> = code.into();
126 let lines = split_lines(
127 &code,
128 self.ranges[first].start..shift(self.ranges[last].end),
129 );
130 let hashes: Vec<u64> = lines.iter().map(|range| hash_line(&code, range)).collect();
131 let cut = first..last + 1;
132
133 if let Some(sized) = self.sized.get_mut() {
134 let (size, line, indent) = sized.at;
135 sized.keys = splice(
136 &sized.keys,
137 cut.clone(),
138 hashes.iter().map(|hash| key(*hash, size)),
139 );
140 sized.guesses = splice(
141 &sized.guesses,
142 cut.clone(),
143 lines.iter().map(|range| guess(range, line, indent)),
144 );
145 sized.gaps = vec![px(0.0); sized.keys.len()].into();
146 }
147 self.hashes = splice(&self.hashes, cut.clone(), hashes);
148 self.ranges = self.ranges[..first]
149 .iter()
150 .cloned()
151 .chain(lines)
152 .chain(
153 self.ranges[last + 1..]
154 .iter()
155 .map(|range| shift(range.start)..shift(range.end)),
156 )
157 .collect();
158 self.spans = self.spans.as_ref().map(|spans| {
159 let mut kept = Vec::with_capacity(spans.len());
160 for (range, kind) in spans.iter() {
161 if range.start < prefix {
162 kept.push((range.start..range.end.min(prefix), *kind));
163 }
164 if range.end > old_end {
165 kept.push((shift(range.start.max(old_end))..shift(range.end), *kind));
166 }
167 }
168 kept.into()
169 });
170 self.code = code;
171 self.generation += 1;
172 Some(SpanJob {
173 code: self.code.clone(),
174 highlighter: crate::highlight::installed(cx),
175 generation: self.generation,
176 })
177 }
178
179 pub fn set_spans(&mut self, spans: Spans) -> bool {
182 if spans.generation != self.generation {
183 return false;
184 }
185 self.spans = spans.spans.map(Into::into);
186 true
187 }
188
189 fn sized(&self, size: f32, line: Pixels, indent: Pixels) -> Sized {
190 let at = (size.to_bits(), line, indent);
191 let mut held = self.sized.borrow_mut();
192 if let Some(sized) = held.as_ref()
193 && sized.at == at
194 {
195 return sized.clone();
196 }
197 let sized = Sized {
198 at,
199 keys: self.hashes.iter().map(|hash| key(*hash, at.0)).collect(),
200 gaps: vec![px(0.0); self.ranges.len()].into(),
201 guesses: self
202 .ranges
203 .iter()
204 .map(|range| guess(range, line, indent))
205 .collect(),
206 };
207 *held = Some(sized.clone());
208 sized
209 }
210}
211
212fn split_lines(code: &str, within: Range<usize>) -> Vec<Range<usize>> {
215 let mut ranges = Vec::new();
216 let mut at = within.start;
217 for line in code[within].split('\n') {
218 ranges.push(at..at + line.len());
219 at += line.len() + 1;
220 }
221 ranges
222}
223
224fn hash_line(code: &str, range: &Range<usize>) -> u64 {
225 let mut hasher = DefaultHasher::new();
226 "source".hash(&mut hasher);
227 code[range.clone()].hash(&mut hasher);
228 hasher.finish()
229}
230
231fn key(hash: u64, size: u32) -> u64 {
232 let mut hasher = DefaultHasher::new();
233 hash.hash(&mut hasher);
234 size.hash(&mut hasher);
235 hasher.finish()
236}
237
238fn guess(range: &Range<usize>, line: Pixels, indent: Pixels) -> Guess {
239 Guess {
240 chars: range.len(),
241 line,
242 rows: 0,
243 extra: px(0.0),
244 indent,
245 }
246}
247
248fn splice<T: Clone>(old: &[T], cut: Range<usize>, new: impl IntoIterator<Item = T>) -> Rc<[T]> {
250 old[..cut.start]
251 .iter()
252 .cloned()
253 .chain(new)
254 .chain(old[cut.end..].iter().cloned())
255 .collect()
256}
257
258pub fn render_source(source: &SourceText, editing: Editing, cx: &mut App) -> AnyElement {
268 let code: &str = &source.code;
269 let Editing {
270 selection,
271 caret_on,
272 layouts,
273 annotations,
274 typography,
275 keep,
276 scroll,
277 ..
278 } = editing;
279 let mut theme = Theme::of(cx).clone();
280 theme.font_mono = theme.font_body.clone();
281 let mut typography = typography.unwrap_or_else(|| cx.typography());
282 typography.code = typography.body;
283 let overlay = Overlay {
284 block: 0,
285 part: Part::Code,
286 selection,
287 caret_on,
288 caret_shape: cx.caret_shape(),
289 caret_height: cx.caret_height(),
290 caret_inactive: cx.inactive_caret(),
291 window_active: cx.active_window().is_some(),
293 layouts,
294 annotations,
295 placeholder: None,
296 caption: Caption::default(),
297 toggle: None,
299 image: None,
300 image_overlay: None,
301 image_overlay_corner: ImageOverlayCorner::BottomRight,
302 fence: None,
303 sizing: None,
304 table_controls: false,
305 copy: CopyButton::Hidden,
307 base: None,
308 highlight: crate::marks::highlight_paint_of(cx),
309 find: crate::find::find_paint_of(cx),
310 jump: None,
311 };
312 let style = cx.source_style();
313 let count = source.ranges.len();
314 let gutter = Gutter::new(&style, count, &typography, &theme);
315
316 let Some(layouts) = layouts else {
317 let (underlay, lines) = code_lines(
318 Some(crate::source::LANGUAGES[0]),
319 code,
320 overlay,
321 &typography,
322 &theme,
323 cx,
324 );
325 let lines = lines
326 .into_iter()
327 .enumerate()
328 .map(|(index, line)| gutter.row(index, line))
329 .collect();
330 return div()
331 .flex()
332 .flex_col()
333 .child(code_body(0, underlay, lines, &typography, true))
334 .into_any_element();
335 };
336
337 let spans = source.spans.clone();
340 let ranges = source.ranges.clone();
341 let mut kept: Vec<usize> = keep.to_vec();
342 if let Some(selection) = selection {
343 kept.push(source.line_of(selection.head.offset));
344 kept.push(source.line_of(selection.anchor.offset));
345 }
346 let line = px(typography.code.line_height());
347 let indent = px(gutter.width() + 2.0 * CODE_PADDING_X);
348 let sized = source.sized(typography.code.size(), line, indent);
349 layouts.prune(&sized.keys);
350 let paint = RowPaint::new(&overlay, code, typography.code.size(), &theme);
351 let code = source.code.clone();
352 let sink = layouts.clone();
353 let column = Column {
354 layouts: layouts.clone(),
355 keys: sized.keys,
356 gaps: sized.gaps,
357 guesses: sized.guesses,
358 keep: kept,
359 scroll: scroll.cloned(),
360 item_of: {
361 let ranges = ranges.clone();
362 Rc::new(move |at| ranges.partition_point(|line| line.end < at.offset))
363 },
364 build: Box::new(move |index, window, _| {
365 let span = ranges[index].clone();
366 let paint = RowPaint {
367 hollow: !window.is_window_active(),
368 ..paint.clone()
369 };
370 let styled = code_line(
371 &code[span.clone()],
372 span.start,
373 spans.as_deref(),
374 paint.cut(),
375 &theme,
376 );
377 let layout = styled.layout().clone();
378 let sink = sink.clone();
379 let underlay = canvas(
380 |_, _, _| (),
381 move |_, _, window, _| {
382 sink.record(
383 0,
384 Part::Code,
385 span.clone(),
386 layout.clone(),
387 Shown::default(),
388 );
389 paint.paint(&span, &layout, window);
390 },
391 )
392 .absolute()
393 .size_full();
394 let text = div().relative().child(underlay).child(styled);
395 div()
396 .px(px(CODE_PADDING_X))
397 .child(gutter.row(index, text.into_any_element()))
398 .into_any_element()
399 }),
400 };
401 div()
402 .flex()
403 .flex_col()
404 .py(px(CODE_PADDING_Y))
405 .text_size(px(typography.code.size()))
406 .line_height(line)
407 .child(column)
408 .into_any_element()
409}
410
411#[derive(Clone)]
413struct Gutter {
414 shown: bool,
415 width: f32,
416 gap: f32,
417 color: Hsla,
418 font: SharedString,
419}
420
421impl Gutter {
422 fn new(
423 style: &crate::SourceStyle,
424 lines: usize,
425 typography: &Typography,
426 theme: &Theme,
427 ) -> Self {
428 let digits = lines.to_string().len().max(style.gutter_min_digits);
429 let gap = style.gutter_gap.max(0.0) * typography.code.size();
430 Self {
431 shown: style.line_numbers,
432 width: digits as f32 * typography.code.size() + gap,
433 gap,
434 color: style.gutter_color.unwrap_or(theme.text_faint),
435 font: theme.font_mono.clone(),
436 }
437 }
438
439 fn width(&self) -> f32 {
440 if self.shown { self.width } else { 0.0 }
441 }
442
443 fn row(&self, index: usize, line: AnyElement) -> AnyElement {
446 if !self.shown {
447 return line;
448 }
449 div()
450 .flex()
451 .items_start()
452 .child(
453 div()
454 .w(px(self.width))
455 .flex_shrink_0()
456 .pr(px(self.gap))
457 .font_family(self.font.clone())
458 .text_color(self.color)
459 .text_right()
460 .child((index + 1).to_string()),
461 )
462 .child(div().flex_1().min_w_0().child(line))
463 .into_any_element()
464 }
465}
466
467pub(super) fn code_lines(
470 language: Option<&str>,
471 code: &str,
472 overlay: Overlay,
473 typography: &Typography,
474 theme: &Theme,
475 cx: &App,
476) -> (AnyElement, Vec<AnyElement>) {
477 let ix = overlay.block;
478 let spans = crate::highlight::spans(cx, language, code).or_else(|| {
483 language
484 .filter(|language| crate::source::is_markdown(language))
485 .map(|_| crate::source::spans(code))
486 });
487 let paint = RowPaint::new(&overlay, code, typography.code.size(), theme);
492 let mut rows: Vec<(Range<usize>, TextLayout)> = Vec::new();
493 let mut offset = 0usize;
494 let lines: Vec<AnyElement> = code
495 .split('\n')
496 .map(|line| {
497 let start = offset;
498 offset += line.len() + 1;
499 let styled = code_line(line, start, spans.as_deref(), paint.cut(), theme);
500 rows.push((start..start + line.len(), styled.layout().clone()));
501 styled.into_any_element()
502 })
503 .collect();
504
505 let sink = overlay.layouts.cloned();
506 let underlay = canvas(
507 |_, _, _| (),
508 move |_, _, window, _| {
509 for (span, layout) in &rows {
510 if let Some(sink) = &sink {
511 sink.record(
512 ix,
513 Part::Code,
514 span.clone(),
515 layout.clone(),
516 Shown::default(),
517 );
518 }
519 paint.paint(span, layout, window);
520 }
521 },
522 )
523 .absolute()
524 .size_full();
525
526 (underlay.into_any_element(), lines)
527}
528
529type Span = (Range<usize>, theme::HighlightKind);
531
532fn code_line(
539 line: &str,
540 start: usize,
541 spans: Option<&[Span]>,
542 glyph: Option<&Range<usize>>,
543 theme: &Theme,
544) -> StyledText {
545 let mono = font(theme.font_mono.clone());
546 let run = |len: usize, color: Hsla| TextRun {
547 len,
548 font: mono.clone(),
549 color,
550 background_color: None,
551 underline: None,
552 strikethrough: None,
553 };
554 let mut runs = Vec::new();
555 let mut pos = 0usize;
556 if let Some(spans) = spans {
557 let end = start + line.len();
558 for (range, kind) in spans.iter().filter(|(r, _)| r.end > start && r.start < end) {
559 let s = range.start.clamp(start, end) - start;
560 let e = range.end.min(end) - start;
561 if s > pos {
562 runs.push(run(s - pos, theme.text));
563 }
564 runs.push(run(e - s, theme.syntax.color(*kind)));
565 pos = e;
566 }
567 }
568 if pos < line.len() {
569 runs.push(run(line.len() - pos, theme.text));
570 }
571 if runs.is_empty() {
572 runs.push(run(0, theme.text));
573 }
574 if let Some(glyph) =
575 glyph.filter(|glyph| glyph.start >= start && glyph.end <= start + line.len())
576 {
577 runs =
578 ui::input::caret::recoloured(runs, &(glyph.start - start..glyph.end - start), theme.bg);
579 }
580 StyledText::new(SharedString::from(line.to_string())).with_runs(runs)
581}
582
583#[derive(Clone)]
586struct RowPaint {
587 caret: Option<usize>,
588 glyph: Option<Range<usize>>,
590 affinity: Affinity,
591 shape: ui::input::CaretShape,
592 height: ui::input::CaretHeight,
593 hollow: bool,
594 font: SharedString,
595 selected: Option<Range<usize>>,
596 annotated: Vec<(Range<usize>, Hsla)>,
597 caret_color: Hsla,
598 selection_color: Hsla,
599 code_size: f32,
600}
601
602impl RowPaint {
603 fn new(overlay: &Overlay, code: &str, code_size: f32, theme: &Theme) -> Self {
604 let caret = overlay.caret_painted();
605 Self {
606 caret,
607 glyph: caret.and_then(|offset| overlay.covered(code, offset)),
608 affinity: overlay.affinity(),
609 shape: overlay.caret_shape,
610 height: overlay.caret_height,
611 hollow: overlay.caret_hollow(),
612 font: theme.font_mono.clone(),
613 selected: overlay.selected(code.len()),
614 annotated: overlay.annotated(code.len(), theme),
615 caret_color: theme.caret,
616 selection_color: theme.selection,
617 code_size,
618 }
619 }
620
621 fn cut(&self) -> Option<&Range<usize>> {
623 self.glyph
624 .as_ref()
625 .filter(|_| self.shape.cuts_out(self.hollow))
626 }
627
628 fn paint(&self, span: &Range<usize>, layout: &TextLayout, window: &mut Window) {
629 let wash = |range: &Range<usize>, color: Hsla, window: &mut Window| {
630 let (from, to) = (range.start.max(span.start), range.end.min(span.end));
631 if from < to {
632 for rect in range_rects(layout, &(from - span.start..to - span.start), 0.0, 0.0) {
633 window.paint_quad(quad(
634 rect,
635 px(2.0),
636 color,
637 px(0.0),
638 gpui::transparent_black(),
639 BorderStyle::default(),
640 ));
641 }
642 }
643 };
644 for (range, color) in &self.annotated {
645 wash(range, *color, window);
646 }
647 if let Some(range) = &self.selected {
648 wash(range, self.selection_color, window);
649 }
650 if let Some(offset) = self.caret.filter(|at| span.contains(at) || *at == span.end) {
651 let glyph = self
652 .cut()
653 .map(|glyph| glyph.start - span.start..glyph.end - span.start);
654 paint_caret(
655 layout,
656 offset - span.start,
657 self.affinity,
658 glyph.as_ref(),
659 CaretPaint {
660 shape: self.shape,
661 height: self.height,
662 hollow: self.hollow,
663 color: self.caret_color,
664 size: self.code_size,
665 face: font(self.font.clone()),
666 },
667 window,
668 );
669 }
670 }
671}
672
673pub(super) fn code_block(
674 language: Option<&str>,
675 code: &str,
676 overlay: Overlay,
677 typography: &Typography,
678 theme: &Theme,
679 window: &mut Window,
680 cx: &mut App,
681) -> AnyElement {
682 let ix = overlay.block;
683 let (underlay, lines) = code_lines(language, code, overlay, typography, theme, cx);
684 let body = code_body(
685 ix,
686 underlay,
687 lines,
688 typography,
689 cx.markdown_layout().wrap_code,
690 );
691
692 fence_panel(theme)
693 .child(
697 fence_band(theme)
698 .text_color(match language {
699 Some(_) => theme.text_muted,
700 None => theme.text_faint,
701 })
702 .child(
706 div()
707 .relative()
708 .children(overlay.layouts.map(|layouts| {
709 let layouts = layouts.clone();
710 canvas(
711 move |bounds, _, _| layouts.record_language(ix, bounds),
712 |_, _, _, _| (),
713 )
714 .absolute()
715 .size_full()
716 }))
717 .child(SharedString::from(
718 language.unwrap_or(PLAIN_LANGUAGE).to_string(),
719 )),
720 ),
721 )
722 .child(body)
723 .children(
724 (overlay.copy == CopyButton::Shown).then(|| copy_button(code, ix, theme, window, cx)),
725 )
726 .into_any_element()
727}
728
729pub fn fence_panel(theme: &Theme) -> gpui::Div {
731 div()
732 .rounded(px(Theme::panel_radius()))
733 .bg(theme.ink(0.035))
734 .border_1()
735 .border_color(theme.border)
736 .overflow_hidden()
737 .relative()
738}
739
740pub fn fence_band(theme: &Theme) -> gpui::Div {
743 div()
744 .relative()
745 .flex()
746 .flex_row()
747 .items_center()
748 .px(px(CODE_PADDING_X))
749 .py(px(5.0))
750 .border_b_1()
751 .border_color(theme.border)
752 .bg(theme.ink(0.02))
753 .text_style(TextStyle::Subheadline)
754}
755
756pub(super) fn code_body(
758 ix: usize,
759 underlay: AnyElement,
760 lines: Vec<AnyElement>,
761 typography: &Typography,
762 wrap: bool,
763) -> AnyElement {
764 let column = div()
765 .flex()
766 .flex_col()
767 .px(px(CODE_PADDING_X))
768 .children(lines);
769 let body = div()
770 .id(ElementId::named_usize("md-code", ix))
771 .relative()
772 .py(px(CODE_PADDING_Y))
773 .text_size(px(typography.code.size()))
774 .line_height(px(typography.code.line_height()))
775 .child(underlay);
776 if wrap {
777 body.child(column.w_full()).into_any_element()
780 } else {
781 ui::scroll::Viewport::new(
782 format!("md-code-scroll-{ix}"),
783 body.flex()
784 .flex_row()
785 .whitespace_nowrap()
786 .child(column.items_start()),
793 gpui::Axis::Horizontal,
794 )
795 .into_any_element()
796 }
797}
798
799pub(crate) fn copy_button(
806 code: &str,
807 ix: usize,
808 theme: &Theme,
809 window: &mut Window,
810 cx: &mut App,
811) -> AnyElement {
812 let copied = window.use_keyed_state(ElementId::named_usize("md-copied", ix), cx, |_, _| false);
813 let showing = *copied.read(cx);
814 let text: SharedString = code.to_string().into();
815
816 div()
817 .id(ElementId::named_usize("md-copy", ix))
818 .absolute()
819 .top(px(3.0))
820 .right(px(5.0))
821 .h(px(20.0))
822 .px(px(6.0))
823 .rounded(px(5.0))
824 .flex()
825 .items_center()
826 .cursor_pointer()
827 .text_style(TextStyle::Caption)
828 .text_color(theme.text_muted)
829 .hover(|el| el.bg(theme.element_hover))
830 .child(if showing { "Copied" } else { "Copy" })
831 .on_click({
832 let copied = copied.clone();
833 move |_, _, cx| {
834 cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.to_string()));
835 copied.update(cx, |state, cx| {
836 *state = true;
837 cx.notify();
838 });
839 }
840 })
841 .on_hover(move |hovering, _, cx| {
842 if !*hovering && *copied.read(cx) {
843 copied.update(cx, |state, cx| {
844 *state = false;
845 cx.notify();
846 });
847 }
848 })
849 .into_any_element()
850}