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