Add more menus to Zed (#12940)

### TODO

- [x] Make sure keybinding shows up in pane + menu
- [x] Selection tool in the editor toolbar
- [x] Application Menu
- [x] Add more options to pane + menu
   - Go to File...
  - Go to Symbol in Project... 
- [x] Add go items to the selection tool in the editor:
   - Go to Symbol in Editor...
   - Go to Line/Column...
   - Next Problem
   - Previous Problem
- [x] Fix a bug where modals opened from a context menu aren't focused
correclty
- [x] Determine if or what needs to be done with project actions:
- Difficulty is that these are exposed in the UI via clicking the
project name in the titlebar or by right clicking the root entry in the
project panel. But they require reading and are two clicks away. Is that
sufficient?
    - Add Folder to Project
    - Open a new project
    - Open recent
 - [x] Get a style pass 
 - [x] Implement style pass
   - [x] Fix the wrong actions in the selection menu
   - [x] Show selection tool toggle in the 'editor settings' thing
- [x] Put preferences section from the app menu onto the right hand user
menu
- [x] Add Project menu into app menu to replace 'preferences' section,
and put the rest of the actions there
- [ ] ~~Adopt `...` convention for opening a surface~~ uncertain what
this convention is.
   - [x] Adopt link styling  for webview actions
   - [x] Set lucide hamburger for menu icon
   - [x] Gate application menu to only show on Linux and Windows




Release Notes:

- Added a 'selection and movement' tool to the Editor's toolbar, as well
as controls to toggle it and a setting to remove it (`"toolbar":
{"selections_menu": true/false }`)
- Changed the behavior of the `+` menu in the tab bar to use standard
actions and keybindings. Replaced 'New Center Terminal' with 'New
Terminal', and 'New Search', with the usual 'Deploy Search'. Also added
item-creating actions to this menu.
- Added an 'application' menu to the titlebar to Linux and Windows
builds of Zed
This commit is contained in:
Mikayla Maki 2024-06-18 12:16:54 -07:00 committed by GitHub
parent 8af8493da6
commit 6b9ddbfef2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 712 additions and 198 deletions

2
Cargo.lock generated
View file

@ -2475,11 +2475,13 @@ dependencies = [
"channel", "channel",
"client", "client",
"collections", "collections",
"command_palette",
"db", "db",
"dev_server_projects", "dev_server_projects",
"editor", "editor",
"emojis", "emojis",
"extensions_ui", "extensions_ui",
"feedback",
"futures 0.3.28", "futures 0.3.28",
"fuzzy", "fuzzy",
"gpui", "gpui",

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>

After

Width:  |  Height:  |  Size: 302 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>

After

Width:  |  Height:  |  Size: 345 B

View file

@ -183,7 +183,9 @@
// Whether to show breadcrumbs. // Whether to show breadcrumbs.
"breadcrumbs": true, "breadcrumbs": true,
// Whether to show quick action buttons. // 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 related settings
"scrollbar": { "scrollbar": {

View file

@ -832,13 +832,8 @@ impl PromptLibrary {
impl Render for PromptLibrary { impl Render for PromptLibrary {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let (ui_font, ui_font_size) = { let ui_font = theme::setup_ui_font(cx);
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
let theme = cx.theme().clone(); let theme = cx.theme().clone();
cx.set_rem_size(ui_font_size);
h_flex() h_flex()
.id("prompt-manager") .id("prompt-manager")

View file

@ -86,10 +86,16 @@ impl Render for Breadcrumbs {
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.on_click(move |_, cx| { .on_click(move |_, cx| {
if let Some(editor) = editor.upgrade() { 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 None => element
// Match the height of the `ButtonLike` in the other arm. // Match the height of the `ButtonLike` in the other arm.

View file

@ -35,10 +35,12 @@ call.workspace = true
channel.workspace = true channel.workspace = true
client.workspace = true client.workspace = true
collections.workspace = true collections.workspace = true
command_palette.workspace = true
db.workspace = true db.workspace = true
editor.workspace = true editor.workspace = true
emojis.workspace = true emojis.workspace = true
extensions_ui.workspace = true extensions_ui.workspace = true
feedback.workspace = true
futures.workspace = true futures.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
gpui.workspace = true gpui.workspace = true

View file

@ -10,8 +10,9 @@ use gpui::{
use project::{Project, RepositoryEntry}; use project::{Project, RepositoryEntry};
use recent_projects::RecentProjects; use recent_projects::RecentProjects;
use rpc::proto::{self, DevServerStatus}; use rpc::proto::{self, DevServerStatus};
use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
use theme::ActiveTheme; use theme::{ActiveTheme, ThemeSettings};
use ui::{ use ui::{
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle, h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip, ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
@ -73,6 +74,7 @@ impl Render for CollabTitlebarItem {
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.children(self.render_application_menu(cx))
.children(self.render_project_host(cx)) .children(self.render_project_host(cx))
.child(self.render_project_name(cx)) .child(self.render_project_name(cx))
.children(self.render_project_branch(cx)) .children(self.render_project_branch(cx))
@ -386,8 +388,173 @@ impl CollabTitlebarItem {
} }
} }
// resolve if you are in a room -> render_project_owner pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
// render_project_owner -> resolve if you are in a room -> Option<foo> 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<Self>) -> Option<AnyElement> { pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
if let Some(dev_server) = if let Some(dev_server) =
@ -743,8 +910,9 @@ impl CollabTitlebarItem {
.menu(|cx| { .menu(|cx| {
ContextMenu::build(cx, |menu, _| { ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone()) .action("Key Bindings", Box::new(zed_actions::OpenKeymap))
.action("Themes…", theme_selector::Toggle::default().boxed_clone()) .action("Themes", theme_selector::Toggle::default().boxed_clone())
.action("Extensions...", extensions_ui::Extensions.boxed_clone())
.separator() .separator()
.action("Sign Out", client::SignOut.boxed_clone()) .action("Sign Out", client::SignOut.boxed_clone())
}) })
@ -771,8 +939,9 @@ impl CollabTitlebarItem {
.menu(|cx| { .menu(|cx| {
ContextMenu::build(cx, |menu, _| { ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone()) .action("Key Bindings", Box::new(zed_actions::OpenKeymap))
.action("Themes…", theme_selector::Toggle::default().boxed_clone()) .action("Themes", theme_selector::Toggle::default().boxed_clone())
.action("Extensions...", extensions_ui::Extensions.boxed_clone())
}) })
.into() .into()
}) })

View file

@ -3,9 +3,8 @@ use crate::notifications::collab_notification::CollabNotification;
use call::{ActiveCall, IncomingCall}; use call::{ActiveCall, IncomingCall};
use futures::StreamExt; use futures::StreamExt;
use gpui::{prelude::*, AppContext, WindowHandle}; use gpui::{prelude::*, AppContext, WindowHandle};
use settings::Settings;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use theme::ThemeSettings;
use ui::{prelude::*, Button, Label}; use ui::{prelude::*, Button, Label};
use util::ResultExt; use util::ResultExt;
use workspace::AppState; use workspace::AppState;
@ -113,13 +112,7 @@ impl IncomingCallNotification {
impl Render for IncomingCallNotification { impl Render for IncomingCallNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
// TODO: Is there a better place for us to initialize the font? let ui_font = theme::setup_ui_font(cx);
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);
div().size_full().font(ui_font).child( div().size_full().font(ui_font).child(
CollabNotification::new( CollabNotification::new(

View file

@ -4,9 +4,8 @@ use call::{room, ActiveCall};
use client::User; use client::User;
use collections::HashMap; use collections::HashMap;
use gpui::{AppContext, Size}; use gpui::{AppContext, Size};
use settings::Settings;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use theme::ThemeSettings;
use ui::{prelude::*, Button, Label}; use ui::{prelude::*, Button, Label};
use util::ResultExt; use util::ResultExt;
use workspace::AppState; use workspace::AppState;
@ -124,13 +123,7 @@ impl ProjectSharedNotification {
impl Render for ProjectSharedNotification { impl Render for ProjectSharedNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
// TODO: Is there a better place for us to initialize the font? let ui_font = theme::setup_ui_font(cx);
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);
div().size_full().font(ui_font).child( div().size_full().font(ui_font).child(
CollabNotification::new( CollabNotification::new(

View file

@ -1,5 +1,6 @@
//! This module contains all actions supported by [`Editor`]. //! This module contains all actions supported by [`Editor`].
use super::*; use super::*;
use gpui::action_as;
use util::serde::default_true; use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default)]
@ -290,6 +291,7 @@ gpui::actions!(
TabPrev, TabPrev,
ToggleGitBlame, ToggleGitBlame,
ToggleGitBlameInline, ToggleGitBlameInline,
ToggleSelectionMenu,
ToggleHunkDiff, ToggleHunkDiff,
ToggleInlayHints, ToggleInlayHints,
ToggleLineNumbers, ToggleLineNumbers,
@ -304,3 +306,7 @@ gpui::actions!(
UniqueLinesCaseSensitive, UniqueLinesCaseSensitive,
] ]
); );
action_as!(outline, ToggleOutline as Toggle);
action_as!(go_to_line, ToggleGoToLine as Toggle);

View file

@ -537,6 +537,7 @@ pub struct Editor {
show_git_blame_inline: bool, show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>, show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_enabled: bool, git_blame_inline_enabled: bool,
show_selection_menu: Option<bool>,
blame: Option<Model<GitBlame>>, blame: Option<Model<GitBlame>>,
blame_subscription: Option<Subscription>, blame_subscription: Option<Subscription>,
custom_context_menu: Option< custom_context_menu: Option<
@ -1833,6 +1834,7 @@ impl Editor {
custom_context_menu: None, custom_context_menu: None,
show_git_blame_gutter: false, show_git_blame_gutter: false,
show_git_blame_inline: false, show_git_blame_inline: false,
show_selection_menu: None,
show_git_blame_inline_delay_task: None, show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(), git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
blame: None, blame: None,
@ -10182,6 +10184,20 @@ impl Editor {
self.git_blame_inline_enabled self.git_blame_inline_enabled
} }
pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
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<Self>) { fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
if let Some(project) = self.project.as_ref() { if let Some(project) = self.project.as_ref() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else { let Some(buffer) = self.buffer().read(cx).as_singleton() else {

View file

@ -67,6 +67,7 @@ pub enum DoubleClickInMultibuffer {
pub struct Toolbar { pub struct Toolbar {
pub breadcrumbs: bool, pub breadcrumbs: bool,
pub quick_actions: bool, pub quick_actions: bool,
pub selections_menu: bool,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -129,6 +130,7 @@ pub struct EditorSettingsContent {
/// ///
/// Default: true /// Default: true
pub hover_popover_enabled: Option<bool>, pub hover_popover_enabled: Option<bool>,
/// Whether to pop the completions menu while typing in an editor without /// Whether to pop the completions menu while typing in an editor without
/// explicitly requesting it. /// explicitly requesting it.
/// ///
@ -202,10 +204,15 @@ pub struct ToolbarContent {
/// ///
/// Default: true /// Default: true
pub breadcrumbs: Option<bool>, pub breadcrumbs: Option<bool>,
/// Whether to display quik action buttons in the editor toolbar. /// Whether to display quick action buttons in the editor toolbar.
/// ///
/// Default: true /// Default: true
pub quick_actions: Option<bool>, pub quick_actions: Option<bool>,
/// Whether to show the selections menu in the editor toolbar
///
/// Default: true
pub selections_menu: Option<bool>,
} }
/// Scrollbar related settings /// Scrollbar related settings

View file

@ -7,9 +7,9 @@ use collections::{BTreeSet, HashMap};
use editor::{scroll::Autoscroll, Bias, Editor}; use editor::{scroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{ use gpui::{
actions, impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusHandle, FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task,
Styled, Task, View, ViewContext, VisualContext, WeakView, View, ViewContext, VisualContext, WeakView,
}; };
use itertools::Itertools; use itertools::Itertools;
use new_path_prompt::NewPathPrompt; use new_path_prompt::NewPathPrompt;
@ -30,13 +30,6 @@ use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::{item::PreviewTabsSettings, ModalView, Workspace}; use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
actions!(file_finder, [SelectPrev]); 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 {} impl ModalView for FileFinder {}
@ -52,7 +45,7 @@ pub fn init(cx: &mut AppContext) {
impl FileFinder { impl FileFinder {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) { fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, action: &Toggle, cx| { workspace.register_action(|workspace, action: &workspace::ToggleFileFinder, cx| {
let Some(file_finder) = workspace.active_modal::<Self>(cx) else { let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
Self::open(workspace, action.separate_history, cx); Self::open(workspace, action.separate_history, cx);
return; return;

View file

@ -6,7 +6,7 @@ use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::FS_WATCH_LATENCY; use project::FS_WATCH_LATENCY;
use serde_json::json; use serde_json::json;
use workspace::{AppState, Workspace}; use workspace::{AppState, ToggleFileFinder, Workspace};
#[ctor::ctor] #[ctor::ctor]
fn init_logger() { 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; let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
for expected_selected_index in 0..current_history.len() { 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 picker = active_file_picker(&workspace, cx);
let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index()); let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
assert_eq!( 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| { let selected_index = workspace.update(cx, |workspace, cx| {
workspace workspace
.active_modal::<FileFinder>(cx) .active_modal::<FileFinder>(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_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
open_queried_buffer("main", 1, "main.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); let picker = active_file_picker(&workspace, cx);
// main.rs is on top, previously used is selected // main.rs is on top, previously used is selected
picker.update(cx, |finder, _| { 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 // Back to navigation with initial shortcut
// Open file on modifiers release // Open file on modifiers release
cx.simulate_modifiers_change(Modifiers::secondary_key()); cx.simulate_modifiers_change(Modifiers::secondary_key());
cx.dispatch_action(Toggle::default()); cx.dispatch_action(ToggleFileFinder::default());
cx.simulate_modifiers_change(Modifiers::none()); cx.simulate_modifiers_change(Modifiers::none());
cx.read(|cx| { cx.read(|cx| {
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap(); let active_editor = workspace.read(cx).active_item_as::<Editor>(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 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)); 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); let picker = active_file_picker(&workspace, cx);
picker.update(cx, |picker, _| { picker.update(cx, |picker, _| {
assert_eq!(picker.delegate.selected_index, 0); 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. // When toggling repeatedly, the picker scrolls to reveal the selected item.
cx.dispatch_action(Toggle::default()); cx.dispatch_action(ToggleFileFinder::default());
cx.dispatch_action(Toggle::default()); cx.dispatch_action(ToggleFileFinder::default());
cx.dispatch_action(Toggle::default()); cx.dispatch_action(ToggleFileFinder::default());
picker.update(cx, |picker, _| { picker.update(cx, |picker, _| {
assert_eq!(picker.delegate.selected_index, 3); assert_eq!(picker.delegate.selected_index, 3);
assert_eq!(picker.logical_scroll_top_index(), 3); assert_eq!(picker.logical_scroll_top_index(), 3);
@ -1886,7 +1886,7 @@ fn open_file_picker(
workspace: &View<Workspace>, workspace: &View<Workspace>,
cx: &mut VisualTestContext, cx: &mut VisualTestContext,
) -> View<Picker<FileFinderDelegate>> { ) -> View<Picker<FileFinderDelegate>> {
cx.dispatch_action(Toggle { cx.dispatch_action(ToggleFileFinder {
separate_history: true, separate_history: true,
}); });
active_file_picker(workspace, cx) active_file_picker(workspace, cx)

View file

@ -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,
)
}),
) )
}) })
} }

View file

@ -3,7 +3,7 @@ pub mod cursor_position;
use cursor_position::LineIndicatorFormat; use cursor_position::LineIndicatorFormat;
use editor::{scroll::Autoscroll, Editor}; use editor::{scroll::Autoscroll, Editor};
use gpui::{ 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, FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
}; };
use settings::Settings; use settings::Settings;
@ -13,8 +13,6 @@ use ui::{h_flex, prelude::*, v_flex, Label};
use util::paths::FILE_ROW_COLUMN_DELIMITER; use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::ModalView; use workspace::ModalView;
actions!(go_to_line, [Toggle]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
LineIndicatorFormat::register(cx); LineIndicatorFormat::register(cx);
cx.observe_new_views(GoToLine::register).detach(); cx.observe_new_views(GoToLine::register).detach();
@ -43,7 +41,7 @@ impl GoToLine {
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) { fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
let handle = cx.view().downgrade(); let handle = cx.view().downgrade();
editor editor
.register_action(move |_: &Toggle, cx| { .register_action(move |_: &editor::actions::ToggleGoToLine, cx| {
let Some(editor) = handle.upgrade() else { let Some(editor) = handle.upgrade() else {
return; return;
}; };
@ -341,7 +339,7 @@ mod tests {
workspace: &View<Workspace>, workspace: &View<Workspace>,
cx: &mut VisualTestContext, cx: &mut VisualTestContext,
) -> View<GoToLine> { ) -> View<GoToLine> {
cx.dispatch_action(Toggle); cx.dispatch_action(editor::actions::ToggleGoToLine);
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
workspace.active_modal::<GoToLine>(cx).unwrap().clone() workspace.active_modal::<GoToLine>(cx).unwrap().clone()
}) })

View file

@ -189,7 +189,7 @@ macro_rules! actions {
#[serde(crate = "gpui::private::serde")] #[serde(crate = "gpui::private::serde")]
pub struct $name; 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<dyn gpui::Action>> { fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self)) 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<dyn gpui::Action>> {
Ok(Box::new(Self))
}
);
gpui::register_action!($name);
};
}
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize /// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
#[macro_export] #[macro_export]
macro_rules! impl_actions { macro_rules! impl_actions {
($namespace:path, [ $($name:ident),* $(,)? ]) => { ($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<dyn gpui::Action>> { fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?)) Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(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<dyn gpui::Action>> {
Ok(std::boxed::Box::new(
gpui::private::serde_json::from_value::<Self>(value)?,
))
}
);
gpui::register_action!($name);
};
}
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! __impl_action { macro_rules! __impl_action {
($namespace:path, $name:ident, $build:item) => { ($namespace:path, $name:ident, $visual_name:tt, $build:item) => {
impl gpui::Action for $name { impl gpui::Action for $name {
fn name(&self) -> &'static str fn name(&self) -> &'static str
{ {
concat!( concat!(
stringify!($namespace), stringify!($namespace),
"::", "::",
stringify!($name), stringify!($visual_name),
) )
} }
@ -237,7 +295,7 @@ macro_rules! __impl_action {
concat!( concat!(
stringify!($namespace), stringify!($namespace),
"::", "::",
stringify!($name), stringify!($visual_name),
) )
} }

View file

@ -2157,6 +2157,12 @@ impl From<Percentage> for Radians {
#[repr(transparent)] #[repr(transparent)]
pub struct Pixels(pub f32); 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 { impl std::ops::Div for Pixels {
type Output = f32; type Output = f32;

View file

@ -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 fuzzy::StringMatch;
use gpui::{ use gpui::{
actions, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, HighlightStyle,
HighlightStyle, ParentElement, Point, Render, Styled, Task, View, ViewContext, VisualContext, ParentElement, Point, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
WeakView, WindowContext, WindowContext,
}; };
use language::Outline; use language::Outline;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
@ -18,13 +20,11 @@ use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt; use util::ResultExt;
use workspace::{DismissDecision, ModalView}; use workspace::{DismissDecision, ModalView};
actions!(outline, [Toggle]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.observe_new_views(OutlineView::register).detach(); cx.observe_new_views(OutlineView::register).detach();
} }
pub fn toggle(editor: View<Editor>, _: &Toggle, cx: &mut WindowContext) { pub fn toggle(editor: View<Editor>, _: &ToggleOutline, cx: &mut WindowContext) {
let outline = editor let outline = editor
.read(cx) .read(cx)
.buffer() .buffer()
@ -423,7 +423,7 @@ mod tests {
workspace: &View<Workspace>, workspace: &View<Workspace>,
cx: &mut VisualTestContext, cx: &mut VisualTestContext,
) -> View<Picker<OutlineViewDelegate>> { ) -> View<Picker<OutlineViewDelegate>> {
cx.dispatch_action(Toggle); cx.dispatch_action(ToggleOutline);
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
workspace workspace
.active_modal::<OutlineView>(cx) .active_modal::<OutlineView>(cx)

View file

@ -1,8 +1,8 @@
use editor::{scroll::Autoscroll, styled_runs_for_code_label, Bias, Editor}; use editor::{scroll::Autoscroll, styled_runs_for_code_label, Bias, Editor};
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task, View,
View, ViewContext, WeakView, WindowContext, ViewContext, WeakView, WindowContext,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
@ -15,12 +15,10 @@ use workspace::{
Workspace, Workspace,
}; };
actions!(project_symbols, [Toggle]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.observe_new_views( cx.observe_new_views(
|workspace: &mut Workspace, _: &mut ViewContext<Workspace>| { |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &Toggle, cx| { workspace.register_action(|workspace, _: &workspace::ToggleProjectSymbols, cx| {
let project = workspace.project().clone(); let project = workspace.project().clone();
let handle = cx.view().downgrade(); let handle = cx.view().downgrade();
workspace.toggle_modal(cx, move |cx| { workspace.toggle_modal(cx, move |cx| {

View file

@ -1,5 +1,10 @@
use assistant::assistant_settings::AssistantSettings; use assistant::assistant_settings::AssistantSettings;
use assistant::{AssistantPanel, InlineAssist}; 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 editor::{Editor, EditorSettings};
use gpui::{ use gpui::{
@ -18,6 +23,7 @@ use workspace::{
pub struct QuickActionBar { pub struct QuickActionBar {
buffer_search_bar: View<BufferSearchBar>, buffer_search_bar: View<BufferSearchBar>,
toggle_settings_menu: Option<View<ContextMenu>>, toggle_settings_menu: Option<View<ContextMenu>>,
toggle_selections_menu: Option<View<ContextMenu>>,
active_item: Option<Box<dyn ItemHandle>>, active_item: Option<Box<dyn ItemHandle>>,
_inlay_hints_enabled_subscription: Option<Subscription>, _inlay_hints_enabled_subscription: Option<Subscription>,
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
@ -33,6 +39,7 @@ impl QuickActionBar {
let mut this = Self { let mut this = Self {
buffer_search_bar, buffer_search_bar,
toggle_settings_menu: None, toggle_settings_menu: None,
toggle_selections_menu: None,
active_item: None, active_item: None,
_inlay_hints_enabled_subscription: None, _inlay_hints_enabled_subscription: None,
workspace: workspace.weak_handle(), workspace: workspace.weak_handle(),
@ -86,22 +93,43 @@ impl Render for QuickActionBar {
return div().id("empty quick action bar"); return div().id("empty quick action bar");
}; };
let search_button = Some(QuickActionBarButton::new( let (
"toggle buffer search", selection_menu_enabled,
IconName::MagnifyingGlass, inlay_hints_enabled,
!self.buffer_search_bar.read(cx).is_dismissed(), supports_inlay_hints,
Box::new(buffer_search::Deploy::find()), git_blame_inline_enabled,
"Buffer Search", ) = {
{ let editor = editor.read(cx);
let buffer_search_bar = self.buffer_search_bar.clone(); let selection_menu_enabled = editor.selection_menu_enabled(cx);
move |_, cx| { let inlay_hints_enabled = editor.inlay_hints_enabled();
buffer_search_bar.update(cx, |search_bar, cx| { let supports_inlay_hints = editor.supports_inlay_hints(cx);
search_bar.toggle(&buffer_search::Deploy::find(), cx) let git_blame_inline_enabled = editor.git_blame_inline_enabled();
});
} (
}, selection_menu_enabled,
)) inlay_hints_enabled,
.filter(|_| editor.is_singleton(cx)); 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( let assistant_button = QuickActionBarButton::new(
"toggle inline assistant", "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 = let editor_settings_dropdown =
IconButton::new("toggle_editor_settings_icon", IconName::Sliders) IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
.size(ButtonSize::Compact) .size(ButtonSize::Compact)
@ -130,10 +207,6 @@ impl Render for QuickActionBar {
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();
cx.listener(move |quick_action_bar, _, cx| { 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, _| { let menu = ContextMenu::build(cx, |mut menu, _| {
if supports_inlay_hints { if supports_inlay_hints {
menu = menu.toggleable_entry( 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 menu
}); });
cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| { cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
@ -191,6 +281,7 @@ impl Render for QuickActionBar {
h_flex() h_flex()
.gap_1p5() .gap_1p5()
.children(search_button) .children(search_button)
.children(editor_selections_dropdown)
.when(AssistantSettings::get_global(cx).button, |bar| { .when(AssistantSettings::get_global(cx).button, |bar| {
bar.child(assistant_button) bar.child(assistant_button)
}), }),
@ -202,6 +293,12 @@ impl Render for QuickActionBar {
el.child(Self::render_menu_overlay(toggle_settings_menu)) 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))
},
)
} }
} }

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use gpui::{ use gpui::{
px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription, px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription,
ViewContext, ViewContext, WindowContext,
}; };
use refineable::Refineable; use refineable::Refineable;
use schemars::{ use schemars::{
@ -167,6 +167,11 @@ pub(crate) struct AdjustedBufferFontSize(Pixels);
impl Global for AdjustedBufferFontSize {} impl Global for AdjustedBufferFontSize {}
#[derive(Default)]
pub(crate) struct AdjustedUiFontSize(Pixels);
impl Global for AdjustedUiFontSize {}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)] #[serde(untagged)]
pub enum ThemeSelection { pub enum ThemeSelection {
@ -358,7 +363,13 @@ pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
.max(MIN_FONT_SIZE) .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::<AdjustedBufferFontSize>()
.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 buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
let mut adjusted_size = cx let mut adjusted_size = cx
.try_global::<AdjustedBufferFontSize>() .try_global::<AdjustedBufferFontSize>()
@ -370,13 +381,49 @@ pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
cx.refresh(); cx.refresh();
} }
pub fn reset_font_size(cx: &mut AppContext) { pub fn reset_buffer_font_size(cx: &mut AppContext) {
if cx.has_global::<AdjustedBufferFontSize>() { if cx.has_global::<AdjustedBufferFontSize>() {
cx.remove_global::<AdjustedBufferFontSize>(); cx.remove_global::<AdjustedBufferFontSize>();
cx.refresh(); 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::<AdjustedUiFontSize>()
.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::<AdjustedUiFontSize>()
.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::<AdjustedUiFontSize>() {
cx.remove_global::<AdjustedUiFontSize>();
cx.refresh();
}
}
impl settings::Settings for ThemeSettings { impl settings::Settings for ThemeSettings {
const KEY: Option<&'static str> = None; const KEY: Option<&'static str> = None;

View file

@ -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; let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
if buffer_font_size != prev_buffer_font_size { if buffer_font_size != prev_buffer_font_size {
prev_buffer_font_size = buffer_font_size; prev_buffer_font_size = buffer_font_size;
reset_font_size(cx); reset_buffer_font_size(cx);
} }
}) })
.detach(); .detach();

View file

@ -18,12 +18,13 @@ enum ContextMenuItem {
toggled: Option<bool>, toggled: Option<bool>,
label: SharedString, label: SharedString,
icon: Option<IconName>, icon: Option<IconName>,
handler: Rc<dyn Fn(&mut WindowContext)>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut WindowContext)>,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
}, },
CustomEntry { CustomEntry {
entry_render: Box<dyn Fn(&mut WindowContext) -> AnyElement>, entry_render: Box<dyn Fn(&mut WindowContext) -> AnyElement>,
handler: Rc<dyn Fn(&mut WindowContext)>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut WindowContext)>,
selectable: bool,
}, },
} }
@ -97,7 +98,7 @@ impl ContextMenu {
self.items.push(ContextMenuItem::Entry { self.items.push(ContextMenuItem::Entry {
toggled: None, toggled: None,
label: label.into(), label: label.into(),
handler: Rc::new(handler), handler: Rc::new(move |_, cx| handler(cx)),
icon: None, icon: None,
action, action,
}); });
@ -114,13 +115,25 @@ impl ContextMenu {
self.items.push(ContextMenuItem::Entry { self.items.push(ContextMenuItem::Entry {
toggled: Some(toggled), toggled: Some(toggled),
label: label.into(), label: label.into(),
handler: Rc::new(handler), handler: Rc::new(move |_, cx| handler(cx)),
icon: None, icon: None,
action, action,
}); });
self 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( pub fn custom_entry(
mut self, mut self,
entry_render: impl Fn(&mut WindowContext) -> AnyElement + 'static, entry_render: impl Fn(&mut WindowContext) -> AnyElement + 'static,
@ -128,7 +141,8 @@ impl ContextMenu {
) -> Self { ) -> Self {
self.items.push(ContextMenuItem::CustomEntry { self.items.push(ContextMenuItem::CustomEntry {
entry_render: Box::new(entry_render), entry_render: Box::new(entry_render),
handler: Rc::new(handler), handler: Rc::new(move |_, cx| handler(cx)),
selectable: true,
}); });
self self
} }
@ -138,7 +152,13 @@ impl ContextMenu {
toggled: None, toggled: None,
label: label.into(), label: label.into(),
action: Some(action.boxed_clone()), 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, icon: None,
}); });
self self
@ -148,19 +168,21 @@ impl ContextMenu {
self.items.push(ContextMenuItem::Entry { self.items.push(ContextMenuItem::Entry {
toggled: None, toggled: None,
label: label.into(), label: label.into(),
action: Some(action.boxed_clone()), 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), icon: Some(IconName::Link),
}); });
self self
} }
pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) { pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
let context = self.action_context.as_ref();
match self.selected_index.and_then(|ix| self.items.get(ix)) { match self.selected_index.and_then(|ix| self.items.get(ix)) {
Some( Some(
ContextMenuItem::Entry { handler, .. } ContextMenuItem::Entry { handler, .. }
| ContextMenuItem::CustomEntry { handler, .. }, | ContextMenuItem::CustomEntry { handler, .. },
) => (handler)(cx), ) => (handler)(context, cx),
_ => {} _ => {}
} }
@ -260,7 +282,12 @@ impl ContextMenu {
impl ContextMenuItem { impl ContextMenuItem {
fn is_selectable(&self) -> bool { 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)) .map(|binding| div().ml_4().child(binding))
})), })),
) )
.on_click(move |_, cx| { .on_click({
handler(cx); let context = self.action_context.clone();
menu.update(cx, |menu, cx| { move |_, cx| {
menu.clicked = true; handler(context.as_ref(), cx);
cx.emit(DismissEvent); menu.update(cx, |menu, cx| {
}) menu.clicked = true;
.ok(); cx.emit(DismissEvent);
})
.ok();
}
}) })
.into_any_element() .into_any_element()
} }
ContextMenuItem::CustomEntry { ContextMenuItem::CustomEntry {
entry_render, entry_render,
handler, handler,
selectable,
} => { } => {
let handler = handler.clone(); let handler = handler.clone();
let menu = cx.view().downgrade(); let menu = cx.view().downgrade();
ListItem::new(ix) ListItem::new(ix)
.inset(true) .inset(true)
.selected(Some(ix) == self.selected_index) .selected(if *selectable {
.on_click(move |_, cx| { Some(ix) == self.selected_index
handler(cx); } else {
menu.update(cx, |menu, cx| { false
menu.clicked = true; })
cx.emit(DismissEvent); .selectable(*selectable)
}) .on_click({
.ok(); 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)) .child(entry_render(cx))
.into_any_element() .into_any_element()

View file

@ -174,6 +174,7 @@ pub enum IconName {
Rerun, Rerun,
Return, Return,
Reveal, Reveal,
RotateCcw,
RotateCw, RotateCw,
Save, Save,
Screen, Screen,
@ -199,6 +200,7 @@ pub enum IconName {
SupermavenInit, SupermavenInit,
Tab, Tab,
Terminal, Terminal,
TextCursor,
Trash, Trash,
TriangleRight, TriangleRight,
Update, Update,
@ -307,6 +309,7 @@ impl IconName {
IconName::Rerun => "icons/rerun.svg", IconName::Rerun => "icons/rerun.svg",
IconName::Return => "icons/return.svg", IconName::Return => "icons/return.svg",
IconName::RotateCw => "icons/rotate_cw.svg", IconName::RotateCw => "icons/rotate_cw.svg",
IconName::RotateCcw => "icons/rotate_ccw.svg",
IconName::Save => "icons/save.svg", IconName::Save => "icons/save.svg",
IconName::Screen => "icons/desktop.svg", IconName::Screen => "icons/desktop.svg",
IconName::SelectAll => "icons/select_all.svg", IconName::SelectAll => "icons/select_all.svg",
@ -331,6 +334,7 @@ impl IconName {
IconName::SupermavenInit => "icons/supermaven_init.svg", IconName::SupermavenInit => "icons/supermaven_init.svg",
IconName::Tab => "icons/tab.svg", IconName::Tab => "icons/tab.svg",
IconName::Terminal => "icons/terminal.svg", IconName::Terminal => "icons/terminal.svg",
IconName::TextCursor => "icons/text-cursor.svg",
IconName::Trash => "icons/trash.svg", IconName::Trash => "icons/trash.svg",
IconName::TriangleRight => "icons/triangle_right.svg", IconName::TriangleRight => "icons/triangle_right.svg",
IconName::Update => "icons/update.svg", IconName::Update => "icons/update.svg",

View file

@ -35,6 +35,7 @@ pub struct ListItem {
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>, tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>, on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>, children: SmallVec<[AnyElement; 2]>,
selectable: bool,
} }
impl ListItem { impl ListItem {
@ -56,6 +57,7 @@ impl ListItem {
on_toggle: None, on_toggle: None,
tooltip: None, tooltip: None,
children: SmallVec::new(), children: SmallVec::new(),
selectable: true,
} }
} }
@ -64,6 +66,11 @@ impl ListItem {
self 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 { pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
@ -164,10 +171,12 @@ impl RenderOnce for ListItem {
// this.border_1() // this.border_1()
// .border_color(cx.theme().colors().border_focused) // .border_color(cx.theme().colors().border_focused)
// }) // })
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .when(self.selectable, |this| {
.active(|style| style.bg(cx.theme().colors().ghost_element_active)) this.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.when(self.selected, |this| { .active(|style| style.bg(cx.theme().colors().ghost_element_active))
this.bg(cx.theme().colors().ghost_element_selected) .when(self.selected, |this| {
this.bg(cx.theme().colors().ghost_element_selected)
})
}) })
}) })
.child( .child(
@ -189,10 +198,14 @@ impl RenderOnce for ListItem {
// this.border_1() // this.border_1()
// .border_color(cx.theme().colors().border_focused) // .border_color(cx.theme().colors().border_focused)
// }) // })
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .when(self.selectable, |this| {
.active(|style| style.bg(cx.theme().colors().ghost_element_active)) this.hover(|style| {
.when(self.selected, |this| { style.bg(cx.theme().colors().ghost_element_hover)
this.bg(cx.theme().colors().ghost_element_selected) })
.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| { .when_some(self.on_click, |this, on_click| {

View file

@ -96,7 +96,9 @@ impl ModalLayer {
previous_focus_handle: cx.focused(), previous_focus_handle: cx.focused(),
focus_handle, focus_handle,
}); });
cx.focus_view(&new_modal); cx.defer(move |_, cx| {
cx.focus_view(&new_modal);
});
cx.notify(); cx.notify();
} }

View file

@ -5,8 +5,8 @@ use crate::{
}, },
toolbar::Toolbar, toolbar::Toolbar,
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings}, workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
CloseWindow, NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible, CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenTerminal, OpenVisible, SplitDirection,
SplitDirection, ToggleZoom, Workspace, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
}; };
use anyhow::Result; use anyhow::Result;
use collections::{BTreeSet, HashMap, HashSet, VecDeque}; use collections::{BTreeSet, HashMap, HashSet, VecDeque};
@ -366,8 +366,24 @@ impl Pane {
.on_click(cx.listener(|pane, _, cx| { .on_click(cx.listener(|pane, _, cx| {
let menu = ContextMenu::build(cx, |menu, _| { let menu = ContextMenu::build(cx, |menu, _| {
menu.action("New File", NewFile.boxed_clone()) menu.action("New File", NewFile.boxed_clone())
.action("New Terminal", NewCenterTerminal.boxed_clone()) .action(
.action("New Search", NewSearch.boxed_clone()) "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| { cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
pane.focus(cx); pane.focus(cx);
@ -1818,7 +1834,11 @@ impl Pane {
.track_scroll(self.tab_bar_scroll_handle.clone()) .track_scroll(self.tab_bar_scroll_handle.clone())
.when( .when(
self.display_nav_history_buttons.unwrap_or_default(), 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| { .when(self.has_focus(cx), |tab_bar| {
tab_bar.end_child({ tab_bar.end_child({

View file

@ -27,11 +27,11 @@ use futures::{
Future, FutureExt, StreamExt, Future, FutureExt, StreamExt,
}; };
use gpui::{ use gpui::{
actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView, action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size, Action,
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DragMoveEvent, Entity as _, EntityId, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds,
EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke, ManagedView, Model, DragMoveEvent, Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global,
ModelContext, PathPromptOptions, Point, PromptLevel, Render, Size, Subscription, Task, View, KeyContext, Keystroke, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel,
WeakView, WindowBounds, WindowHandle, WindowOptions, Render, Size, Subscription, Task, View, WeakView, WindowBounds, WindowHandle, WindowOptions,
}; };
use item::{ use item::{
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
@ -112,30 +112,30 @@ pub struct RemoveWorktreeFromProject(pub WorktreeId);
actions!( actions!(
workspace, workspace,
[ [
ActivateNextPane,
ActivatePreviousPane,
AddFolderToProject,
CloseAllDocks,
CloseWindow,
Feedback,
FollowNextCollaborator,
NewCenterTerminal,
NewFile,
NewSearch,
NewTerminal,
NewWindow,
Open, Open,
OpenInTerminal, OpenInTerminal,
NewFile, ReloadActiveItem,
NewWindow,
CloseWindow,
AddFolderToProject,
Unfollow,
SaveAs, SaveAs,
SaveWithoutFormat, SaveWithoutFormat,
ReloadActiveItem,
ActivatePreviousPane,
ActivateNextPane,
FollowNextCollaborator,
NewTerminal,
NewCenterTerminal,
NewSearch,
Feedback,
Welcome,
ToggleZoom,
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock, ToggleBottomDock,
ToggleCenteredLayout, ToggleCenteredLayout,
CloseAllDocks, ToggleLeftDock,
ToggleRightDock,
ToggleZoom,
Unfollow,
Welcome,
] ]
); );
@ -188,6 +188,16 @@ pub struct Reload {
pub binary_path: Option<PathBuf>, pub binary_path: Option<PathBuf>,
} }
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!( impl_actions!(
workspace, workspace,
[ [
@ -4144,14 +4154,10 @@ impl Render for Workspace {
} else { } else {
(None, None) (None, None)
}; };
let (ui_font, ui_font_size) = { let ui_font = theme::setup_ui_font(cx);
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
let theme = cx.theme().clone(); let theme = cx.theme().clone();
let colors = theme.colors(); let colors = theme.colors();
cx.set_rem_size(ui_font_size);
self.actions(div(), cx) self.actions(div(), cx)
.key_context(context) .key_context(context)

View file

@ -52,22 +52,15 @@ use zed_actions::{OpenBrowser, OpenSettings, OpenZedUrl, Quit};
actions!( actions!(
zed, zed,
[ [
About,
DebugElements, DebugElements,
DecreaseBufferFontSize,
Hide, Hide,
HideOthers, HideOthers,
IncreaseBufferFontSize,
Minimize, Minimize,
OpenDefaultKeymap, OpenDefaultKeymap,
OpenDefaultSettings, OpenDefaultSettings,
OpenKeymap,
OpenLicenses,
OpenLocalSettings, OpenLocalSettings,
OpenLocalTasks, OpenLocalTasks,
OpenTasks, OpenTasks,
OpenTelemetryLog,
ResetBufferFontSize,
ResetDatabase, ResetDatabase,
ShowAll, ShowAll,
ToggleFullScreen, ToggleFullScreen,
@ -252,13 +245,33 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
OpenListener::global(cx).open_urls(vec![action.url.clone()]) OpenListener::global(cx).open_urls(vec![action.url.clone()])
}) })
.register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url)) .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url))
.register_action(move |_, _: &IncreaseBufferFontSize, cx| { .register_action(move |_, _: &zed_actions::IncreaseBufferFontSize, cx| {
theme::adjust_font_size(cx, |size| *size += px(1.0)) theme::adjust_buffer_font_size(cx, |size| *size += px(1.0))
}) })
.register_action(move |_, _: &DecreaseBufferFontSize, cx| { .register_action(move |_, _: &zed_actions::DecreaseBufferFontSize, cx| {
theme::adjust_font_size(cx, |size| *size -= px(1.0)) 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| { .register_action(|_, _: &install_cli::Install, cx| {
cx.spawn(|workspace, mut cx| async move { cx.spawn(|workspace, mut cx| async move {
if cfg!(target_os = "linux") { if cfg!(target_os = "linux") {
@ -323,7 +336,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
.register_action(|workspace, _: &OpenLog, cx| { .register_action(|workspace, _: &OpenLog, cx| {
open_log_file(workspace, cx); open_log_file(workspace, cx);
}) })
.register_action(|workspace, _: &OpenLicenses, cx| { .register_action(|workspace, _: &zed_actions::OpenLicenses, cx| {
open_bundled_file( open_bundled_file(
workspace, workspace,
asset_str::<Assets>("licenses.md"), asset_str::<Assets>("licenses.md"),
@ -334,14 +347,16 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
}) })
.register_action( .register_action(
move |workspace: &mut Workspace, move |workspace: &mut Workspace,
_: &OpenTelemetryLog, _: &zed_actions::OpenTelemetryLog,
cx: &mut ViewContext<Workspace>| { cx: &mut ViewContext<Workspace>| {
open_telemetry_log_file(workspace, cx); open_telemetry_log_file(workspace, cx);
}, },
) )
.register_action( .register_action(
move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| { move |_: &mut Workspace,
open_settings_file(paths::keymap_file(), Rope::default, cx); _: &zed_actions::OpenKeymap,
cx: &mut ViewContext<Workspace>| {
open_settings_file(&paths::keymap_file(), Rope::default, cx);
}, },
) )
.register_action( .register_action(
@ -485,7 +500,7 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewCo
}); });
} }
fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) { fn about(_: &mut Workspace, _: &zed_actions::About, cx: &mut gpui::ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx).display_name(); let release_channel = ReleaseChannel::global(cx).display_name();
let version = env!("CARGO_PKG_VERSION"); let version = env!("CARGO_PKG_VERSION");
let message = format!("{release_channel} {version}"); let message = format!("{release_channel} {version}");

View file

@ -9,14 +9,14 @@ pub fn app_menus() -> Vec<Menu<'static>> {
Menu { Menu {
name: "Zed", name: "Zed",
items: vec![ items: vec![
MenuItem::action("About Zed…", super::About), MenuItem::action("About Zed…", zed_actions::About),
MenuItem::action("Check for Updates", auto_update::Check), MenuItem::action("Check for Updates", auto_update::Check),
MenuItem::separator(), MenuItem::separator(),
MenuItem::submenu(Menu { MenuItem::submenu(Menu {
name: "Preferences", name: "Preferences",
items: vec![ items: vec![
MenuItem::action("Open Settings", super::OpenSettings), 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 Settings", super::OpenDefaultSettings),
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap), MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
MenuItem::action("Open Local Settings", super::OpenLocalSettings), MenuItem::action("Open Local Settings", super::OpenLocalSettings),
@ -104,9 +104,9 @@ pub fn app_menus() -> Vec<Menu<'static>> {
Menu { Menu {
name: "View", name: "View",
items: vec![ items: vec![
MenuItem::action("Zoom In", super::IncreaseBufferFontSize), MenuItem::action("Zoom In", zed_actions::IncreaseBufferFontSize),
MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), MenuItem::action("Zoom Out", zed_actions::DecreaseBufferFontSize),
MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::action("Reset Zoom", zed_actions::ResetBufferFontSize),
MenuItem::separator(), MenuItem::separator(),
MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock),
MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock),
@ -139,10 +139,10 @@ pub fn app_menus() -> Vec<Menu<'static>> {
MenuItem::separator(), MenuItem::separator(),
MenuItem::action("Command Palette...", command_palette::Toggle), MenuItem::action("Command Palette...", command_palette::Toggle),
MenuItem::separator(), 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 Project", project_symbols::Toggle),
MenuItem::action("Go to Symbol in Editor...", outline::Toggle), MenuItem::action("Go to Symbol in Editor...", editor::actions::ToggleOutline),
MenuItem::action("Go to Line/Column...", go_to_line::Toggle), MenuItem::action("Go to Line/Column...", editor::actions::ToggleGoToLine),
MenuItem::separator(), MenuItem::separator(),
MenuItem::action("Go to Definition", editor::actions::GoToDefinition), MenuItem::action("Go to Definition", editor::actions::GoToDefinition),
MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition), MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition),
@ -163,8 +163,8 @@ pub fn app_menus() -> Vec<Menu<'static>> {
Menu { Menu {
name: "Help", name: "Help",
items: vec![ items: vec![
MenuItem::action("View Telemetry", super::OpenTelemetryLog), MenuItem::action("View Telemetry", zed_actions::OpenTelemetryLog),
MenuItem::action("View Dependency Licenses", super::OpenLicenses), MenuItem::action("View Dependency Licenses", zed_actions::OpenLicenses),
MenuItem::action("Show Welcome", workspace::Welcome), MenuItem::action("Show Welcome", workspace::Welcome),
MenuItem::action("Give Feedback...", feedback::GiveFeedback), MenuItem::action("Give Feedback...", feedback::GiveFeedback),
MenuItem::separator(), MenuItem::separator(),

View file

@ -22,4 +22,20 @@ pub struct OpenZedUrl {
impl_actions!(zed, [OpenBrowser, OpenZedUrl]); impl_actions!(zed, [OpenBrowser, OpenZedUrl]);
actions!(zed, [OpenSettings, Quit]); actions!(
zed,
[
OpenSettings,
Quit,
OpenKeymap,
About,
OpenLicenses,
OpenTelemetryLog,
DecreaseBufferFontSize,
IncreaseBufferFontSize,
ResetBufferFontSize,
DecreaseUiFontSize,
IncreaseUiFontSize,
ResetUiFontSize
]
);