markdown_preview_view.rs

  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}