diff --git a/Cargo.lock b/Cargo.lock index bcb4885f66..ba8f7f1b88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,11 +2475,13 @@ dependencies = [ "channel", "client", "collections", + "command_palette", "db", "dev_server_projects", "editor", "emojis", "extensions_ui", + "feedback", "futures 0.3.28", "fuzzy", "gpui", diff --git a/assets/icons/rotate_ccw.svg b/assets/icons/rotate_ccw.svg new file mode 100644 index 0000000000..4eff13b94b --- /dev/null +++ b/assets/icons/rotate_ccw.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/text-cursor.svg b/assets/icons/text-cursor.svg new file mode 100644 index 0000000000..2e7b95b203 --- /dev/null +++ b/assets/icons/text-cursor.svg @@ -0,0 +1 @@ + diff --git a/assets/settings/default.json b/assets/settings/default.json index 709b418b98..6576281554 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -183,7 +183,9 @@ // Whether to show breadcrumbs. "breadcrumbs": true, // Whether to show quick action buttons. - "quick_actions": true + "quick_actions": true, + // Whether to show the Selections menu in the editor toolbar + "selections_menu": true }, // Scrollbar related settings "scrollbar": { diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs index 0a4a408891..c3047c243d 100644 --- a/crates/assistant/src/prompt_library.rs +++ b/crates/assistant/src/prompt_library.rs @@ -832,13 +832,8 @@ impl PromptLibrary { impl Render for PromptLibrary { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let (ui_font, ui_font_size) = { - let theme_settings = ThemeSettings::get_global(cx); - (theme_settings.ui_font.clone(), theme_settings.ui_font_size) - }; - + let ui_font = theme::setup_ui_font(cx); let theme = cx.theme().clone(); - cx.set_rem_size(ui_font_size); h_flex() .id("prompt-manager") diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index d70b1cb227..f370c7dd44 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -86,10 +86,16 @@ impl Render for Breadcrumbs { .style(ButtonStyle::Subtle) .on_click(move |_, cx| { if let Some(editor) = editor.upgrade() { - outline::toggle(editor, &outline::Toggle, cx) + outline::toggle(editor, &editor::actions::ToggleOutline, cx) } }) - .tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)), + .tooltip(|cx| { + Tooltip::for_action( + "Show symbol outline", + &editor::actions::ToggleOutline, + cx, + ) + }), ), None => element // Match the height of the `ButtonLike` in the other arm. diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 01da2ac15b..c1715c68e0 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -35,10 +35,12 @@ call.workspace = true channel.workspace = true client.workspace = true collections.workspace = true +command_palette.workspace = true db.workspace = true editor.workspace = true emojis.workspace = true extensions_ui.workspace = true +feedback.workspace = true futures.workspace = true fuzzy.workspace = true gpui.workspace = true diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index e98ce855d7..a234a2c28d 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -10,8 +10,9 @@ use gpui::{ use project::{Project, RepositoryEntry}; use recent_projects::RecentProjects; use rpc::proto::{self, DevServerStatus}; +use settings::Settings; use std::sync::Arc; -use theme::ActiveTheme; +use theme::{ActiveTheme, ThemeSettings}; use ui::{ h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip, @@ -73,6 +74,7 @@ impl Render for CollabTitlebarItem { .child( h_flex() .gap_1() + .children(self.render_application_menu(cx)) .children(self.render_project_host(cx)) .child(self.render_project_name(cx)) .children(self.render_project_branch(cx)) @@ -386,8 +388,173 @@ impl CollabTitlebarItem { } } - // resolve if you are in a room -> render_project_owner - // render_project_owner -> resolve if you are in a room -> Option + pub fn render_application_menu(&self, cx: &mut ViewContext) -> Option { + cfg!(not(target_os = "macos")).then(|| { + let ui_font_size = ThemeSettings::get_global(cx).ui_font_size; + let font = cx.text_style().font(); + let font_id = cx.text_system().resolve_font(&font); + let width = cx + .text_system() + .typographic_bounds(font_id, ui_font_size, 'm') + .unwrap() + .size + .width + * 3.0; + + PopoverMenu::new("application-menu") + .menu(move |cx| { + let width = width; + ContextMenu::build(cx, move |menu, _cx| { + let width = width; + menu.header("Workspace") + .action("Open Command Palette", Box::new(command_palette::Toggle)) + .custom_row(move |cx| { + div() + .w_full() + .flex() + .flex_row() + .justify_between() + .cursor(gpui::CursorStyle::Arrow) + .child(Label::new("Buffer Font Size")) + .child( + div() + .flex() + .flex_row() + .child(div().w(px(16.0))) + .child( + IconButton::new( + "reset-buffer-zoom", + IconName::RotateCcw, + ) + .on_click(|_, cx| { + cx.dispatch_action(Box::new( + zed_actions::ResetBufferFontSize, + )) + }), + ) + .child( + IconButton::new("--buffer-zoom", IconName::Dash) + .on_click(|_, cx| { + cx.dispatch_action(Box::new( + zed_actions::DecreaseBufferFontSize, + )) + }), + ) + .child( + div() + .w(width) + .flex() + .flex_row() + .justify_around() + .child(Label::new( + theme::get_buffer_font_size(cx).to_string(), + )), + ) + .child( + IconButton::new("+-buffer-zoom", IconName::Plus) + .on_click(|_, cx| { + cx.dispatch_action(Box::new( + zed_actions::IncreaseBufferFontSize, + )) + }), + ), + ) + .into_any_element() + }) + .custom_row(move |cx| { + div() + .w_full() + .flex() + .flex_row() + .justify_between() + .cursor(gpui::CursorStyle::Arrow) + .child(Label::new("UI Font Size")) + .child( + div() + .flex() + .flex_row() + .child( + IconButton::new( + "reset-ui-zoom", + IconName::RotateCcw, + ) + .on_click(|_, cx| { + cx.dispatch_action(Box::new( + zed_actions::ResetUiFontSize, + )) + }), + ) + .child( + IconButton::new("--ui-zoom", IconName::Dash) + .on_click(|_, cx| { + cx.dispatch_action(Box::new( + zed_actions::DecreaseUiFontSize, + )) + }), + ) + .child( + div() + .w(width) + .flex() + .flex_row() + .justify_around() + .child(Label::new( + theme::get_ui_font_size(cx).to_string(), + )), + ) + .child( + IconButton::new("+-ui-zoom", IconName::Plus) + .on_click(|_, cx| { + cx.dispatch_action(Box::new( + zed_actions::IncreaseUiFontSize, + )) + }), + ), + ) + .into_any_element() + }) + .header("Project") + .action( + "Add Folder to Project...", + Box::new(workspace::AddFolderToProject), + ) + .action("Open a new Project...", Box::new(workspace::Open)) + .action( + "Open Recent Projects...", + Box::new(recent_projects::OpenRecent { + create_new_window: false, + }), + ) + .header("Help") + .action("About Zed", Box::new(zed_actions::About)) + .action("Welcome", Box::new(workspace::Welcome)) + .link( + "Documentation", + Box::new(zed_actions::OpenBrowser { + url: "https://zed.dev/docs".into(), + }), + ) + .action("Give Feedback", Box::new(feedback::GiveFeedback)) + .action("Check for Updates", Box::new(auto_update::Check)) + .action("View Telemetry", Box::new(zed_actions::OpenTelemetryLog)) + .action( + "View Dependency Licenses", + Box::new(zed_actions::OpenLicenses), + ) + .separator() + .action("Quit", Box::new(zed_actions::Quit)) + }) + .into() + }) + .trigger( + IconButton::new("application-menu", ui::IconName::Menu) + .style(ButtonStyle::Subtle) + .tooltip(|cx| Tooltip::text("Open Application Menu", cx)) + .icon_size(IconSize::Small), + ) + .into_any_element() + }) + } pub fn render_project_host(&self, cx: &mut ViewContext) -> Option { if let Some(dev_server) = @@ -743,8 +910,9 @@ impl CollabTitlebarItem { .menu(|cx| { ContextMenu::build(cx, |menu, _| { menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) - .action("Extensions", extensions_ui::Extensions.boxed_clone()) - .action("Themes…", theme_selector::Toggle::default().boxed_clone()) + .action("Key Bindings", Box::new(zed_actions::OpenKeymap)) + .action("Themes", theme_selector::Toggle::default().boxed_clone()) + .action("Extensions...", extensions_ui::Extensions.boxed_clone()) .separator() .action("Sign Out", client::SignOut.boxed_clone()) }) @@ -771,8 +939,9 @@ impl CollabTitlebarItem { .menu(|cx| { ContextMenu::build(cx, |menu, _| { menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) - .action("Extensions", extensions_ui::Extensions.boxed_clone()) - .action("Themes…", theme_selector::Toggle::default().boxed_clone()) + .action("Key Bindings", Box::new(zed_actions::OpenKeymap)) + .action("Themes", theme_selector::Toggle::default().boxed_clone()) + .action("Extensions...", extensions_ui::Extensions.boxed_clone()) }) .into() }) diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 97f5c2d437..cca67cb5e7 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -3,9 +3,8 @@ use crate::notifications::collab_notification::CollabNotification; use call::{ActiveCall, IncomingCall}; use futures::StreamExt; use gpui::{prelude::*, AppContext, WindowHandle}; -use settings::Settings; + use std::sync::{Arc, Weak}; -use theme::ThemeSettings; use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -113,13 +112,7 @@ impl IncomingCallNotification { impl Render for IncomingCallNotification { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - // TODO: Is there a better place for us to initialize the font? - let (ui_font, ui_font_size) = { - let theme_settings = ThemeSettings::get_global(cx); - (theme_settings.ui_font.clone(), theme_settings.ui_font_size) - }; - - cx.set_rem_size(ui_font_size); + let ui_font = theme::setup_ui_font(cx); div().size_full().font(ui_font).child( CollabNotification::new( diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 9970c1feee..2634bf1c6f 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -4,9 +4,8 @@ use call::{room, ActiveCall}; use client::User; use collections::HashMap; use gpui::{AppContext, Size}; -use settings::Settings; use std::sync::{Arc, Weak}; -use theme::ThemeSettings; + use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -124,13 +123,7 @@ impl ProjectSharedNotification { impl Render for ProjectSharedNotification { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - // TODO: Is there a better place for us to initialize the font? - let (ui_font, ui_font_size) = { - let theme_settings = ThemeSettings::get_global(cx); - (theme_settings.ui_font.clone(), theme_settings.ui_font_size) - }; - - cx.set_rem_size(ui_font_size); + let ui_font = theme::setup_ui_font(cx); div().size_full().font(ui_font).child( CollabNotification::new( diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 39d8b3036f..88d6df6cad 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -1,5 +1,6 @@ //! This module contains all actions supported by [`Editor`]. use super::*; +use gpui::action_as; use util::serde::default_true; #[derive(PartialEq, Clone, Deserialize, Default)] @@ -290,6 +291,7 @@ gpui::actions!( TabPrev, ToggleGitBlame, ToggleGitBlameInline, + ToggleSelectionMenu, ToggleHunkDiff, ToggleInlayHints, ToggleLineNumbers, @@ -304,3 +306,7 @@ gpui::actions!( UniqueLinesCaseSensitive, ] ); + +action_as!(outline, ToggleOutline as Toggle); + +action_as!(go_to_line, ToggleGoToLine as Toggle); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 62f4caaa95..0696e4b5ac 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -537,6 +537,7 @@ pub struct Editor { show_git_blame_inline: bool, show_git_blame_inline_delay_task: Option>, git_blame_inline_enabled: bool, + show_selection_menu: Option, blame: Option>, blame_subscription: Option, custom_context_menu: Option< @@ -1833,6 +1834,7 @@ impl Editor { custom_context_menu: None, show_git_blame_gutter: false, show_git_blame_inline: false, + show_selection_menu: None, show_git_blame_inline_delay_task: None, git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(), blame: None, @@ -10182,6 +10184,20 @@ impl Editor { self.git_blame_inline_enabled } + pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext) { + self.show_selection_menu = self + .show_selection_menu + .map(|show_selections_menu| !show_selections_menu) + .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu)); + + cx.notify(); + } + + pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool { + self.show_selection_menu + .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu) + } + fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext) { if let Some(project) = self.project.as_ref() { let Some(buffer) = self.buffer().read(cx).as_singleton() else { diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 4de22ee954..3aa407d6a0 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -67,6 +67,7 @@ pub enum DoubleClickInMultibuffer { pub struct Toolbar { pub breadcrumbs: bool, pub quick_actions: bool, + pub selections_menu: bool, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -129,6 +130,7 @@ pub struct EditorSettingsContent { /// /// Default: true pub hover_popover_enabled: Option, + /// Whether to pop the completions menu while typing in an editor without /// explicitly requesting it. /// @@ -202,10 +204,15 @@ pub struct ToolbarContent { /// /// Default: true pub breadcrumbs: Option, - /// Whether to display quik action buttons in the editor toolbar. + /// Whether to display quick action buttons in the editor toolbar. /// /// Default: true pub quick_actions: Option, + + /// Whether to show the selections menu in the editor toolbar + /// + /// Default: true + pub selections_menu: Option, } /// Scrollbar related settings diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 62f9ebf23d..b83bcb3d91 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -7,9 +7,9 @@ use collections::{BTreeSet, HashMap}; use editor::{scroll::Autoscroll, Bias, Editor}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ - actions, impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, - FocusHandle, FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, - Styled, Task, View, ViewContext, VisualContext, WeakView, + actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, + FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task, + View, ViewContext, VisualContext, WeakView, }; use itertools::Itertools; use new_path_prompt::NewPathPrompt; @@ -30,13 +30,6 @@ use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use workspace::{item::PreviewTabsSettings, ModalView, Workspace}; actions!(file_finder, [SelectPrev]); -impl_actions!(file_finder, [Toggle]); - -#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)] -pub struct Toggle { - #[serde(default)] - pub separate_history: bool, -} impl ModalView for FileFinder {} @@ -52,7 +45,7 @@ pub fn init(cx: &mut AppContext) { impl FileFinder { fn register(workspace: &mut Workspace, _: &mut ViewContext) { - workspace.register_action(|workspace, action: &Toggle, cx| { + workspace.register_action(|workspace, action: &workspace::ToggleFileFinder, cx| { let Some(file_finder) = workspace.active_modal::(cx) else { Self::open(workspace, action.separate_history, cx); return; diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index 9d70581f91..13c628a023 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -6,7 +6,7 @@ use gpui::{Entity, TestAppContext, VisualTestContext}; use menu::{Confirm, SelectNext, SelectPrev}; use project::FS_WATCH_LATENCY; use serde_json::json; -use workspace::{AppState, Workspace}; +use workspace::{AppState, ToggleFileFinder, Workspace}; #[ctor::ctor] fn init_logger() { @@ -872,7 +872,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) { let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await; for expected_selected_index in 0..current_history.len() { - cx.dispatch_action(Toggle::default()); + cx.dispatch_action(ToggleFileFinder::default()); let picker = active_file_picker(&workspace, cx); let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index()); assert_eq!( @@ -881,7 +881,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) { ); } - cx.dispatch_action(Toggle::default()); + cx.dispatch_action(ToggleFileFinder::default()); let selected_index = workspace.update(cx, |workspace, cx| { workspace .active_modal::(cx) @@ -1201,7 +1201,7 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) { open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await; open_queried_buffer("main", 1, "main.rs", &workspace, cx).await; - cx.dispatch_action(Toggle::default()); + cx.dispatch_action(ToggleFileFinder::default()); let picker = active_file_picker(&workspace, cx); // main.rs is on top, previously used is selected picker.update(cx, |finder, _| { @@ -1653,7 +1653,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav( // Back to navigation with initial shortcut // Open file on modifiers release cx.simulate_modifiers_change(Modifiers::secondary_key()); - cx.dispatch_action(Toggle::default()); + cx.dispatch_action(ToggleFileFinder::default()); cx.simulate_modifiers_change(Modifiers::none()); cx.read(|cx| { let active_editor = workspace.read(cx).active_item_as::(cx).unwrap(); @@ -1769,7 +1769,7 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) { let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); - cx.dispatch_action(Toggle::default()); + cx.dispatch_action(ToggleFileFinder::default()); let picker = active_file_picker(&workspace, cx); picker.update(cx, |picker, _| { assert_eq!(picker.delegate.selected_index, 0); @@ -1777,9 +1777,9 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) { }); // When toggling repeatedly, the picker scrolls to reveal the selected item. - cx.dispatch_action(Toggle::default()); - cx.dispatch_action(Toggle::default()); - cx.dispatch_action(Toggle::default()); + cx.dispatch_action(ToggleFileFinder::default()); + cx.dispatch_action(ToggleFileFinder::default()); + cx.dispatch_action(ToggleFileFinder::default()); picker.update(cx, |picker, _| { assert_eq!(picker.delegate.selected_index, 3); assert_eq!(picker.logical_scroll_top_index(), 3); @@ -1886,7 +1886,7 @@ fn open_file_picker( workspace: &View, cx: &mut VisualTestContext, ) -> View> { - cx.dispatch_action(Toggle { + cx.dispatch_action(ToggleFileFinder { separate_history: true, }); active_file_picker(workspace, cx) diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 420cb858e6..0f14af3bd1 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -134,7 +134,13 @@ impl Render for CursorPosition { }); } })) - .tooltip(|cx| Tooltip::for_action("Go to Line/Column", &crate::Toggle, cx)), + .tooltip(|cx| { + Tooltip::for_action( + "Go to Line/Column", + &editor::actions::ToggleGoToLine, + cx, + ) + }), ) }) } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index bce517997d..4efef28d0e 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -3,7 +3,7 @@ pub mod cursor_position; use cursor_position::LineIndicatorFormat; use editor::{scroll::Autoscroll, Editor}; use gpui::{ - actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle, + div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, }; use settings::Settings; @@ -13,8 +13,6 @@ use ui::{h_flex, prelude::*, v_flex, Label}; use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::ModalView; -actions!(go_to_line, [Toggle]); - pub fn init(cx: &mut AppContext) { LineIndicatorFormat::register(cx); cx.observe_new_views(GoToLine::register).detach(); @@ -43,7 +41,7 @@ impl GoToLine { fn register(editor: &mut Editor, cx: &mut ViewContext) { let handle = cx.view().downgrade(); editor - .register_action(move |_: &Toggle, cx| { + .register_action(move |_: &editor::actions::ToggleGoToLine, cx| { let Some(editor) = handle.upgrade() else { return; }; @@ -341,7 +339,7 @@ mod tests { workspace: &View, cx: &mut VisualTestContext, ) -> View { - cx.dispatch_action(Toggle); + cx.dispatch_action(editor::actions::ToggleGoToLine); workspace.update(cx, |workspace, cx| { workspace.active_modal::(cx).unwrap().clone() }) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index cf0ad7e598..2b5b3b9756 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -189,7 +189,7 @@ macro_rules! actions { #[serde(crate = "gpui::private::serde")] pub struct $name; - gpui::__impl_action!($namespace, $name, + gpui::__impl_action!($namespace, $name, $name, fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { Ok(Box::new(Self)) } @@ -200,12 +200,48 @@ macro_rules! actions { }; } +/// Defines a unit struct that can be used as an actions, with a name +/// that differs from it's type name. +/// +/// To use more complex data types as actions, and rename them use +/// `impl_action_as!` +#[macro_export] +macro_rules! action_as { + ($namespace:path, $name:ident as $visual_name:tt) => { + #[doc = "The `"] + #[doc = stringify!($name)] + #[doc = "` action, see [`gpui::actions!`]"] + #[derive( + ::std::cmp::PartialEq, + ::std::clone::Clone, + ::std::default::Default, + ::std::fmt::Debug, + gpui::private::serde_derive::Deserialize, + )] + #[serde(crate = "gpui::private::serde")] + pub struct $name; + + gpui::__impl_action!( + $namespace, + $name, + $visual_name, + fn build( + _: gpui::private::serde_json::Value, + ) -> gpui::Result<::std::boxed::Box> { + Ok(Box::new(Self)) + } + ); + + gpui::register_action!($name); + }; +} + /// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize #[macro_export] macro_rules! impl_actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { $( - gpui::__impl_action!($namespace, $name, + gpui::__impl_action!($namespace, $name, $name, fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::(value)?)) } @@ -216,17 +252,39 @@ macro_rules! impl_actions { }; } +/// Implements the Action trait for a struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize +/// Allows you to rename the action visually, without changing the struct's name +#[macro_export] +macro_rules! impl_action_as { + ($namespace:path, $name:ident as $visual_name:tt ) => { + gpui::__impl_action!( + $namespace, + $name, + $visual_name, + fn build( + value: gpui::private::serde_json::Value, + ) -> gpui::Result<::std::boxed::Box> { + Ok(std::boxed::Box::new( + gpui::private::serde_json::from_value::(value)?, + )) + } + ); + + gpui::register_action!($name); + }; +} + #[doc(hidden)] #[macro_export] macro_rules! __impl_action { - ($namespace:path, $name:ident, $build:item) => { + ($namespace:path, $name:ident, $visual_name:tt, $build:item) => { impl gpui::Action for $name { fn name(&self) -> &'static str { concat!( stringify!($namespace), "::", - stringify!($name), + stringify!($visual_name), ) } @@ -237,7 +295,7 @@ macro_rules! __impl_action { concat!( stringify!($namespace), "::", - stringify!($name), + stringify!($visual_name), ) } diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 1d3c32daa4..feceae2e6c 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -2157,6 +2157,12 @@ impl From for Radians { #[repr(transparent)] pub struct Pixels(pub f32); +impl std::fmt::Display for Pixels { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}px", self.0)) + } +} + impl std::ops::Div for Pixels { type Output = f32; diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index f78d62f95f..6a0d37e395 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -1,9 +1,11 @@ -use editor::{scroll::Autoscroll, Anchor, AnchorRangeExt, Editor, EditorMode}; +use editor::{ + actions::ToggleOutline, scroll::Autoscroll, Anchor, AnchorRangeExt, Editor, EditorMode, +}; use fuzzy::StringMatch; use gpui::{ - actions, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, - HighlightStyle, ParentElement, Point, Render, Styled, Task, View, ViewContext, VisualContext, - WeakView, WindowContext, + div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, HighlightStyle, + ParentElement, Point, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use language::Outline; use ordered_float::OrderedFloat; @@ -18,13 +20,11 @@ use ui::{prelude::*, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{DismissDecision, ModalView}; -actions!(outline, [Toggle]); - pub fn init(cx: &mut AppContext) { cx.observe_new_views(OutlineView::register).detach(); } -pub fn toggle(editor: View, _: &Toggle, cx: &mut WindowContext) { +pub fn toggle(editor: View, _: &ToggleOutline, cx: &mut WindowContext) { let outline = editor .read(cx) .buffer() @@ -423,7 +423,7 @@ mod tests { workspace: &View, cx: &mut VisualTestContext, ) -> View> { - cx.dispatch_action(Toggle); + cx.dispatch_action(ToggleOutline); workspace.update(cx, |workspace, cx| { workspace .active_modal::(cx) diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index e45db2ba86..75b02e6826 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,8 +1,8 @@ use editor::{scroll::Autoscroll, styled_runs_for_code_label, Bias, Editor}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task, - View, ViewContext, WeakView, WindowContext, + rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task, View, + ViewContext, WeakView, WindowContext, }; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; @@ -15,12 +15,10 @@ use workspace::{ Workspace, }; -actions!(project_symbols, [Toggle]); - pub fn init(cx: &mut AppContext) { cx.observe_new_views( |workspace: &mut Workspace, _: &mut ViewContext| { - workspace.register_action(|workspace, _: &Toggle, cx| { + workspace.register_action(|workspace, _: &workspace::ToggleProjectSymbols, cx| { let project = workspace.project().clone(); let handle = cx.view().downgrade(); workspace.toggle_modal(cx, move |cx| { diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index 620c21c807..4b41674730 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -1,5 +1,10 @@ use assistant::assistant_settings::AssistantSettings; use assistant::{AssistantPanel, InlineAssist}; +use editor::actions::{ + AddSelectionAbove, AddSelectionBelow, DuplicateLineDown, GoToDiagnostic, GoToHunk, + GoToPrevDiagnostic, GoToPrevHunk, MoveLineDown, MoveLineUp, SelectAll, SelectLargerSyntaxNode, + SelectNext, SelectSmallerSyntaxNode, ToggleGoToLine, ToggleOutline, +}; use editor::{Editor, EditorSettings}; use gpui::{ @@ -18,6 +23,7 @@ use workspace::{ pub struct QuickActionBar { buffer_search_bar: View, toggle_settings_menu: Option>, + toggle_selections_menu: Option>, active_item: Option>, _inlay_hints_enabled_subscription: Option, workspace: WeakView, @@ -33,6 +39,7 @@ impl QuickActionBar { let mut this = Self { buffer_search_bar, toggle_settings_menu: None, + toggle_selections_menu: None, active_item: None, _inlay_hints_enabled_subscription: None, workspace: workspace.weak_handle(), @@ -86,22 +93,43 @@ impl Render for QuickActionBar { return div().id("empty quick action bar"); }; - let search_button = Some(QuickActionBarButton::new( - "toggle buffer search", - IconName::MagnifyingGlass, - !self.buffer_search_bar.read(cx).is_dismissed(), - Box::new(buffer_search::Deploy::find()), - "Buffer Search", - { - let buffer_search_bar = self.buffer_search_bar.clone(); - move |_, cx| { - buffer_search_bar.update(cx, |search_bar, cx| { - search_bar.toggle(&buffer_search::Deploy::find(), cx) - }); - } - }, - )) - .filter(|_| editor.is_singleton(cx)); + let ( + selection_menu_enabled, + inlay_hints_enabled, + supports_inlay_hints, + git_blame_inline_enabled, + ) = { + let editor = editor.read(cx); + let selection_menu_enabled = editor.selection_menu_enabled(cx); + let inlay_hints_enabled = editor.inlay_hints_enabled(); + let supports_inlay_hints = editor.supports_inlay_hints(cx); + let git_blame_inline_enabled = editor.git_blame_inline_enabled(); + + ( + selection_menu_enabled, + inlay_hints_enabled, + supports_inlay_hints, + git_blame_inline_enabled, + ) + }; + + let search_button = editor.is_singleton(cx).then(|| { + QuickActionBarButton::new( + "toggle buffer search", + IconName::MagnifyingGlass, + !self.buffer_search_bar.read(cx).is_dismissed(), + Box::new(buffer_search::Deploy::find()), + "Buffer Search", + { + let buffer_search_bar = self.buffer_search_bar.clone(); + move |_, cx| { + buffer_search_bar.update(cx, |search_bar, cx| { + search_bar.toggle(&buffer_search::Deploy::find(), cx) + }); + } + }, + ) + }); let assistant_button = QuickActionBarButton::new( "toggle inline assistant", @@ -121,6 +149,55 @@ impl Render for QuickActionBar { }, ); + let editor_selections_dropdown = selection_menu_enabled.then(|| { + IconButton::new("toggle_editor_selections_icon", IconName::TextCursor) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .selected(self.toggle_selections_menu.is_some()) + .on_click({ + let focus = editor.focus_handle(cx); + cx.listener(move |quick_action_bar, _, cx| { + let focus = focus.clone(); + let menu = ContextMenu::build(cx, move |menu, _| { + menu.context(focus.clone()) + .action("Select All", Box::new(SelectAll)) + .action( + "Select Next Occurrence", + Box::new(SelectNext { + replace_newest: false, + }), + ) + .action("Expand Selection", Box::new(SelectLargerSyntaxNode)) + .action("Shrink Selection", Box::new(SelectSmallerSyntaxNode)) + .action("Add Cursor Above", Box::new(AddSelectionAbove)) + .action("Add Cursor Below", Box::new(AddSelectionBelow)) + .separator() + .action("Go to Symbol", Box::new(ToggleOutline)) + .action("Go to Line/Column", Box::new(ToggleGoToLine)) + .separator() + .action("Next Problem", Box::new(GoToDiagnostic)) + .action("Previous Problem", Box::new(GoToPrevDiagnostic)) + .separator() + .action("Next Hunk", Box::new(GoToHunk)) + .action("Previous Hunk", Box::new(GoToPrevHunk)) + .separator() + .action("Move Line Up", Box::new(MoveLineUp)) + .action("Move Line Down", Box::new(MoveLineDown)) + .action("Duplicate Selection", Box::new(DuplicateLineDown)) + }); + cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| { + quick_action_bar.toggle_selections_menu = None; + }) + .detach(); + quick_action_bar.toggle_selections_menu = Some(menu); + }) + }) + .when(self.toggle_selections_menu.is_none(), |this| { + this.tooltip(|cx| Tooltip::text("Selection Controls", cx)) + }) + }); + let editor_settings_dropdown = IconButton::new("toggle_editor_settings_icon", IconName::Sliders) .size(ButtonSize::Compact) @@ -130,10 +207,6 @@ impl Render for QuickActionBar { .on_click({ let editor = editor.clone(); cx.listener(move |quick_action_bar, _, cx| { - let inlay_hints_enabled = editor.read(cx).inlay_hints_enabled(); - let supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx); - let git_blame_inline_enabled = editor.read(cx).git_blame_inline_enabled(); - let menu = ContextMenu::build(cx, |mut menu, _| { if supports_inlay_hints { menu = menu.toggleable_entry( @@ -171,6 +244,23 @@ impl Render for QuickActionBar { }, ); + menu = menu.toggleable_entry( + "Show Selection Menu", + selection_menu_enabled, + Some(editor::actions::ToggleSelectionMenu.boxed_clone()), + { + let editor = editor.clone(); + move |cx| { + editor.update(cx, |editor, cx| { + editor.toggle_selection_menu( + &editor::actions::ToggleSelectionMenu, + cx, + ) + }); + } + }, + ); + menu }); cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| { @@ -191,6 +281,7 @@ impl Render for QuickActionBar { h_flex() .gap_1p5() .children(search_button) + .children(editor_selections_dropdown) .when(AssistantSettings::get_global(cx).button, |bar| { bar.child(assistant_button) }), @@ -202,6 +293,12 @@ impl Render for QuickActionBar { el.child(Self::render_menu_overlay(toggle_settings_menu)) }, ) + .when_some( + self.toggle_selections_menu.as_ref(), + |el, toggle_selections_menu| { + el.child(Self::render_menu_overlay(toggle_selections_menu)) + }, + ) } } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index ac7d2ba305..d1b329dfb4 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -4,7 +4,7 @@ use anyhow::Result; use derive_more::{Deref, DerefMut}; use gpui::{ px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription, - ViewContext, + ViewContext, WindowContext, }; use refineable::Refineable; use schemars::{ @@ -167,6 +167,11 @@ pub(crate) struct AdjustedBufferFontSize(Pixels); impl Global for AdjustedBufferFontSize {} +#[derive(Default)] +pub(crate) struct AdjustedUiFontSize(Pixels); + +impl Global for AdjustedUiFontSize {} + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(untagged)] pub enum ThemeSelection { @@ -358,7 +363,13 @@ pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels { .max(MIN_FONT_SIZE) } -pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) { +pub fn get_buffer_font_size(cx: &AppContext) -> Pixels { + let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; + cx.try_global::() + .map_or(buffer_font_size, |adjusted_size| adjusted_size.0) +} + +pub fn adjust_buffer_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) { let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; let mut adjusted_size = cx .try_global::() @@ -370,13 +381,49 @@ pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) { cx.refresh(); } -pub fn reset_font_size(cx: &mut AppContext) { +pub fn reset_buffer_font_size(cx: &mut AppContext) { if cx.has_global::() { cx.remove_global::(); cx.refresh(); } } +pub fn setup_ui_font(cx: &mut WindowContext) -> gpui::Font { + let (ui_font, ui_font_size) = { + let theme_settings = ThemeSettings::get_global(cx); + let font = theme_settings.ui_font.clone(); + (font, get_ui_font_size(cx)) + }; + + cx.set_rem_size(ui_font_size); + ui_font +} + +pub fn get_ui_font_size(cx: &WindowContext) -> Pixels { + let ui_font_size = ThemeSettings::get_global(cx).ui_font_size; + cx.try_global::() + .map_or(ui_font_size, |adjusted_size| adjusted_size.0) +} + +pub fn adjust_ui_font_size(cx: &mut WindowContext, f: fn(&mut Pixels)) { + let ui_font_size = ThemeSettings::get_global(cx).ui_font_size; + let mut adjusted_size = cx + .try_global::() + .map_or(ui_font_size, |adjusted_size| adjusted_size.0); + + f(&mut adjusted_size); + adjusted_size = adjusted_size.max(MIN_FONT_SIZE); + cx.set_global(AdjustedUiFontSize(adjusted_size)); + cx.refresh(); +} + +pub fn reset_ui_font_size(cx: &mut WindowContext) { + if cx.has_global::() { + cx.remove_global::(); + cx.refresh(); + } +} + impl settings::Settings for ThemeSettings { const KEY: Option<&'static str> = None; diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 4e3e6ae1bd..fa54159f61 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -84,7 +84,7 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; if buffer_font_size != prev_buffer_font_size { prev_buffer_font_size = buffer_font_size; - reset_font_size(cx); + reset_buffer_font_size(cx); } }) .detach(); diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index aa2670a688..11f24c0377 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -18,12 +18,13 @@ enum ContextMenuItem { toggled: Option, label: SharedString, icon: Option, - handler: Rc, + handler: Rc, &mut WindowContext)>, action: Option>, }, CustomEntry { entry_render: Box AnyElement>, - handler: Rc, + handler: Rc, &mut WindowContext)>, + selectable: bool, }, } @@ -97,7 +98,7 @@ impl ContextMenu { self.items.push(ContextMenuItem::Entry { toggled: None, label: label.into(), - handler: Rc::new(handler), + handler: Rc::new(move |_, cx| handler(cx)), icon: None, action, }); @@ -114,13 +115,25 @@ impl ContextMenu { self.items.push(ContextMenuItem::Entry { toggled: Some(toggled), label: label.into(), - handler: Rc::new(handler), + handler: Rc::new(move |_, cx| handler(cx)), icon: None, action, }); self } + pub fn custom_row( + mut self, + entry_render: impl Fn(&mut WindowContext) -> AnyElement + 'static, + ) -> Self { + self.items.push(ContextMenuItem::CustomEntry { + entry_render: Box::new(entry_render), + handler: Rc::new(|_, _| {}), + selectable: false, + }); + self + } + pub fn custom_entry( mut self, entry_render: impl Fn(&mut WindowContext) -> AnyElement + 'static, @@ -128,7 +141,8 @@ impl ContextMenu { ) -> Self { self.items.push(ContextMenuItem::CustomEntry { entry_render: Box::new(entry_render), - handler: Rc::new(handler), + handler: Rc::new(move |_, cx| handler(cx)), + selectable: true, }); self } @@ -138,7 +152,13 @@ impl ContextMenu { toggled: None, label: label.into(), action: Some(action.boxed_clone()), - handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), + + handler: Rc::new(move |context, cx| { + if let Some(context) = &context { + cx.focus(context); + } + cx.dispatch_action(action.boxed_clone()); + }), icon: None, }); self @@ -148,19 +168,21 @@ impl ContextMenu { self.items.push(ContextMenuItem::Entry { toggled: None, label: label.into(), + action: Some(action.boxed_clone()), - handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), + handler: Rc::new(move |_, cx| cx.dispatch_action(action.boxed_clone())), icon: Some(IconName::Link), }); self } pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { + let context = self.action_context.as_ref(); match self.selected_index.and_then(|ix| self.items.get(ix)) { Some( ContextMenuItem::Entry { handler, .. } | ContextMenuItem::CustomEntry { handler, .. }, - ) => (handler)(cx), + ) => (handler)(context, cx), _ => {} } @@ -260,7 +282,12 @@ impl ContextMenu { impl ContextMenuItem { fn is_selectable(&self) -> bool { - matches!(self, Self::Entry { .. } | Self::CustomEntry { .. }) + match self { + ContextMenuItem::Separator => false, + ContextMenuItem::Header(_) => false, + ContextMenuItem::Entry { .. } => true, + ContextMenuItem::CustomEntry { selectable, .. } => *selectable, + } } } @@ -360,32 +387,47 @@ impl Render for ContextMenu { .map(|binding| div().ml_4().child(binding)) })), ) - .on_click(move |_, cx| { - handler(cx); - menu.update(cx, |menu, cx| { - menu.clicked = true; - cx.emit(DismissEvent); - }) - .ok(); + .on_click({ + let context = self.action_context.clone(); + move |_, cx| { + handler(context.as_ref(), cx); + menu.update(cx, |menu, cx| { + menu.clicked = true; + cx.emit(DismissEvent); + }) + .ok(); + } }) .into_any_element() } ContextMenuItem::CustomEntry { entry_render, handler, + selectable, } => { let handler = handler.clone(); let menu = cx.view().downgrade(); ListItem::new(ix) .inset(true) - .selected(Some(ix) == self.selected_index) - .on_click(move |_, cx| { - handler(cx); - menu.update(cx, |menu, cx| { - menu.clicked = true; - cx.emit(DismissEvent); - }) - .ok(); + .selected(if *selectable { + Some(ix) == self.selected_index + } else { + false + }) + .selectable(*selectable) + .on_click({ + let context = self.action_context.clone(); + let selectable = *selectable; + move |_, cx| { + if selectable { + handler(context.as_ref(), cx); + menu.update(cx, |menu, cx| { + menu.clicked = true; + cx.emit(DismissEvent); + }) + .ok(); + } + } }) .child(entry_render(cx)) .into_any_element() diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 67204c429a..b752da75db 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -174,6 +174,7 @@ pub enum IconName { Rerun, Return, Reveal, + RotateCcw, RotateCw, Save, Screen, @@ -199,6 +200,7 @@ pub enum IconName { SupermavenInit, Tab, Terminal, + TextCursor, Trash, TriangleRight, Update, @@ -307,6 +309,7 @@ impl IconName { IconName::Rerun => "icons/rerun.svg", IconName::Return => "icons/return.svg", IconName::RotateCw => "icons/rotate_cw.svg", + IconName::RotateCcw => "icons/rotate_ccw.svg", IconName::Save => "icons/save.svg", IconName::Screen => "icons/desktop.svg", IconName::SelectAll => "icons/select_all.svg", @@ -331,6 +334,7 @@ impl IconName { IconName::SupermavenInit => "icons/supermaven_init.svg", IconName::Tab => "icons/tab.svg", IconName::Terminal => "icons/terminal.svg", + IconName::TextCursor => "icons/text-cursor.svg", IconName::Trash => "icons/trash.svg", IconName::TriangleRight => "icons/triangle_right.svg", IconName::Update => "icons/update.svg", diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index 736d972e45..e7720afb6c 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -35,6 +35,7 @@ pub struct ListItem { tooltip: Option AnyView + 'static>>, on_secondary_mouse_down: Option>, children: SmallVec<[AnyElement; 2]>, + selectable: bool, } impl ListItem { @@ -56,6 +57,7 @@ impl ListItem { on_toggle: None, tooltip: None, children: SmallVec::new(), + selectable: true, } } @@ -64,6 +66,11 @@ impl ListItem { self } + pub fn selectable(mut self, has_hover: bool) -> Self { + self.selectable = has_hover; + self + } + pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { self.on_click = Some(Box::new(handler)); self @@ -164,10 +171,12 @@ impl RenderOnce for ListItem { // this.border_1() // .border_color(cx.theme().colors().border_focused) // }) - .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) - .active(|style| style.bg(cx.theme().colors().ghost_element_active)) - .when(self.selected, |this| { - this.bg(cx.theme().colors().ghost_element_selected) + .when(self.selectable, |this| { + this.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .active(|style| style.bg(cx.theme().colors().ghost_element_active)) + .when(self.selected, |this| { + this.bg(cx.theme().colors().ghost_element_selected) + }) }) }) .child( @@ -189,10 +198,14 @@ impl RenderOnce for ListItem { // this.border_1() // .border_color(cx.theme().colors().border_focused) // }) - .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) - .active(|style| style.bg(cx.theme().colors().ghost_element_active)) - .when(self.selected, |this| { - this.bg(cx.theme().colors().ghost_element_selected) + .when(self.selectable, |this| { + this.hover(|style| { + style.bg(cx.theme().colors().ghost_element_hover) + }) + .active(|style| style.bg(cx.theme().colors().ghost_element_active)) + .when(self.selected, |this| { + this.bg(cx.theme().colors().ghost_element_selected) + }) }) }) .when_some(self.on_click, |this, on_click| { diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index b735b4c710..1c8071da19 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -96,7 +96,9 @@ impl ModalLayer { previous_focus_handle: cx.focused(), focus_handle, }); - cx.focus_view(&new_modal); + cx.defer(move |_, cx| { + cx.focus_view(&new_modal); + }); cx.notify(); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e690a07d72..d940bee2ef 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -5,8 +5,8 @@ use crate::{ }, toolbar::Toolbar, workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings}, - CloseWindow, NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible, - SplitDirection, ToggleZoom, Workspace, + CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenTerminal, OpenVisible, SplitDirection, + ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace, }; use anyhow::Result; use collections::{BTreeSet, HashMap, HashSet, VecDeque}; @@ -366,8 +366,24 @@ impl Pane { .on_click(cx.listener(|pane, _, cx| { let menu = ContextMenu::build(cx, |menu, _| { menu.action("New File", NewFile.boxed_clone()) - .action("New Terminal", NewCenterTerminal.boxed_clone()) - .action("New Search", NewSearch.boxed_clone()) + .action( + "Open File", + ToggleFileFinder::default().boxed_clone(), + ) + .separator() + .action( + "Search Project", + DeploySearch { + replace_enabled: false, + } + .boxed_clone(), + ) + .action( + "Search Symbols", + ToggleProjectSymbols.boxed_clone(), + ) + .separator() + .action("New Terminal", NewTerminal.boxed_clone()) }); cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| { pane.focus(cx); @@ -1818,7 +1834,11 @@ impl Pane { .track_scroll(self.tab_bar_scroll_handle.clone()) .when( self.display_nav_history_buttons.unwrap_or_default(), - |tab_bar| tab_bar.start_children(vec![navigate_backward, navigate_forward]), + |tab_bar| { + tab_bar + .start_child(navigate_backward) + .start_child(navigate_forward) + }, ) .when(self.has_focus(cx), |tab_bar| { tab_bar.end_child({ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 83a88bbc6b..387aea6143 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -27,11 +27,11 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui::{ - actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView, - AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DragMoveEvent, Entity as _, EntityId, - EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke, ManagedView, Model, - ModelContext, PathPromptOptions, Point, PromptLevel, Render, Size, Subscription, Task, View, - WeakView, WindowBounds, WindowHandle, WindowOptions, + action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size, Action, + AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, + DragMoveEvent, Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, + KeyContext, Keystroke, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, + Render, Size, Subscription, Task, View, WeakView, WindowBounds, WindowHandle, WindowOptions, }; use item::{ FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, @@ -112,30 +112,30 @@ pub struct RemoveWorktreeFromProject(pub WorktreeId); actions!( workspace, [ + ActivateNextPane, + ActivatePreviousPane, + AddFolderToProject, + CloseAllDocks, + CloseWindow, + Feedback, + FollowNextCollaborator, + NewCenterTerminal, + NewFile, + NewSearch, + NewTerminal, + NewWindow, Open, OpenInTerminal, - NewFile, - NewWindow, - CloseWindow, - AddFolderToProject, - Unfollow, + ReloadActiveItem, SaveAs, SaveWithoutFormat, - ReloadActiveItem, - ActivatePreviousPane, - ActivateNextPane, - FollowNextCollaborator, - NewTerminal, - NewCenterTerminal, - NewSearch, - Feedback, - Welcome, - ToggleZoom, - ToggleLeftDock, - ToggleRightDock, ToggleBottomDock, ToggleCenteredLayout, - CloseAllDocks, + ToggleLeftDock, + ToggleRightDock, + ToggleZoom, + Unfollow, + Welcome, ] ); @@ -188,6 +188,16 @@ pub struct Reload { pub binary_path: Option, } +action_as!(project_symbols, ToggleProjectSymbols as Toggle); + +#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)] +pub struct ToggleFileFinder { + #[serde(default)] + pub separate_history: bool, +} + +impl_action_as!(file_finder, ToggleFileFinder as Toggle); + impl_actions!( workspace, [ @@ -4144,14 +4154,10 @@ impl Render for Workspace { } else { (None, None) }; - let (ui_font, ui_font_size) = { - let theme_settings = ThemeSettings::get_global(cx); - (theme_settings.ui_font.clone(), theme_settings.ui_font_size) - }; + let ui_font = theme::setup_ui_font(cx); let theme = cx.theme().clone(); let colors = theme.colors(); - cx.set_rem_size(ui_font_size); self.actions(div(), cx) .key_context(context) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e02c0e1244..9407c39bad 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -52,22 +52,15 @@ use zed_actions::{OpenBrowser, OpenSettings, OpenZedUrl, Quit}; actions!( zed, [ - About, DebugElements, - DecreaseBufferFontSize, Hide, HideOthers, - IncreaseBufferFontSize, Minimize, OpenDefaultKeymap, OpenDefaultSettings, - OpenKeymap, - OpenLicenses, OpenLocalSettings, OpenLocalTasks, OpenTasks, - OpenTelemetryLog, - ResetBufferFontSize, ResetDatabase, ShowAll, ToggleFullScreen, @@ -252,13 +245,33 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { OpenListener::global(cx).open_urls(vec![action.url.clone()]) }) .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url)) - .register_action(move |_, _: &IncreaseBufferFontSize, cx| { - theme::adjust_font_size(cx, |size| *size += px(1.0)) + .register_action(move |_, _: &zed_actions::IncreaseBufferFontSize, cx| { + theme::adjust_buffer_font_size(cx, |size| *size += px(1.0)) }) - .register_action(move |_, _: &DecreaseBufferFontSize, cx| { - theme::adjust_font_size(cx, |size| *size -= px(1.0)) + .register_action(move |_, _: &zed_actions::DecreaseBufferFontSize, cx| { + theme::adjust_buffer_font_size(cx, |size| *size -= px(1.0)) + }) + .register_action(move |_, _: &zed_actions::ResetBufferFontSize, cx| { + theme::reset_buffer_font_size(cx) + }) + .register_action(move |_, _: &zed_actions::IncreaseUiFontSize, cx| { + theme::adjust_ui_font_size(cx, |size| *size += px(1.0)) + }) + .register_action(move |_, _: &zed_actions::DecreaseUiFontSize, cx| { + theme::adjust_ui_font_size(cx, |size| *size -= px(1.0)) + }) + .register_action(move |_, _: &zed_actions::ResetUiFontSize, cx| { + theme::reset_ui_font_size(cx) + }) + .register_action(move |_, _: &zed_actions::IncreaseBufferFontSize, cx| { + theme::adjust_buffer_font_size(cx, |size| *size += px(1.0)) + }) + .register_action(move |_, _: &zed_actions::DecreaseBufferFontSize, cx| { + theme::adjust_buffer_font_size(cx, |size| *size -= px(1.0)) + }) + .register_action(move |_, _: &zed_actions::ResetBufferFontSize, cx| { + theme::reset_buffer_font_size(cx) }) - .register_action(move |_, _: &ResetBufferFontSize, cx| theme::reset_font_size(cx)) .register_action(|_, _: &install_cli::Install, cx| { cx.spawn(|workspace, mut cx| async move { if cfg!(target_os = "linux") { @@ -323,7 +336,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { .register_action(|workspace, _: &OpenLog, cx| { open_log_file(workspace, cx); }) - .register_action(|workspace, _: &OpenLicenses, cx| { + .register_action(|workspace, _: &zed_actions::OpenLicenses, cx| { open_bundled_file( workspace, asset_str::("licenses.md"), @@ -334,14 +347,16 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }) .register_action( move |workspace: &mut Workspace, - _: &OpenTelemetryLog, + _: &zed_actions::OpenTelemetryLog, cx: &mut ViewContext| { open_telemetry_log_file(workspace, cx); }, ) .register_action( - move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { - open_settings_file(paths::keymap_file(), Rope::default, cx); + move |_: &mut Workspace, + _: &zed_actions::OpenKeymap, + cx: &mut ViewContext| { + open_settings_file(&paths::keymap_file(), Rope::default, cx); }, ) .register_action( @@ -485,7 +500,7 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View, cx: &mut ViewCo }); } -fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { +fn about(_: &mut Workspace, _: &zed_actions::About, cx: &mut gpui::ViewContext) { let release_channel = ReleaseChannel::global(cx).display_name(); let version = env!("CARGO_PKG_VERSION"); let message = format!("{release_channel} {version}"); diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 8a12df90cb..403b28e360 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -9,14 +9,14 @@ pub fn app_menus() -> Vec> { Menu { name: "Zed", items: vec![ - MenuItem::action("About Zed…", super::About), + MenuItem::action("About Zed…", zed_actions::About), MenuItem::action("Check for Updates", auto_update::Check), MenuItem::separator(), MenuItem::submenu(Menu { name: "Preferences", items: vec![ MenuItem::action("Open Settings", super::OpenSettings), - MenuItem::action("Open Key Bindings", super::OpenKeymap), + MenuItem::action("Open Key Bindings", zed_actions::OpenKeymap), MenuItem::action("Open Default Settings", super::OpenDefaultSettings), MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap), MenuItem::action("Open Local Settings", super::OpenLocalSettings), @@ -104,9 +104,9 @@ pub fn app_menus() -> Vec> { Menu { name: "View", items: vec![ - MenuItem::action("Zoom In", super::IncreaseBufferFontSize), - MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), - MenuItem::action("Reset Zoom", super::ResetBufferFontSize), + MenuItem::action("Zoom In", zed_actions::IncreaseBufferFontSize), + MenuItem::action("Zoom Out", zed_actions::DecreaseBufferFontSize), + MenuItem::action("Reset Zoom", zed_actions::ResetBufferFontSize), MenuItem::separator(), MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), @@ -139,10 +139,10 @@ pub fn app_menus() -> Vec> { MenuItem::separator(), MenuItem::action("Command Palette...", command_palette::Toggle), MenuItem::separator(), - MenuItem::action("Go to File...", file_finder::Toggle::default()), + MenuItem::action("Go to File...", workspace::ToggleFileFinder::default()), // MenuItem::action("Go to Symbol in Project", project_symbols::Toggle), - MenuItem::action("Go to Symbol in Editor...", outline::Toggle), - MenuItem::action("Go to Line/Column...", go_to_line::Toggle), + MenuItem::action("Go to Symbol in Editor...", editor::actions::ToggleOutline), + MenuItem::action("Go to Line/Column...", editor::actions::ToggleGoToLine), MenuItem::separator(), MenuItem::action("Go to Definition", editor::actions::GoToDefinition), MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition), @@ -163,8 +163,8 @@ pub fn app_menus() -> Vec> { Menu { name: "Help", items: vec![ - MenuItem::action("View Telemetry", super::OpenTelemetryLog), - MenuItem::action("View Dependency Licenses", super::OpenLicenses), + MenuItem::action("View Telemetry", zed_actions::OpenTelemetryLog), + MenuItem::action("View Dependency Licenses", zed_actions::OpenLicenses), MenuItem::action("Show Welcome", workspace::Welcome), MenuItem::action("Give Feedback...", feedback::GiveFeedback), MenuItem::separator(), diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 9c62e225c7..7e2c8a096e 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -22,4 +22,20 @@ pub struct OpenZedUrl { impl_actions!(zed, [OpenBrowser, OpenZedUrl]); -actions!(zed, [OpenSettings, Quit]); +actions!( + zed, + [ + OpenSettings, + Quit, + OpenKeymap, + About, + OpenLicenses, + OpenTelemetryLog, + DecreaseBufferFontSize, + IncreaseBufferFontSize, + ResetBufferFontSize, + DecreaseUiFontSize, + IncreaseUiFontSize, + ResetUiFontSize + ] +);