diff --git a/crates/story/examples/editor.rs b/crates/story/examples/editor.rs index 74fcb0fb..392b5119 100644 --- a/crates/story/examples/editor.rs +++ b/crates/story/examples/editor.rs @@ -24,7 +24,7 @@ use lsp_types::{ CodeAction, CodeActionKind, CompletionContext, CompletionItem, CompletionResponse, CompletionTextEdit, InsertReplaceEdit, TextEdit, WorkspaceEdit, }; -use story::Assets; +use story::{Assets, Open}; fn init() { LanguageRegistry::singleton().register( @@ -865,6 +865,45 @@ impl Example { lsp_store.update_diagnostics(diagnostics.clone()); }); } + + 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 source file".into()), + }); + + let view = cx.entity(); + let editor = self.editor.clone(); + cx.spawn_in(window, async move |_, window| { + let path = path.await.ok()?.ok()??.iter().next()?.clone(); + + let language = path + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or_default(); + let language = Language::from_str(&language); + let lang = Lang::BuiltIn(language.clone()); + let content = std::fs::read_to_string(&path).ok()?; + + window + .update(|window, cx| { + _ = editor.update(cx, |this, cx| { + this.set_highlighter(language.name(), cx); + this.set_value(content, window, cx); + }); + view.update(cx, |this, cx| { + this.language = lang; + cx.notify(); + }) + }) + .ok(); + + Some(()) + }) + .detach(); + } } impl Render for Example { @@ -883,78 +922,88 @@ impl Render for Example { }); } - v_flex().size_full().child( - v_flex() - .id("source") - .w_full() - .flex_1() - .child( - TextInput::new(&self.editor) - .bordered(false) - .p_0() - .h_full() - .font_family("Monaco") - .text_size(px(12.)) - .focus_bordered(false), - ) - .child( - h_flex() - .justify_between() - .text_sm() - .bg(cx.theme().background) - .py_1p5() - .px_4() - .border_t_1() - .border_color(cx.theme().border) - .text_color(cx.theme().muted_foreground) - .child( - h_flex() - .gap_3() - .child( - Dropdown::new(&self.language_state) - .menu_width(px(160.)) - .xsmall(), - ) - .child( - Button::new("line-number") - .ghost() - .when(self.line_number, |this| this.icon(IconName::Check)) - .label("Line Number") - .xsmall() - .on_click(cx.listener(|this, _, window, cx| { - this.line_number = !this.line_number; - this.editor.update(cx, |state, cx| { - state.set_line_number(this.line_number, window, cx); - }); - cx.notify(); - })), - ) - .child({ - Button::new("soft-wrap") - .ghost() - .xsmall() - .label("Soft Wrap") - .selected(self.soft_wrap) - .on_click(cx.listener(Self::toggle_soft_wrap)) - }), - ) - .child({ - let position = self.editor.read(cx).cursor_position(); - let cursor = self.editor.read(cx).cursor(); + v_flex() + .id("app") + .size_full() + .on_action(cx.listener(Self::on_action_open)) + .child( + v_flex() + .id("source") + .w_full() + .flex_1() + .child( + TextInput::new(&self.editor) + .bordered(false) + .p_0() + .h_full() + .font_family("Monaco") + .text_size(px(12.)) + .focus_bordered(false), + ) + .child( + h_flex() + .justify_between() + .text_sm() + .bg(cx.theme().background) + .py_1p5() + .px_4() + .border_t_1() + .border_color(cx.theme().border) + .text_color(cx.theme().muted_foreground) + .child( + h_flex() + .gap_3() + .child( + Dropdown::new(&self.language_state) + .menu_width(px(160.)) + .xsmall(), + ) + .child( + Button::new("line-number") + .ghost() + .when(self.line_number, |this| { + this.icon(IconName::Check) + }) + .label("Line Number") + .xsmall() + .on_click(cx.listener(|this, _, window, cx| { + this.line_number = !this.line_number; + this.editor.update(cx, |state, cx| { + state.set_line_number( + this.line_number, + window, + cx, + ); + }); + cx.notify(); + })), + ) + .child({ + Button::new("soft-wrap") + .ghost() + .xsmall() + .label("Soft Wrap") + .selected(self.soft_wrap) + .on_click(cx.listener(Self::toggle_soft_wrap)) + }), + ) + .child({ + let position = self.editor.read(cx).cursor_position(); + let cursor = self.editor.read(cx).cursor(); - Button::new("line-column") - .ghost() - .xsmall() - .label(format!( - "{}:{} ({} byte)", - position.line + 1, - position.character + 1, - cursor - )) - .on_click(cx.listener(Self::go_to_line)) - }), - ), - ) + Button::new("line-column") + .ghost() + .xsmall() + .label(format!( + "{}:{} ({} byte)", + position.line + 1, + position.character + 1, + cursor + )) + .on_click(cx.listener(Self::go_to_line)) + }), + ), + ) } } diff --git a/crates/story/examples/markdown.rs b/crates/story/examples/markdown.rs index 85d44c86..8b2cb3eb 100644 --- a/crates/story/examples/markdown.rs +++ b/crates/story/examples/markdown.rs @@ -2,10 +2,10 @@ use gpui::*; use gpui_component::{ highlighter::Language, input::{InputEvent, InputState, TabSize, TextInput}, - resizable::{h_resizable, resizable_panel, ResizableState}, + resizable::{ResizableState, h_resizable, resizable_panel}, text::TextView, }; -use story::Assets; +use story::{Assets, Open}; pub struct Example { input_state: Entity, @@ -40,6 +40,33 @@ impl Example { } } + 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)) } @@ -47,40 +74,46 @@ impl Example { impl Render for Example { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - h_resizable("container", self.resizable_state.clone()) + div() + .id("editor") + .size_full() + .on_action(cx.listener(Self::on_action_open)) .child( - resizable_panel().child( - div() - .id("source") - .size_full() - .font_family("Monaco") - .text_size(px(12.)) - .child( - TextInput::new(&self.input_state) - .h_full() - .p_0() - .border_0() - .focus_bordered(false), + h_resizable("container", self.resizable_state.clone()) + .child( + resizable_panel().child( + div() + .id("source") + .size_full() + .font_family("Monaco") + .text_size(px(12.)) + .child( + TextInput::new(&self.input_state) + .h_full() + .p_0() + .border_0() + .focus_bordered(false), + ), ), - ), - ) - .child( - resizable_panel().child( - div() - .id("preview") - .size_full() - .p_5() - .overflow_y_scroll() - .child( - TextView::markdown( - "preview", - self.input_state.read(cx).value().clone(), - window, - cx, - ) - .selectable(), + ) + .child( + resizable_panel().child( + div() + .id("preview") + .size_full() + .p_5() + .overflow_y_scroll() + .child( + TextView::markdown( + "preview", + self.input_state.read(cx).value().clone(), + window, + cx, + ) + .selectable(), + ), ), - ), + ), ) } } diff --git a/crates/story/src/app_menus.rs b/crates/story/src/app_menus.rs index 27f9d987..2521df92 100644 --- a/crates/story/src/app_menus.rs +++ b/crates/story/src/app_menus.rs @@ -1,13 +1,11 @@ -use gpui::{App, Menu, MenuItem, SharedString, actions}; +use gpui::{App, Menu, MenuItem, SharedString}; use gpui_component::{ThemeMode, ThemeRegistry}; use crate::{ - CloseWindow, Open, Quit, SelectLocale, ToggleSearch, + About, CloseWindow, Open, Quit, SelectLocale, ToggleSearch, themes::{SwitchTheme, SwitchThemeMode}, }; -actions!(story, [About]); - pub fn init(title: impl Into, cx: &mut App) { cx.set_menus(vec![ Menu { @@ -15,6 +13,8 @@ pub fn init(title: impl Into, cx: &mut App) { items: vec![ MenuItem::action("About", About), MenuItem::Separator, + MenuItem::action("Open...", Open), + MenuItem::Separator, MenuItem::Submenu(Menu { name: "Appearance".into(), items: vec![ diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index ab6a491f..886c11ba 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -140,8 +140,9 @@ pub struct SelectRadius(usize); actions!( story, [ - Quit, + About, Open, + Quit, CloseWindow, ToggleSearch, TestAction, @@ -307,7 +308,14 @@ pub fn init(cx: &mut App) { cx.bind_keys([ KeyBinding::new("/", ToggleSearch, None), + #[cfg(target_os = "macos")] + KeyBinding::new("cmd-o", Open, None), + #[cfg(not(target_os = "macos"))] + KeyBinding::new("ctrl-o", Open, None), + #[cfg(target_os = "macos")] KeyBinding::new("cmd-q", Quit, None), + #[cfg(not(target_os = "macos"))] + KeyBinding::new("alt-f4", Quit, None), ]); cx.on_action(|_: &Quit, cx: &mut App| {