1use super::*;
4use ui::input::overdraw::Layers;
5
6pub(super) fn code_lines(
9 language: Option<&str>,
10 code: &str,
11 overlay: Overlay,
12 typography: &Typography,
13 theme: &Theme,
14 cx: &App,
15) -> (AnyElement, AnyElement, Vec<AnyElement>) {
16 let ix = overlay.block;
17 let spans = crate::fence::highlight::spans(cx, language, code).or_else(|| {
22 language
23 .filter(|language| crate::source::is_markdown(language))
24 .map(|_| crate::source::spans(code))
25 });
26 let paint = RowPaint::new(&overlay, code, typography.code.size(), theme);
31 let mut rows: Vec<(Range<usize>, TextLayout, Vec<TextRun>)> = Vec::new();
32 let mut offset = 0usize;
33 let lines: Vec<AnyElement> = code
34 .split('\n')
35 .map(|line| {
36 let start = offset;
37 offset += line.len() + 1;
38 let (styled, runs) = code_line(line, start, spans.as_deref(), theme);
39 rows.push((start..start + line.len(), styled.layout().clone(), runs));
40 styled.into_any_element()
41 })
42 .collect();
43
44 let sink = overlay.layouts.cloned();
45 let over = {
46 let (rows, paint) = (rows.clone(), paint.clone());
47 canvas(
48 |_, _, _| (),
49 move |_, _, window, _| {
50 for (span, layout, runs) in &rows {
51 paint.paint_over(span, layout, runs, window);
52 }
53 },
54 )
55 .absolute()
56 .size_full()
57 };
58 let underlay = canvas(
59 |_, _, _| (),
60 move |_, _, window, _| {
61 for (span, layout, _) in &rows {
62 if let Some(sink) = &sink {
63 sink.record(
64 ix,
65 Part::Code,
66 span.clone(),
67 layout.clone(),
68 Shown::default(),
69 );
70 }
71 paint.paint_under(span, layout, window);
72 }
73 },
74 )
75 .absolute()
76 .size_full();
77
78 (underlay.into_any_element(), over.into_any_element(), lines)
79}
80
81pub(super) type Span = (Range<usize>, theme::HighlightKind);
83
84pub(super) fn code_line(
90 line: &str,
91 start: usize,
92 spans: Option<&[Span]>,
93 theme: &Theme,
94) -> (StyledText, Vec<TextRun>) {
95 let mono = font(theme.font_mono.clone());
96 let run = |len: usize, color: Hsla| TextRun {
97 len,
98 font: mono.clone(),
99 color,
100 background_color: None,
101 underline: None,
102 strikethrough: None,
103 };
104 let mut runs = Vec::new();
105 let mut pos = 0usize;
106 if let Some(spans) = spans {
107 let end = start + line.len();
108 for (range, kind) in spans.iter().filter(|(r, _)| r.end > start && r.start < end) {
109 let s = range.start.clamp(start, end) - start;
110 let e = range.end.min(end) - start;
111 if s > pos {
112 runs.push(run(s - pos, theme.text));
113 }
114 runs.push(run(e - s, theme.syntax.color(*kind)));
115 pos = e;
116 }
117 }
118 if pos < line.len() {
119 runs.push(run(line.len() - pos, theme.text));
120 }
121 if runs.is_empty() {
122 runs.push(run(0, theme.text));
123 }
124 let styled = StyledText::new(SharedString::from(line.to_string())).with_runs(runs.clone());
125 (styled, runs)
126}
127
128#[derive(Clone)]
132pub(super) struct RowPaint {
133 caret: Option<usize>,
134 glyph: Option<Range<usize>>,
136 affinity: Affinity,
137 shape: ui::input::CaretShape,
138 height: ui::input::CaretHeight,
139 hollow: bool,
140 font: SharedString,
141 layers: Layers<()>,
143 caret_color: Hsla,
144 page: Hsla,
146 code_size: f32,
147}
148
149impl RowPaint {
150 pub(super) fn hollow(&self, hollow: bool) -> Self {
152 Self {
153 hollow,
154 ..self.clone()
155 }
156 }
157
158 pub(super) fn new(overlay: &Overlay, code: &str, code_size: f32, theme: &Theme) -> Self {
159 let caret = overlay.caret_painted();
160 Self {
161 caret,
162 glyph: caret.and_then(|offset| overlay.covered(code, offset)),
163 affinity: overlay.affinity(),
164 shape: overlay.caret_shape,
165 height: overlay.caret_height,
166 hollow: overlay.caret_hollow(),
167 font: theme.font_mono.clone(),
168 layers: {
169 let mut layers = Layers::new();
170 for (range, color) in overlay.annotated(code.len(), theme) {
171 layers = layers.wash(range, color, ());
172 }
173 if let Some(range) = overlay.selected(code.len()) {
174 layers = layers.select(range, theme.selection, ());
175 }
176 layers
177 },
178 caret_color: theme.caret,
179 page: theme.bg,
180 code_size,
181 }
182 }
183
184 fn cut(&self) -> Option<&Range<usize>> {
186 self.glyph
187 .as_ref()
188 .filter(|_| self.shape.cuts_out(self.hollow))
189 }
190
191 pub(super) fn paint_under(
192 &self,
193 span: &Range<usize>,
194 layout: &TextLayout,
195 window: &mut Window,
196 ) {
197 for (range, color, ()) in self.layers.clipped(span).painted() {
198 for rect in range_rects(layout, range, 0.0, 0.0) {
199 window.paint_quad(quad(
200 rect,
201 px(2.0),
202 color,
203 px(0.0),
204 gpui::transparent_black(),
205 BorderStyle::default(),
206 ));
207 }
208 }
209 }
210
211 pub(super) fn paint_over(
213 &self,
214 span: &Range<usize>,
215 layout: &TextLayout,
216 runs: &[TextRun],
217 window: &mut Window,
218 ) {
219 let layers = self.layers.clipped(span);
220 ui::input::overdraw::paint(
221 layout.line_layouts().iter().map(|line| &**line),
222 layout.bounds().origin,
223 layout.line_height(),
224 px(2.0),
225 &ui::input::overdraw::overdraws(runs, self.page, &layers),
226 window,
227 );
228 if let Some(offset) = self.caret.filter(|at| span.contains(at) || *at == span.end) {
229 let glyph = self
230 .cut()
231 .map(|glyph| glyph.start - span.start..glyph.end - span.start);
232 paint_caret(
233 layout,
234 offset - span.start,
235 self.affinity,
236 glyph.as_ref(),
237 CaretPaint {
238 shape: self.shape,
239 height: self.height,
240 hollow: self.hollow,
241 color: self.caret_color,
242 cover: ui::input::caret::cover(
243 runs,
244 offset - span.start,
245 self.page,
246 &layers,
247 self.caret_color,
248 ),
249 size: self.code_size,
250 face: font(self.font.clone()),
251 },
252 window,
253 );
254 }
255 }
256}
257
258#[allow(clippy::too_many_arguments)]
259pub(super) fn code_block(
260 language: Option<&str>,
261 code: &str,
262 note: Option<SharedString>,
263 overlay: Overlay,
264 typography: &Typography,
265 theme: &Theme,
266 window: &mut Window,
267 cx: &mut App,
268) -> AnyElement {
269 let ix = overlay.block;
270 let (underlay, over, lines) = code_lines(language, code, overlay, typography, theme, cx);
271 let body = code_body(
272 ix,
273 underlay,
274 over,
275 lines,
276 typography,
277 cx.markdown_layout().wrap_code,
278 );
279
280 fence_panel(theme)
281 .child(
285 fence_band(theme)
286 .text_color(match language {
287 Some(_) => theme.text_muted,
288 None => theme.text_faint,
289 })
290 .child(
294 div()
295 .relative()
296 .children(overlay.layouts.map(|layouts| {
297 let layouts = layouts.clone();
298 canvas(
299 move |bounds, _, _| layouts.record_language(ix, bounds),
300 |_, _, _, _| (),
301 )
302 .absolute()
303 .size_full()
304 }))
305 .child(SharedString::from(
306 language.unwrap_or(PLAIN_LANGUAGE).to_string(),
307 )),
308 ),
309 )
310 .child(body)
311 .children(note.map(|note| fence_note(note, theme)))
312 .children(
313 (overlay.copy == CopyButton::Shown).then(|| copy_button(code, ix, theme, window, cx)),
314 )
315 .into_any_element()
316}
317
318pub(super) fn code_body(
320 ix: usize,
321 underlay: AnyElement,
322 over: AnyElement,
323 lines: Vec<AnyElement>,
324 typography: &Typography,
325 wrap: bool,
326) -> AnyElement {
327 let column = div()
328 .flex()
329 .flex_col()
330 .px(px(CODE_PADDING_X))
331 .children(lines);
332 let body = div()
333 .id(ElementId::named_usize("md-code", ix))
334 .relative()
335 .py(px(CODE_PADDING_Y))
336 .text_size(px(typography.code.size()))
337 .line_height(px(typography.code.line_height()))
338 .child(underlay);
339 if wrap {
340 body.child(column.w_full()).child(over).into_any_element()
343 } else {
344 ui::scroll::Viewport::new(
345 format!("md-code-scroll-{ix}"),
346 body.flex()
347 .flex_row()
348 .whitespace_nowrap()
349 .child(column.items_start())
356 .child(over),
357 gpui::Axis::Horizontal,
358 )
359 .into_any_element()
360 }
361}