use gpui::{prelude::FluentBuilder as _, *}; use gpui_component::{ ActiveTheme as _, IconName, Sizable as _, button::{Button, ButtonVariants as _}, clipboard::Clipboard, h_flex, highlighter::Language, input::{Input, InputEvent, InputState, TabSize}, resizable::{h_resizable, resizable_panel}, text::TextView, }; use gpui_component_assets::Assets; use gpui_component_story::Open; pub struct Example { input_state: Entity, _subscriptions: Vec, } const EXAMPLE: &str = include_str!("./fixtures/test.md"); impl Example { pub fn new(window: &mut Window, cx: &mut Context) -> Self { let input_state = cx.new(|cx| { InputState::new(window, cx) .code_editor(Language::Markdown) .line_number(true) .tab_size(TabSize { tab_size: 2, ..Default::default() }) .searchable(true) .placeholder("Enter your Markdown here...") .default_value(EXAMPLE) }); let _subscriptions = vec![cx.subscribe(&input_state, |_, _, _: &InputEvent, _| {})]; Self { input_state, _subscriptions, } } fn on_action_open(&mut self, _: &Open, window: &mut Window, cx: &mut Context) { let path = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: false, prompt: Some("Select a Markdown file".into()), }); let input_state = self.input_state.clone(); cx.spawn_in(window, async move |_, window| { let path = path.await.ok()?.ok()??.iter().next()?.clone(); let content = std::fs::read_to_string(&path).ok()?; window .update(|window, cx| { _ = input_state.update(cx, |this, cx| { this.set_value(content, window, cx); }); }) .ok(); Some(()) }) .detach(); } fn view(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Self::new(window, cx)) } } impl Render for Example { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { div() .id("editor") .size_full() .on_action(cx.listener(Self::on_action_open)) .child( h_resizable("container") .child( resizable_panel().child( div() .id("source") .size_full() .font_family(cx.theme().mono_font_family.clone()) .text_size(cx.theme().mono_font_size) .child( Input::new(&self.input_state) .h_full() .p_0() .border_0() .focus_bordered(false), ), ), ) .child( resizable_panel().child( TextView::markdown( "preview", self.input_state.read(cx).value().clone(), window, cx, ) .code_block_actions(|code_block, _window, _cx| { let code = code_block.code(); let lang = code_block.lang(); h_flex() .gap_1() .child(Clipboard::new("copy").value(code.clone())) .when_some(lang, |this, lang| { // Only show run terminal button for certain languages if lang.as_ref() == "rust" || lang.as_ref() == "python" { this.child( Button::new("run-terminal") .icon(IconName::SquareTerminal) .ghost() .xsmall() .on_click(move |_, _, _cx| { println!("Running {} code: {}", lang, code); }), ) } else { this } }) }) .flex_none() .p_5() .scrollable(true) .selectable(true), ), ), ) } } fn main() { let app = Application::new().with_assets(Assets); app.run(move |cx| { gpui_component_story::init(cx); cx.activate(true); gpui_component_story::create_new_window("Markdown Editor", Example::view, cx); }); }