1use editor::{Editor, EditorEvent};
2use gpui::{
3 canvas, AnyElement, AppContext, AvailableSpace, EventEmitter, FocusHandle, FocusableView,
4 InteractiveElement, IntoElement, ParentElement, Render, Styled, View, ViewContext,
5};
6use language::LanguageRegistry;
7use std::sync::Arc;
8use ui::prelude::*;
9use workspace::item::Item;
10use workspace::Workspace;
11
12use crate::{markdown_renderer::render_markdown, OpenPreview};
13
14pub struct MarkdownPreviewView {
15 focus_handle: FocusHandle,
16 languages: Arc<LanguageRegistry>,
17 contents: String,
18}
19
20impl MarkdownPreviewView {
21 pub fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
22 let languages = workspace.app_state().languages.clone();
23
24 workspace.register_action(move |workspace, _: &OpenPreview, cx| {
25 if workspace.has_active_modal(cx) {
26 cx.propagate();
27 return;
28 }
29 let languages = languages.clone();
30 if let Some(editor) = workspace.active_item_as::<Editor>(cx) {
31 let view: View<MarkdownPreviewView> =
32 cx.new_view(|cx| MarkdownPreviewView::new(editor, languages, cx));
33 workspace.split_item(workspace::SplitDirection::Right, Box::new(view.clone()), cx);
34 cx.notify();
35 }
36 });
37 }
38
39 pub fn new(
40 active_editor: View<Editor>,
41 languages: Arc<LanguageRegistry>,
42 cx: &mut ViewContext<Self>,
43 ) -> Self {
44 let focus_handle = cx.focus_handle();
45
46 cx.subscribe(&active_editor, |this, editor, event: &EditorEvent, cx| {
47 if *event == EditorEvent::Edited {
48 let editor = editor.read(cx);
49 let contents = editor.buffer().read(cx).snapshot(cx).text();
50 this.contents = contents;
51 cx.notify();
52 }
53 })
54 .detach();
55
56 let editor = active_editor.read(cx);
57 let contents = editor.buffer().read(cx).snapshot(cx).text();
58
59 Self {
60 focus_handle,
61 languages,
62 contents,
63 }
64 }
65}
66
67impl FocusableView for MarkdownPreviewView {
68 fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
69 self.focus_handle.clone()
70 }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub enum PreviewEvent {}
75
76impl EventEmitter<PreviewEvent> for MarkdownPreviewView {}
77
78impl Item for MarkdownPreviewView {
79 type Event = PreviewEvent;
80
81 fn tab_content(
82 &self,
83 _detail: Option<usize>,
84 selected: bool,
85 _cx: &WindowContext,
86 ) -> AnyElement {
87 h_flex()
88 .gap_2()
89 .child(Icon::new(IconName::FileDoc).color(if selected {
90 Color::Default
91 } else {
92 Color::Muted
93 }))
94 .child(Label::new("Markdown preview").color(if selected {
95 Color::Default
96 } else {
97 Color::Muted
98 }))
99 .into_any()
100 }
101
102 fn telemetry_event_text(&self) -> Option<&'static str> {
103 Some("markdown preview")
104 }
105
106 fn to_item_events(_event: &Self::Event, _f: impl FnMut(workspace::item::ItemEvent)) {}
107}
108
109impl Render for MarkdownPreviewView {
110 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
111 let rendered_markdown = v_flex()
112 .items_start()
113 .justify_start()
114 .key_context("MarkdownPreview")
115 .track_focus(&self.focus_handle)
116 .id("MarkdownPreview")
117 .overflow_y_scroll()
118 .overflow_x_hidden()
119 .size_full()
120 .bg(cx.theme().colors().editor_background)
121 .p_4()
122 .children(render_markdown(&self.contents, &self.languages, cx));
123
124 div().flex_1().child(
125 // FIXME: This shouldn't be necessary
126 // but the overflow_scroll above doesn't seem to work without it
127 canvas(move |bounds, cx| {
128 rendered_markdown.into_any().draw(
129 bounds.origin,
130 bounds.size.map(AvailableSpace::Definite),
131 cx,
132 )
133 })
134 .size_full(),
135 )
136 }
137}