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