From ff6ac60bad635b4e9b0c46bfb6575de1f4326948 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 4 Jun 2025 20:29:08 +0300 Subject: [PATCH] Allow running certain Zed actions when headless (#32095) Rework of https://github.com/zed-industries/zed/pull/30783 Before: before_1 before_2 before_3 After: after_1 after_2 after_3 Release Notes: - Allowed running certain Zed actions when headless --- assets/keymaps/default-linux.json | 4 +- assets/keymaps/default-macos.json | 2 +- crates/recent_projects/src/recent_projects.rs | 53 ++--- crates/recent_projects/src/remote_servers.rs | 17 -- crates/settings_ui/src/settings_ui.rs | 16 +- crates/theme_selector/src/theme_selector.rs | 22 ++- crates/workspace/src/workspace.rs | 27 +++ crates/zed/src/zed.rs | 186 +++++++++--------- crates/zed/src/zed/app_menus.rs | 2 +- 9 files changed, 178 insertions(+), 151 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index dd9610d4a184d18acda00d3f971bf3cb59c3a6cc..f7dd30012b785e1e1a36868c7f30ab4d8e475fab 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -514,8 +514,8 @@ "bindings": { // Change the default action on `menu::Confirm` by setting the parameter // "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }], - "alt-open": "projects::OpenRecent", - "alt-ctrl-o": "projects::OpenRecent", + "alt-open": ["projects::OpenRecent", { "create_new_window": false }], + "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": false }], "alt-shift-open": "projects::OpenRemote", "alt-ctrl-shift-o": "projects::OpenRemote", // Change to open path modal for existing remote connection by setting the parameter diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 4dfce63b46e14dc89e215a08fc0cacea86fcec80..8e3e895d11e06232236503a2abeb4b19b29a547a 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -584,7 +584,7 @@ "bindings": { // Change the default action on `menu::Confirm` by setting the parameter // "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }], - "alt-cmd-o": "projects::OpenRecent", + "alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }], "ctrl-cmd-o": "projects::OpenRemote", "ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true }], "alt-cmd-b": "branches::OpenRecent", diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 60f7b38d56355fbdda48941048485a7017274a22..1e5361e1e6162eaff78c0ffa088da82836759376 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -27,14 +27,42 @@ use ui::{KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*, tooltip_con use util::{ResultExt, paths::PathExt}; use workspace::{ CloseIntent, HistoryManager, ModalView, OpenOptions, SerializedWorkspaceLocation, WORKSPACE_DB, - Workspace, WorkspaceId, + Workspace, WorkspaceId, with_active_or_new_workspace, }; use zed_actions::{OpenRecent, OpenRemote}; pub fn init(cx: &mut App) { SshSettings::register(cx); - cx.observe_new(RecentProjects::register).detach(); - cx.observe_new(RemoteServerProjects::register).detach(); + cx.on_action(|open_recent: &OpenRecent, cx| { + let create_new_window = open_recent.create_new_window; + with_active_or_new_workspace(cx, move |workspace, window, cx| { + let Some(recent_projects) = workspace.active_modal::(cx) else { + RecentProjects::open(workspace, create_new_window, window, cx); + return; + }; + + recent_projects.update(cx, |recent_projects, cx| { + recent_projects + .picker + .update(cx, |picker, cx| picker.cycle_selection(window, cx)) + }); + }); + }); + cx.on_action(|open_remote: &OpenRemote, cx| { + let from_existing_connection = open_remote.from_existing_connection; + with_active_or_new_workspace(cx, move |workspace, window, cx| { + if from_existing_connection { + cx.propagate(); + return; + } + let handle = cx.entity().downgrade(); + let fs = workspace.project().read(cx).fs().clone(); + workspace.toggle_modal(window, cx, |window, cx| { + RemoteServerProjects::new(fs, window, cx, handle) + }) + }); + }); + cx.observe_new(DisconnectedOverlay::register).detach(); } @@ -86,25 +114,6 @@ impl RecentProjects { } } - fn register( - workspace: &mut Workspace, - _window: Option<&mut Window>, - _cx: &mut Context, - ) { - workspace.register_action(|workspace, open_recent: &OpenRecent, window, cx| { - let Some(recent_projects) = workspace.active_modal::(cx) else { - Self::open(workspace, open_recent.create_new_window, window, cx); - return; - }; - - recent_projects.update(cx, |recent_projects, cx| { - recent_projects - .picker - .update(cx, |picker, cx| picker.cycle_selection(window, cx)) - }); - }); - } - pub fn open( workspace: &mut Workspace, create_new_window: bool, diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index f90db17fa8b31fd992c4c641ee7c36c185f2a340..b0ee050b795ff251d851c918f7964bfddf390760 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -50,7 +50,6 @@ use workspace::{ open_ssh_project_with_existing_connection, }; -use crate::OpenRemote; use crate::ssh_config::parse_ssh_config_hosts; use crate::ssh_connections::RemoteSettingsContent; use crate::ssh_connections::SshConnection; @@ -362,22 +361,6 @@ impl Mode { } } impl RemoteServerProjects { - pub fn register( - workspace: &mut Workspace, - _window: Option<&mut Window>, - _: &mut Context, - ) { - workspace.register_action(|workspace, action: &OpenRemote, window, cx| { - if action.from_existing_connection { - cx.propagate(); - return; - } - let handle = cx.entity().downgrade(); - let fs = workspace.project().read(cx).fs().clone(); - workspace.toggle_modal(window, cx, |window, cx| Self::new(fs, window, cx, handle)) - }); - } - pub fn open(workspace: Entity, window: &mut Window, cx: &mut App) { workspace.update(cx, |workspace, cx| { let handle = cx.entity().downgrade(); diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 58d0ce9147bbfbd01c53acb3d4b3b5d85472d1a5..b7bb4b77e7540c07b6f6afe6956290140e8fba2f 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -15,8 +15,8 @@ use schemars::JsonSchema; use serde::Deserialize; use settings::{SettingsStore, VsCodeSettingsSource}; use ui::prelude::*; -use workspace::Workspace; use workspace::item::{Item, ItemEvent}; +use workspace::{Workspace, with_active_or_new_workspace}; use crate::appearance_settings_controls::AppearanceSettingsControls; @@ -42,12 +42,8 @@ impl_actions!(zed, [ImportVsCodeSettings, ImportCursorSettings]); actions!(zed, [OpenSettingsEditor]); pub fn init(cx: &mut App) { - cx.observe_new(|workspace: &mut Workspace, window, cx| { - let Some(window) = window else { - return; - }; - - workspace.register_action(|workspace, _: &OpenSettingsEditor, window, cx| { + cx.on_action(|_: &OpenSettingsEditor, cx| { + with_active_or_new_workspace(cx, move |workspace, window, cx| { let existing = workspace .active_pane() .read(cx) @@ -61,6 +57,12 @@ pub fn init(cx: &mut App) { workspace.add_item_to_active_pane(Box::new(settings_page), None, true, window, cx) } }); + }); + + cx.observe_new(|workspace: &mut Workspace, window, cx| { + let Some(window) = window else { + return; + }; workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| { let fs = ::global(cx); diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 97acbc3359aeff572d9666aff6253627946f8302..e6cc08483fb4506c0a536748bb4d90717a87eb33 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use theme::{Appearance, Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; use ui::{ListItem, ListItemSpacing, prelude::*, v_flex}; use util::ResultExt; -use workspace::{ModalView, Workspace, ui::HighlightedLabel}; +use workspace::{ModalView, Workspace, ui::HighlightedLabel, with_active_or_new_workspace}; use zed_actions::{ExtensionCategoryFilter, Extensions}; use crate::icon_theme_selector::{IconThemeSelector, IconThemeSelectorDelegate}; @@ -20,14 +20,18 @@ use crate::icon_theme_selector::{IconThemeSelector, IconThemeSelectorDelegate}; actions!(theme_selector, [Reload]); pub fn init(cx: &mut App) { - cx.observe_new( - |workspace: &mut Workspace, _window, _cx: &mut Context| { - workspace - .register_action(toggle_theme_selector) - .register_action(toggle_icon_theme_selector); - }, - ) - .detach(); + cx.on_action(|action: &zed_actions::theme_selector::Toggle, cx| { + let action = action.clone(); + with_active_or_new_workspace(cx, move |workspace, window, cx| { + toggle_theme_selector(workspace, &action, window, cx); + }); + }); + cx.on_action(|action: &zed_actions::icon_theme_selector::Toggle, cx| { + let action = action.clone(); + with_active_or_new_workspace(cx, move |workspace, window, cx| { + toggle_icon_theme_selector(workspace, &action, window, cx); + }); + }); } fn toggle_theme_selector( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e16cf0038a13aa26c26e496661f83c82b118b214..b289078e33648d02bb61f8756060c184ac99bc8e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7642,6 +7642,33 @@ pub fn ssh_workspace_position_from_db( }) } +pub fn with_active_or_new_workspace( + cx: &mut App, + f: impl FnOnce(&mut Workspace, &mut Window, &mut Context) + Send + 'static, +) { + match cx.active_window().and_then(|w| w.downcast::()) { + Some(workspace) => { + cx.defer(move |cx| { + workspace + .update(cx, |workspace, window, cx| f(workspace, window, cx)) + .log_err(); + }); + } + None => { + let app_state = AppState::global(cx); + if let Some(app_state) = app_state.upgrade() { + open_new( + OpenOptions::default(), + app_state, + cx, + move |workspace, window, cx| f(workspace, window, cx), + ) + .detach_and_log_err(cx); + } + } + } +} + #[cfg(test)] mod tests { use std::{cell::RefCell, rc::Rc}; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 92b11507b33f4c27776286d19bc22cad1ec73e72..0c284661aa22e8844f1971276908b88b8882d9c8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -70,7 +70,7 @@ use workspace::{ create_and_open_local_file, notifications::simple_message_notification::MessageNotification, open_new, }; -use workspace::{CloseIntent, RestoreBanner}; +use workspace::{CloseIntent, CloseWindow, RestoreBanner, with_active_or_new_workspace}; use workspace::{Pane, notifications::DetachAndPromptErr}; use zed_actions::{ OpenAccountSettings, OpenBrowser, OpenDocs, OpenServerSettings, OpenSettings, OpenZedUrl, Quit, @@ -111,6 +111,98 @@ pub fn init(cx: &mut App) { if ReleaseChannel::global(cx) == ReleaseChannel::Dev { cx.on_action(test_panic); } + + cx.on_action(|_: &OpenLog, cx| { + with_active_or_new_workspace(cx, |workspace, window, cx| { + open_log_file(workspace, window, cx); + }); + }); + cx.on_action(|_: &zed_actions::OpenLicenses, cx| { + with_active_or_new_workspace(cx, |workspace, window, cx| { + open_bundled_file( + workspace, + asset_str::("licenses.md"), + "Open Source License Attribution", + "Markdown", + window, + cx, + ); + }); + }); + cx.on_action(|_: &zed_actions::OpenTelemetryLog, cx| { + with_active_or_new_workspace(cx, |workspace, window, cx| { + open_telemetry_log_file(workspace, window, cx); + }); + }); + cx.on_action(|&zed_actions::OpenKeymap, cx| { + with_active_or_new_workspace(cx, |_, window, cx| { + open_settings_file( + paths::keymap_file(), + || settings::initial_keymap_content().as_ref().into(), + window, + cx, + ); + }); + }); + cx.on_action(|_: &OpenSettings, cx| { + with_active_or_new_workspace(cx, |_, window, cx| { + open_settings_file( + paths::settings_file(), + || settings::initial_user_settings_content().as_ref().into(), + window, + cx, + ); + }); + }); + cx.on_action(|_: &OpenAccountSettings, cx| { + with_active_or_new_workspace(cx, |_, _, cx| { + cx.open_url(&zed_urls::account_url(cx)); + }); + }); + cx.on_action(|_: &OpenTasks, cx| { + with_active_or_new_workspace(cx, |_, window, cx| { + open_settings_file( + paths::tasks_file(), + || settings::initial_tasks_content().as_ref().into(), + window, + cx, + ); + }); + }); + cx.on_action(|_: &OpenDebugTasks, cx| { + with_active_or_new_workspace(cx, |_, window, cx| { + open_settings_file( + paths::debug_scenarios_file(), + || settings::initial_debug_tasks_content().as_ref().into(), + window, + cx, + ); + }); + }); + cx.on_action(|_: &OpenDefaultSettings, cx| { + with_active_or_new_workspace(cx, |workspace, window, cx| { + open_bundled_file( + workspace, + settings::default_settings(), + "Default Settings", + "JSON", + window, + cx, + ); + }); + }); + cx.on_action(|_: &zed_actions::OpenDefaultKeymap, cx| { + with_active_or_new_workspace(cx, |workspace, window, cx| { + open_bundled_file( + workspace, + settings::default_keymap(), + "Default Key Bindings", + "JSON", + window, + cx, + ); + }); + }); } fn bind_on_window_closed(cx: &mut App) -> Option { @@ -255,7 +347,7 @@ pub fn initialize_workspace( handle .update(cx, |workspace, cx| { // We'll handle closing asynchronously - workspace.close_window(&Default::default(), window, cx); + workspace.close_window(&CloseWindow, window, cx); false }) .unwrap_or(true) @@ -683,99 +775,9 @@ fn register_actions( |_, _, _| None, ); }) - .register_action(|workspace, _: &OpenLog, window, cx| { - open_log_file(workspace, window, cx); - }) - .register_action(|workspace, _: &zed_actions::OpenLicenses, window, cx| { - open_bundled_file( - workspace, - asset_str::("licenses.md"), - "Open Source License Attribution", - "Markdown", - window, - cx, - ); - }) - .register_action( - move |workspace: &mut Workspace, - _: &zed_actions::OpenTelemetryLog, - window: &mut Window, - cx: &mut Context| { - open_telemetry_log_file(workspace, window, cx); - }, - ) - .register_action( - move |_: &mut Workspace, _: &zed_actions::OpenKeymap, window, cx| { - open_settings_file( - paths::keymap_file(), - || settings::initial_keymap_content().as_ref().into(), - window, - cx, - ); - }, - ) - .register_action(move |_: &mut Workspace, _: &OpenSettings, window, cx| { - open_settings_file( - paths::settings_file(), - || settings::initial_user_settings_content().as_ref().into(), - window, - cx, - ); - }) - .register_action( - |_: &mut Workspace, _: &OpenAccountSettings, _: &mut Window, cx| { - cx.open_url(&zed_urls::account_url(cx)); - }, - ) - .register_action(move |_: &mut Workspace, _: &OpenTasks, window, cx| { - open_settings_file( - paths::tasks_file(), - || settings::initial_tasks_content().as_ref().into(), - window, - cx, - ); - }) - .register_action(move |_: &mut Workspace, _: &OpenDebugTasks, window, cx| { - open_settings_file( - paths::debug_scenarios_file(), - || settings::initial_debug_tasks_content().as_ref().into(), - window, - cx, - ); - }) - .register_action(move |_: &mut Workspace, _: &OpenDebugTasks, window, cx| { - open_settings_file( - paths::debug_scenarios_file(), - || settings::initial_debug_tasks_content().as_ref().into(), - window, - cx, - ); - }) .register_action(open_project_settings_file) .register_action(open_project_tasks_file) .register_action(open_project_debug_tasks_file) - .register_action( - move |workspace, _: &zed_actions::OpenDefaultKeymap, window, cx| { - open_bundled_file( - workspace, - settings::default_keymap(), - "Default Key Bindings", - "JSON", - window, - cx, - ); - }, - ) - .register_action(move |workspace, _: &OpenDefaultSettings, window, cx| { - open_bundled_file( - workspace, - settings::default_settings(), - "Default Settings", - "JSON", - window, - cx, - ); - }) .register_action( |workspace: &mut Workspace, _: &project_panel::ToggleFocus, diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 4c0077585da6f4a3fe6338caa047d1629c07c5f4..ec98bb912252681788d72d6b8c2b3ee4e7217261 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -67,7 +67,7 @@ pub fn app_menus() -> Vec { MenuItem::action( "Open Recent...", zed_actions::OpenRecent { - create_new_window: true, + create_new_window: false, }, ), MenuItem::action(