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",
"client",
"collections",
"command_palette",
"db",
"dev_server_projects",
"editor",
"emojis",
"extensions_ui",
"feedback",
"futures 0.3.28",
"fuzzy",
"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.
"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": {

View File

@ -832,13 +832,8 @@ impl PromptLibrary {
impl Render for PromptLibrary {
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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")

View File

@ -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.

View File

@ -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

View File

@ -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<foo>
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
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> {
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()
})

View File

@ -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<Self>) -> 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(

View File

@ -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<Self>) -> 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(

View File

@ -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);

View File

@ -537,6 +537,7 @@ pub struct Editor {
show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_enabled: bool,
show_selection_menu: Option<bool>,
blame: Option<Model<GitBlame>>,
blame_subscription: Option<Subscription>,
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>) {
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>) {
if let Some(project) = self.project.as_ref() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else {

View File

@ -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<bool>,
/// 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<bool>,
/// 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<bool>,
/// Whether to show the selections menu in the editor toolbar
///
/// Default: true
pub selections_menu: Option<bool>,
}
/// Scrollbar related settings

View File

@ -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>) {
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 {
Self::open(workspace, action.separate_history, cx);
return;

View File

@ -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::<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_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::<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 (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<Workspace>,
cx: &mut VisualTestContext,
) -> View<Picker<FileFinderDelegate>> {
cx.dispatch_action(Toggle {
cx.dispatch_action(ToggleFileFinder {
separate_history: true,
});
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 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<Editor>) {
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<Workspace>,
cx: &mut VisualTestContext,
) -> View<GoToLine> {
cx.dispatch_action(Toggle);
cx.dispatch_action(editor::actions::ToggleGoToLine);
workspace.update(cx, |workspace, cx| {
workspace.active_modal::<GoToLine>(cx).unwrap().clone()
})

View File

@ -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<dyn gpui::Action>> {
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
#[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<dyn gpui::Action>> {
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)]
#[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),
)
}

View File

@ -2157,6 +2157,12 @@ impl From<Percentage> 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;

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 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<Editor>, _: &Toggle, cx: &mut WindowContext) {
pub fn toggle(editor: View<Editor>, _: &ToggleOutline, cx: &mut WindowContext) {
let outline = editor
.read(cx)
.buffer()
@ -423,7 +423,7 @@ mod tests {
workspace: &View<Workspace>,
cx: &mut VisualTestContext,
) -> View<Picker<OutlineViewDelegate>> {
cx.dispatch_action(Toggle);
cx.dispatch_action(ToggleOutline);
workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<OutlineView>(cx)

View File

@ -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>| {
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| {

View File

@ -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<BufferSearchBar>,
toggle_settings_menu: Option<View<ContextMenu>>,
toggle_selections_menu: Option<View<ContextMenu>>,
active_item: Option<Box<dyn ItemHandle>>,
_inlay_hints_enabled_subscription: Option<Subscription>,
workspace: WeakView<Workspace>,
@ -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))
},
)
}
}

View File

@ -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::<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 mut adjusted_size = cx
.try_global::<AdjustedBufferFontSize>()
@ -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::<AdjustedBufferFontSize>() {
cx.remove_global::<AdjustedBufferFontSize>();
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 {
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;
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();

View File

@ -18,12 +18,13 @@ enum ContextMenuItem {
toggled: Option<bool>,
label: SharedString,
icon: Option<IconName>,
handler: Rc<dyn Fn(&mut WindowContext)>,
handler: Rc<dyn Fn(Option<&FocusHandle>, &mut WindowContext)>,
action: Option<Box<dyn Action>>,
},
CustomEntry {
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 {
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<Self>) {
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()

View File

@ -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",

View File

@ -35,6 +35,7 @@ pub struct ListItem {
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
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| {

View File

@ -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();
}

View File

@ -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({

View File

@ -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<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!(
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)

View File

@ -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<AppState>, 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<AppState>, 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::<Assets>("licenses.md"),
@ -334,14 +347,16 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
})
.register_action(
move |workspace: &mut Workspace,
_: &OpenTelemetryLog,
_: &zed_actions::OpenTelemetryLog,
cx: &mut ViewContext<Workspace>| {
open_telemetry_log_file(workspace, cx);
},
)
.register_action(
move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
open_settings_file(paths::keymap_file(), Rope::default, cx);
move |_: &mut Workspace,
_: &zed_actions::OpenKeymap,
cx: &mut ViewContext<Workspace>| {
open_settings_file(&paths::keymap_file(), Rope::default, cx);
},
)
.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 version = env!("CARGO_PKG_VERSION");
let message = format!("{release_channel} {version}");

View File

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

View File

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