1mod parser;
2
3use crate::parser::CodeBlockKind;
4use futures::FutureExt;
5use gpui::{
6 actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
7 DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
8 Hitbox, Hsla, KeyContext, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point,
9 Render, StrikethroughStyle, Style, StyledText, Task, TextLayout, TextRun, TextStyle,
10 TextStyleRefinement, View,
11};
12use language::{Language, LanguageRegistry, Rope};
13use parser::{parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
14use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
15use theme::SyntaxTheme;
16use ui::prelude::*;
17use util::{ResultExt, TryFutureExt};
18
19#[derive(Clone)]
20pub struct MarkdownStyle {
21 pub code_block: TextStyleRefinement,
22 pub inline_code: TextStyleRefinement,
23 pub block_quote: TextStyleRefinement,
24 pub link: TextStyleRefinement,
25 pub rule_color: Hsla,
26 pub block_quote_border_color: Hsla,
27 pub syntax: Arc<SyntaxTheme>,
28 pub selection_background_color: Hsla,
29}
30
31pub struct Markdown {
32 source: String,
33 selection: Selection,
34 pressed_link: Option<RenderedLink>,
35 autoscroll_request: Option<usize>,
36 style: MarkdownStyle,
37 parsed_markdown: ParsedMarkdown,
38 should_reparse: bool,
39 pending_parse: Option<Task<Option<()>>>,
40 focus_handle: FocusHandle,
41 language_registry: Option<Arc<LanguageRegistry>>,
42}
43
44actions!(markdown, [Copy]);
45
46impl Markdown {
47 pub fn new(
48 source: String,
49 style: MarkdownStyle,
50 language_registry: Option<Arc<LanguageRegistry>>,
51 cx: &mut ViewContext<Self>,
52 ) -> Self {
53 let focus_handle = cx.focus_handle();
54 let mut this = Self {
55 source,
56 selection: Selection::default(),
57 pressed_link: None,
58 autoscroll_request: None,
59 style,
60 should_reparse: false,
61 parsed_markdown: ParsedMarkdown::default(),
62 pending_parse: None,
63 focus_handle,
64 language_registry,
65 };
66 this.parse(cx);
67 this
68 }
69
70 pub fn append(&mut self, text: &str, cx: &mut ViewContext<Self>) {
71 self.source.push_str(text);
72 self.parse(cx);
73 }
74
75 pub fn reset(&mut self, source: String, cx: &mut ViewContext<Self>) {
76 if source == self.source() {
77 return;
78 }
79 self.source = source;
80 self.selection = Selection::default();
81 self.autoscroll_request = None;
82 self.pending_parse = None;
83 self.should_reparse = false;
84 self.parsed_markdown = ParsedMarkdown::default();
85 self.parse(cx);
86 }
87
88 pub fn source(&self) -> &str {
89 &self.source
90 }
91
92 fn copy(&self, text: &RenderedText, cx: &mut ViewContext<Self>) {
93 let text = text.text_for_range(self.selection.start..self.selection.end);
94 cx.write_to_clipboard(ClipboardItem::new(text));
95 }
96
97 fn parse(&mut self, cx: &mut ViewContext<Self>) {
98 if self.source.is_empty() {
99 return;
100 }
101
102 if self.pending_parse.is_some() {
103 self.should_reparse = true;
104 return;
105 }
106
107 let text = self.source.clone();
108 let parsed = cx.background_executor().spawn(async move {
109 let text = SharedString::from(text);
110 let events = Arc::from(parse_markdown(text.as_ref()));
111 anyhow::Ok(ParsedMarkdown {
112 source: text,
113 events,
114 })
115 });
116
117 self.should_reparse = false;
118 self.pending_parse = Some(cx.spawn(|this, mut cx| {
119 async move {
120 let parsed = parsed.await?;
121 this.update(&mut cx, |this, cx| {
122 this.parsed_markdown = parsed;
123 this.pending_parse.take();
124 if this.should_reparse {
125 this.parse(cx);
126 }
127 cx.notify();
128 })
129 .ok();
130 anyhow::Ok(())
131 }
132 .log_err()
133 }));
134 }
135}
136
137impl Render for Markdown {
138 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
139 MarkdownElement::new(
140 cx.view().clone(),
141 self.style.clone(),
142 self.language_registry.clone(),
143 )
144 }
145}
146
147impl FocusableView for Markdown {
148 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
149 self.focus_handle.clone()
150 }
151}
152
153#[derive(Copy, Clone, Default, Debug)]
154struct Selection {
155 start: usize,
156 end: usize,
157 reversed: bool,
158 pending: bool,
159}
160
161impl Selection {
162 fn set_head(&mut self, head: usize) {
163 if head < self.tail() {
164 if !self.reversed {
165 self.end = self.start;
166 self.reversed = true;
167 }
168 self.start = head;
169 } else {
170 if self.reversed {
171 self.start = self.end;
172 self.reversed = false;
173 }
174 self.end = head;
175 }
176 }
177
178 fn tail(&self) -> usize {
179 if self.reversed {
180 self.end
181 } else {
182 self.start
183 }
184 }
185}
186
187#[derive(Clone)]
188struct ParsedMarkdown {
189 source: SharedString,
190 events: Arc<[(Range<usize>, MarkdownEvent)]>,
191}
192
193impl Default for ParsedMarkdown {
194 fn default() -> Self {
195 Self {
196 source: SharedString::default(),
197 events: Arc::from([]),
198 }
199 }
200}
201
202pub struct MarkdownElement {
203 markdown: View<Markdown>,
204 style: MarkdownStyle,
205 language_registry: Option<Arc<LanguageRegistry>>,
206}
207
208impl MarkdownElement {
209 fn new(
210 markdown: View<Markdown>,
211 style: MarkdownStyle,
212 language_registry: Option<Arc<LanguageRegistry>>,
213 ) -> Self {
214 Self {
215 markdown,
216 style,
217 language_registry,
218 }
219 }
220
221 fn load_language(&self, name: &str, cx: &mut WindowContext) -> Option<Arc<Language>> {
222 let language = self
223 .language_registry
224 .as_ref()?
225 .language_for_name(name)
226 .map(|language| language.ok())
227 .shared();
228
229 match language.clone().now_or_never() {
230 Some(language) => language,
231 None => {
232 let markdown = self.markdown.downgrade();
233 cx.spawn(|mut cx| async move {
234 language.await;
235 markdown.update(&mut cx, |_, cx| cx.notify())
236 })
237 .detach_and_log_err(cx);
238 None
239 }
240 }
241 }
242
243 fn paint_selection(
244 &mut self,
245 bounds: Bounds<Pixels>,
246 rendered_text: &RenderedText,
247 cx: &mut WindowContext,
248 ) {
249 let selection = self.markdown.read(cx).selection;
250 let selection_start = rendered_text.position_for_source_index(selection.start);
251 let selection_end = rendered_text.position_for_source_index(selection.end);
252
253 if let Some(((start_position, start_line_height), (end_position, end_line_height))) =
254 selection_start.zip(selection_end)
255 {
256 if start_position.y == end_position.y {
257 cx.paint_quad(quad(
258 Bounds::from_corners(
259 start_position,
260 point(end_position.x, end_position.y + end_line_height),
261 ),
262 Pixels::ZERO,
263 self.style.selection_background_color,
264 Edges::default(),
265 Hsla::transparent_black(),
266 ));
267 } else {
268 cx.paint_quad(quad(
269 Bounds::from_corners(
270 start_position,
271 point(bounds.right(), start_position.y + start_line_height),
272 ),
273 Pixels::ZERO,
274 self.style.selection_background_color,
275 Edges::default(),
276 Hsla::transparent_black(),
277 ));
278
279 if end_position.y > start_position.y + start_line_height {
280 cx.paint_quad(quad(
281 Bounds::from_corners(
282 point(bounds.left(), start_position.y + start_line_height),
283 point(bounds.right(), end_position.y),
284 ),
285 Pixels::ZERO,
286 self.style.selection_background_color,
287 Edges::default(),
288 Hsla::transparent_black(),
289 ));
290 }
291
292 cx.paint_quad(quad(
293 Bounds::from_corners(
294 point(bounds.left(), end_position.y),
295 point(end_position.x, end_position.y + end_line_height),
296 ),
297 Pixels::ZERO,
298 self.style.selection_background_color,
299 Edges::default(),
300 Hsla::transparent_black(),
301 ));
302 }
303 }
304 }
305
306 fn paint_mouse_listeners(
307 &mut self,
308 hitbox: &Hitbox,
309 rendered_text: &RenderedText,
310 cx: &mut WindowContext,
311 ) {
312 let is_hovering_link = hitbox.is_hovered(cx)
313 && !self.markdown.read(cx).selection.pending
314 && rendered_text
315 .link_for_position(cx.mouse_position())
316 .is_some();
317
318 if is_hovering_link {
319 cx.set_cursor_style(CursorStyle::PointingHand, hitbox);
320 } else {
321 cx.set_cursor_style(CursorStyle::IBeam, hitbox);
322 }
323
324 self.on_mouse_event(cx, {
325 let rendered_text = rendered_text.clone();
326 let hitbox = hitbox.clone();
327 move |markdown, event: &MouseDownEvent, phase, cx| {
328 if hitbox.is_hovered(cx) {
329 if phase.bubble() {
330 if let Some(link) = rendered_text.link_for_position(event.position) {
331 markdown.pressed_link = Some(link.clone());
332 } else {
333 let source_index =
334 match rendered_text.source_index_for_position(event.position) {
335 Ok(ix) | Err(ix) => ix,
336 };
337 let range = if event.click_count == 2 {
338 rendered_text.surrounding_word_range(source_index)
339 } else if event.click_count == 3 {
340 rendered_text.surrounding_line_range(source_index)
341 } else {
342 source_index..source_index
343 };
344 markdown.selection = Selection {
345 start: range.start,
346 end: range.end,
347 reversed: false,
348 pending: true,
349 };
350 cx.focus(&markdown.focus_handle);
351 cx.prevent_default()
352 }
353
354 cx.notify();
355 }
356 } else if phase.capture() {
357 markdown.selection = Selection::default();
358 markdown.pressed_link = None;
359 cx.notify();
360 }
361 }
362 });
363 self.on_mouse_event(cx, {
364 let rendered_text = rendered_text.clone();
365 let hitbox = hitbox.clone();
366 let was_hovering_link = is_hovering_link;
367 move |markdown, event: &MouseMoveEvent, phase, cx| {
368 if phase.capture() {
369 return;
370 }
371
372 if markdown.selection.pending {
373 let source_index = match rendered_text.source_index_for_position(event.position)
374 {
375 Ok(ix) | Err(ix) => ix,
376 };
377 markdown.selection.set_head(source_index);
378 markdown.autoscroll_request = Some(source_index);
379 cx.notify();
380 } else {
381 let is_hovering_link = hitbox.is_hovered(cx)
382 && rendered_text.link_for_position(event.position).is_some();
383 if is_hovering_link != was_hovering_link {
384 cx.notify();
385 }
386 }
387 }
388 });
389 self.on_mouse_event(cx, {
390 let rendered_text = rendered_text.clone();
391 move |markdown, event: &MouseUpEvent, phase, cx| {
392 if phase.bubble() {
393 if let Some(pressed_link) = markdown.pressed_link.take() {
394 if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
395 cx.open_url(&pressed_link.destination_url);
396 }
397 }
398 } else {
399 if markdown.selection.pending {
400 markdown.selection.pending = false;
401 #[cfg(target_os = "linux")]
402 {
403 let text = rendered_text
404 .text_for_range(markdown.selection.start..markdown.selection.end);
405 cx.write_to_primary(ClipboardItem::new(text))
406 }
407 cx.notify();
408 }
409 }
410 }
411 });
412 }
413
414 fn autoscroll(&mut self, rendered_text: &RenderedText, cx: &mut WindowContext) -> Option<()> {
415 let autoscroll_index = self
416 .markdown
417 .update(cx, |markdown, _| markdown.autoscroll_request.take())?;
418 let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;
419
420 let text_style = cx.text_style();
421 let font_id = cx.text_system().resolve_font(&text_style.font());
422 let font_size = text_style.font_size.to_pixels(cx.rem_size());
423 let em_width = cx
424 .text_system()
425 .typographic_bounds(font_id, font_size, 'm')
426 .unwrap()
427 .size
428 .width;
429 cx.request_autoscroll(Bounds::from_corners(
430 point(position.x - 3. * em_width, position.y - 3. * line_height),
431 point(position.x + 3. * em_width, position.y + 3. * line_height),
432 ));
433 Some(())
434 }
435
436 fn on_mouse_event<T: MouseEvent>(
437 &self,
438 cx: &mut WindowContext,
439 mut f: impl 'static + FnMut(&mut Markdown, &T, DispatchPhase, &mut ViewContext<Markdown>),
440 ) {
441 cx.on_mouse_event({
442 let markdown = self.markdown.downgrade();
443 move |event, phase, cx| {
444 markdown
445 .update(cx, |markdown, cx| f(markdown, event, phase, cx))
446 .log_err();
447 }
448 });
449 }
450}
451
452impl Element for MarkdownElement {
453 type RequestLayoutState = RenderedMarkdown;
454 type PrepaintState = Hitbox;
455
456 fn id(&self) -> Option<ElementId> {
457 None
458 }
459
460 fn request_layout(
461 &mut self,
462 _id: Option<&GlobalElementId>,
463 cx: &mut WindowContext,
464 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
465 let mut builder = MarkdownElementBuilder::new(cx.text_style(), self.style.syntax.clone());
466 let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone();
467 for (range, event) in parsed_markdown.events.iter() {
468 match event {
469 MarkdownEvent::Start(tag) => {
470 match tag {
471 MarkdownTag::Paragraph => {
472 builder.push_div(div().mb_2().line_height(rems(1.3)));
473 }
474 MarkdownTag::Heading { level, .. } => {
475 let mut heading = div().mb_2();
476 heading = match level {
477 pulldown_cmark::HeadingLevel::H1 => heading.text_3xl(),
478 pulldown_cmark::HeadingLevel::H2 => heading.text_2xl(),
479 pulldown_cmark::HeadingLevel::H3 => heading.text_xl(),
480 pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
481 _ => heading,
482 };
483 builder.push_div(heading);
484 }
485 MarkdownTag::BlockQuote => {
486 builder.push_text_style(self.style.block_quote.clone());
487 builder.push_div(
488 div()
489 .pl_4()
490 .mb_2()
491 .border_l_4()
492 .border_color(self.style.block_quote_border_color),
493 );
494 }
495 MarkdownTag::CodeBlock(kind) => {
496 let language = if let CodeBlockKind::Fenced(language) = kind {
497 self.load_language(language.as_ref(), cx)
498 } else {
499 None
500 };
501
502 builder.push_code_block(language);
503 builder.push_text_style(self.style.code_block.clone());
504 builder.push_div(div().rounded_lg().p_4().mb_2().w_full().when_some(
505 self.style.code_block.background_color,
506 |div, color| div.bg(color),
507 ));
508 }
509 MarkdownTag::HtmlBlock => builder.push_div(div()),
510 MarkdownTag::List(bullet_index) => {
511 builder.push_list(*bullet_index);
512 builder.push_div(div().pl_4());
513 }
514 MarkdownTag::Item => {
515 let bullet = if let Some(bullet_index) = builder.next_bullet_index() {
516 format!("{}.", bullet_index)
517 } else {
518 "•".to_string()
519 };
520 builder.push_div(
521 div()
522 .h_flex()
523 .mb_2()
524 .line_height(rems(1.3))
525 .items_start()
526 .gap_1()
527 .child(bullet),
528 );
529 // Without `w_0`, text doesn't wrap to the width of the container.
530 builder.push_div(div().flex_1().w_0());
531 }
532 MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
533 font_style: Some(FontStyle::Italic),
534 ..Default::default()
535 }),
536 MarkdownTag::Strong => builder.push_text_style(TextStyleRefinement {
537 font_weight: Some(FontWeight::BOLD),
538 ..Default::default()
539 }),
540 MarkdownTag::Strikethrough => {
541 builder.push_text_style(TextStyleRefinement {
542 strikethrough: Some(StrikethroughStyle {
543 thickness: px(1.),
544 color: None,
545 }),
546 ..Default::default()
547 })
548 }
549 MarkdownTag::Link { dest_url, .. } => {
550 if builder.code_block_stack.is_empty() {
551 builder.push_link(dest_url.clone(), range.clone());
552 builder.push_text_style(self.style.link.clone())
553 }
554 }
555 _ => log::error!("unsupported markdown tag {:?}", tag),
556 }
557 }
558 MarkdownEvent::End(tag) => match tag {
559 MarkdownTagEnd::Paragraph => {
560 builder.pop_div();
561 }
562 MarkdownTagEnd::Heading(_) => builder.pop_div(),
563 MarkdownTagEnd::BlockQuote => {
564 builder.pop_text_style();
565 builder.pop_div()
566 }
567 MarkdownTagEnd::CodeBlock => {
568 builder.trim_trailing_newline();
569 builder.pop_div();
570 builder.pop_text_style();
571 builder.pop_code_block();
572 }
573 MarkdownTagEnd::HtmlBlock => builder.pop_div(),
574 MarkdownTagEnd::List(_) => {
575 builder.pop_list();
576 builder.pop_div();
577 }
578 MarkdownTagEnd::Item => {
579 builder.pop_div();
580 builder.pop_div();
581 }
582 MarkdownTagEnd::Emphasis => builder.pop_text_style(),
583 MarkdownTagEnd::Strong => builder.pop_text_style(),
584 MarkdownTagEnd::Strikethrough => builder.pop_text_style(),
585 MarkdownTagEnd::Link => {
586 if builder.code_block_stack.is_empty() {
587 builder.pop_text_style()
588 }
589 }
590 _ => log::error!("unsupported markdown tag end: {:?}", tag),
591 },
592 MarkdownEvent::Text => {
593 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
594 }
595 MarkdownEvent::Code => {
596 builder.push_text_style(self.style.inline_code.clone());
597 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
598 builder.pop_text_style();
599 }
600 MarkdownEvent::Html => {
601 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
602 }
603 MarkdownEvent::InlineHtml => {
604 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
605 }
606 MarkdownEvent::Rule => {
607 builder.push_div(
608 div()
609 .border_b_1()
610 .my_2()
611 .border_color(self.style.rule_color),
612 );
613 builder.pop_div()
614 }
615 MarkdownEvent::SoftBreak => builder.push_text("\n", range.start),
616 MarkdownEvent::HardBreak => builder.push_text("\n", range.start),
617 _ => log::error!("unsupported markdown event {:?}", event),
618 }
619 }
620
621 let mut rendered_markdown = builder.build();
622 let child_layout_id = rendered_markdown.element.request_layout(cx);
623 let layout_id = cx.request_layout(Style::default(), [child_layout_id]);
624 (layout_id, rendered_markdown)
625 }
626
627 fn prepaint(
628 &mut self,
629 _id: Option<&GlobalElementId>,
630 bounds: Bounds<Pixels>,
631 rendered_markdown: &mut Self::RequestLayoutState,
632 cx: &mut WindowContext,
633 ) -> Self::PrepaintState {
634 let hitbox = cx.insert_hitbox(bounds, false);
635 rendered_markdown.element.prepaint(cx);
636 self.autoscroll(&rendered_markdown.text, cx);
637 hitbox
638 }
639
640 fn paint(
641 &mut self,
642 _id: Option<&GlobalElementId>,
643 bounds: Bounds<Pixels>,
644 rendered_markdown: &mut Self::RequestLayoutState,
645 hitbox: &mut Self::PrepaintState,
646 cx: &mut WindowContext,
647 ) {
648 let focus_handle = self.markdown.read(cx).focus_handle.clone();
649 cx.set_focus_handle(&focus_handle);
650
651 let mut context = KeyContext::default();
652 context.add("Markdown");
653 cx.set_key_context(context);
654 let view = self.markdown.clone();
655 cx.on_action(std::any::TypeId::of::<crate::Copy>(), {
656 let text = rendered_markdown.text.clone();
657 move |_, phase, cx| {
658 let text = text.clone();
659 if phase == DispatchPhase::Bubble {
660 view.update(cx, move |this, cx| this.copy(&text, cx))
661 }
662 }
663 });
664
665 self.paint_mouse_listeners(hitbox, &rendered_markdown.text, cx);
666 rendered_markdown.element.paint(cx);
667 self.paint_selection(bounds, &rendered_markdown.text, cx);
668 }
669}
670
671impl IntoElement for MarkdownElement {
672 type Element = Self;
673
674 fn into_element(self) -> Self::Element {
675 self
676 }
677}
678
679struct MarkdownElementBuilder {
680 div_stack: Vec<Div>,
681 rendered_lines: Vec<RenderedLine>,
682 pending_line: PendingLine,
683 rendered_links: Vec<RenderedLink>,
684 current_source_index: usize,
685 base_text_style: TextStyle,
686 text_style_stack: Vec<TextStyleRefinement>,
687 code_block_stack: Vec<Option<Arc<Language>>>,
688 list_stack: Vec<ListStackEntry>,
689 syntax_theme: Arc<SyntaxTheme>,
690}
691
692#[derive(Default)]
693struct PendingLine {
694 text: String,
695 runs: Vec<TextRun>,
696 source_mappings: Vec<SourceMapping>,
697}
698
699struct ListStackEntry {
700 bullet_index: Option<u64>,
701}
702
703impl MarkdownElementBuilder {
704 fn new(base_text_style: TextStyle, syntax_theme: Arc<SyntaxTheme>) -> Self {
705 Self {
706 div_stack: vec![div().debug_selector(|| "inner".into())],
707 rendered_lines: Vec::new(),
708 pending_line: PendingLine::default(),
709 rendered_links: Vec::new(),
710 current_source_index: 0,
711 base_text_style,
712 text_style_stack: Vec::new(),
713 code_block_stack: Vec::new(),
714 list_stack: Vec::new(),
715 syntax_theme,
716 }
717 }
718
719 fn push_text_style(&mut self, style: TextStyleRefinement) {
720 self.text_style_stack.push(style);
721 }
722
723 fn text_style(&self) -> TextStyle {
724 let mut style = self.base_text_style.clone();
725 for refinement in &self.text_style_stack {
726 style.refine(refinement);
727 }
728 style
729 }
730
731 fn pop_text_style(&mut self) {
732 self.text_style_stack.pop();
733 }
734
735 fn push_div(&mut self, div: Div) {
736 self.flush_text();
737 self.div_stack.push(div);
738 }
739
740 fn pop_div(&mut self) {
741 self.flush_text();
742 let div = self.div_stack.pop().unwrap().into_any();
743 self.div_stack.last_mut().unwrap().extend(iter::once(div));
744 }
745
746 fn push_list(&mut self, bullet_index: Option<u64>) {
747 self.list_stack.push(ListStackEntry { bullet_index });
748 }
749
750 fn next_bullet_index(&mut self) -> Option<u64> {
751 self.list_stack.last_mut().and_then(|entry| {
752 let item_index = entry.bullet_index.as_mut()?;
753 *item_index += 1;
754 Some(*item_index - 1)
755 })
756 }
757
758 fn pop_list(&mut self) {
759 self.list_stack.pop();
760 }
761
762 fn push_code_block(&mut self, language: Option<Arc<Language>>) {
763 self.code_block_stack.push(language);
764 }
765
766 fn pop_code_block(&mut self) {
767 self.code_block_stack.pop();
768 }
769
770 fn push_link(&mut self, destination_url: SharedString, source_range: Range<usize>) {
771 self.rendered_links.push(RenderedLink {
772 source_range,
773 destination_url,
774 });
775 }
776
777 fn push_text(&mut self, text: &str, source_index: usize) {
778 self.pending_line.source_mappings.push(SourceMapping {
779 rendered_index: self.pending_line.text.len(),
780 source_index,
781 });
782 self.pending_line.text.push_str(text);
783 self.current_source_index = source_index + text.len();
784
785 if let Some(Some(language)) = self.code_block_stack.last() {
786 let mut offset = 0;
787 for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
788 if range.start > offset {
789 self.pending_line
790 .runs
791 .push(self.text_style().to_run(range.start - offset));
792 }
793
794 let mut run_style = self.text_style();
795 if let Some(highlight) = highlight_id.style(&self.syntax_theme) {
796 run_style = run_style.highlight(highlight);
797 }
798 self.pending_line.runs.push(run_style.to_run(range.len()));
799 offset = range.end;
800 }
801
802 if offset < text.len() {
803 self.pending_line
804 .runs
805 .push(self.text_style().to_run(text.len() - offset));
806 }
807 } else {
808 self.pending_line
809 .runs
810 .push(self.text_style().to_run(text.len()));
811 }
812 }
813
814 fn trim_trailing_newline(&mut self) {
815 if self.pending_line.text.ends_with('\n') {
816 self.pending_line
817 .text
818 .truncate(self.pending_line.text.len() - 1);
819 self.pending_line.runs.last_mut().unwrap().len -= 1;
820 self.current_source_index -= 1;
821 }
822 }
823
824 fn flush_text(&mut self) {
825 let line = mem::take(&mut self.pending_line);
826 if line.text.is_empty() {
827 return;
828 }
829
830 let text = StyledText::new(line.text).with_runs(line.runs);
831 self.rendered_lines.push(RenderedLine {
832 layout: text.layout().clone(),
833 source_mappings: line.source_mappings,
834 source_end: self.current_source_index,
835 });
836 self.div_stack.last_mut().unwrap().extend([text.into_any()]);
837 }
838
839 fn build(mut self) -> RenderedMarkdown {
840 debug_assert_eq!(self.div_stack.len(), 1);
841 self.flush_text();
842 RenderedMarkdown {
843 element: self.div_stack.pop().unwrap().into_any(),
844 text: RenderedText {
845 lines: self.rendered_lines.into(),
846 links: self.rendered_links.into(),
847 },
848 }
849 }
850}
851
852struct RenderedLine {
853 layout: TextLayout,
854 source_mappings: Vec<SourceMapping>,
855 source_end: usize,
856}
857
858impl RenderedLine {
859 fn rendered_index_for_source_index(&self, source_index: usize) -> usize {
860 let mapping = match self
861 .source_mappings
862 .binary_search_by_key(&source_index, |probe| probe.source_index)
863 {
864 Ok(ix) => &self.source_mappings[ix],
865 Err(ix) => &self.source_mappings[ix - 1],
866 };
867 mapping.rendered_index + (source_index - mapping.source_index)
868 }
869
870 fn source_index_for_rendered_index(&self, rendered_index: usize) -> usize {
871 let mapping = match self
872 .source_mappings
873 .binary_search_by_key(&rendered_index, |probe| probe.rendered_index)
874 {
875 Ok(ix) => &self.source_mappings[ix],
876 Err(ix) => &self.source_mappings[ix - 1],
877 };
878 mapping.source_index + (rendered_index - mapping.rendered_index)
879 }
880
881 fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
882 let line_rendered_index;
883 let out_of_bounds;
884 match self.layout.index_for_position(position) {
885 Ok(ix) => {
886 line_rendered_index = ix;
887 out_of_bounds = false;
888 }
889 Err(ix) => {
890 line_rendered_index = ix;
891 out_of_bounds = true;
892 }
893 };
894 let source_index = self.source_index_for_rendered_index(line_rendered_index);
895 if out_of_bounds {
896 Err(source_index)
897 } else {
898 Ok(source_index)
899 }
900 }
901}
902
903#[derive(Copy, Clone, Debug, Default)]
904struct SourceMapping {
905 rendered_index: usize,
906 source_index: usize,
907}
908
909pub struct RenderedMarkdown {
910 element: AnyElement,
911 text: RenderedText,
912}
913
914#[derive(Clone)]
915struct RenderedText {
916 lines: Rc<[RenderedLine]>,
917 links: Rc<[RenderedLink]>,
918}
919
920#[derive(Clone, Eq, PartialEq)]
921struct RenderedLink {
922 source_range: Range<usize>,
923 destination_url: SharedString,
924}
925
926impl RenderedText {
927 fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
928 let mut lines = self.lines.iter().peekable();
929
930 while let Some(line) = lines.next() {
931 let line_bounds = line.layout.bounds();
932 if position.y > line_bounds.bottom() {
933 if let Some(next_line) = lines.peek() {
934 if position.y < next_line.layout.bounds().top() {
935 return Err(line.source_end);
936 }
937 }
938
939 continue;
940 }
941
942 return line.source_index_for_position(position);
943 }
944
945 Err(self.lines.last().map_or(0, |line| line.source_end))
946 }
947
948 fn position_for_source_index(&self, source_index: usize) -> Option<(Point<Pixels>, Pixels)> {
949 for line in self.lines.iter() {
950 let line_source_start = line.source_mappings.first().unwrap().source_index;
951 if source_index < line_source_start {
952 break;
953 } else if source_index > line.source_end {
954 continue;
955 } else {
956 let line_height = line.layout.line_height();
957 let rendered_index_within_line = line.rendered_index_for_source_index(source_index);
958 let position = line.layout.position_for_index(rendered_index_within_line)?;
959 return Some((position, line_height));
960 }
961 }
962 None
963 }
964
965 fn surrounding_word_range(&self, source_index: usize) -> Range<usize> {
966 for line in self.lines.iter() {
967 if source_index > line.source_end {
968 continue;
969 }
970
971 let line_rendered_start = line.source_mappings.first().unwrap().rendered_index;
972 let rendered_index_in_line =
973 line.rendered_index_for_source_index(source_index) - line_rendered_start;
974 let text = line.layout.text();
975 let previous_space = if let Some(idx) = text[0..rendered_index_in_line].rfind(' ') {
976 idx + ' '.len_utf8()
977 } else {
978 0
979 };
980 let next_space = if let Some(idx) = text[rendered_index_in_line..].find(' ') {
981 rendered_index_in_line + idx
982 } else {
983 text.len()
984 };
985
986 return line.source_index_for_rendered_index(line_rendered_start + previous_space)
987 ..line.source_index_for_rendered_index(line_rendered_start + next_space);
988 }
989
990 source_index..source_index
991 }
992
993 fn surrounding_line_range(&self, source_index: usize) -> Range<usize> {
994 for line in self.lines.iter() {
995 if source_index > line.source_end {
996 continue;
997 }
998 let line_source_start = line.source_mappings.first().unwrap().source_index;
999 return line_source_start..line.source_end;
1000 }
1001
1002 source_index..source_index
1003 }
1004
1005 fn text_for_range(&self, range: Range<usize>) -> String {
1006 let mut ret = vec![];
1007
1008 for line in self.lines.iter() {
1009 if range.start > line.source_end {
1010 continue;
1011 }
1012 let line_source_start = line.source_mappings.first().unwrap().source_index;
1013 if range.end < line_source_start {
1014 break;
1015 }
1016
1017 let text = line.layout.text();
1018
1019 let start = if range.start < line_source_start {
1020 0
1021 } else {
1022 line.rendered_index_for_source_index(range.start)
1023 };
1024 let end = if range.end > line.source_end {
1025 line.rendered_index_for_source_index(line.source_end)
1026 } else {
1027 line.rendered_index_for_source_index(range.end)
1028 }
1029 .min(text.len());
1030
1031 ret.push(text[start..end].to_string());
1032 }
1033 ret.join("\n")
1034 }
1035
1036 fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {
1037 let source_index = self.source_index_for_position(position).ok()?;
1038 self.links
1039 .iter()
1040 .find(|link| link.source_range.contains(&source_index))
1041 }
1042}