From 9e31b1019e70242a8e9f6d1c1bff1582fe025bc1 Mon Sep 17 00:00:00 2001 From: Osvaldo Date: Wed, 29 Jan 2025 03:23:17 +0000 Subject: [PATCH 1/5] vim: Add any brackets to support motions like ab and ib to work with any type of brackets (#23679) # Add AnyBrackets text object for Vim mode ## Overview This PR introduces a new text object `AnyBrackets` that allows operations on the closest matching pair of brackets, regardless of the bracket type. This enhances the editing experience by reducing the need to identify specific bracket types before performing text operations. By default, this feature is NOT mapped to any key in vim.json. However, it can be enabled manually, and the recommended key for mapping is b: If you want to add it to your zed keymap config you need to add the following config: ```json { "context": "vim_operator == a || vim_operator == i || vim_operator == cs", "bindings": { "b": "vim::AnyBrackets" } } ``` ## Features - New text object that works with parentheses `()`, square brackets `[]`, curly braces `{}`, they are also know as round brackets, square brackets and curly brackets in english. - Automatically finds the closest matching pair of any bracket type - Works with all standard Vim operators (delete, change, yank) - Supports both "inside" and "around" variants (`i` and `a`) ## Usage Examples ```vim # Delete inside the closest brackets di( # Works on (), [] or {} depending on which is closest # Change around the closest brackets ca[ # Works on (), [] or {} depending on which is closest # Visual select inside the closest brackets vi{ # Works on (), [] or {} depending on which is closest ``` # References: - Based on the popular plugin https://github.com/echasnovski/mini.ai # Important Notes This PR also fixes a bug with nested quotes on AnyQuotes, now it works fine with any type of quotes or brackets. Please take a look at the new tests to understand the expected behavior. Release Notes: - vim: Add `ab`/`ib` "AnyBrackets" text objects that are the smallest of `a(`, `a[` or `a{` or `i(`, `i[` or `i{` - vim: Fix aq/iq "AnyQuotes" text objects when they are nested --- assets/keymaps/vim.json | 1 + crates/vim/src/object.rs | 239 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 230 insertions(+), 10 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index c45a71ff7e7fdf..7c3e8b66c08804 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -408,6 +408,7 @@ "(": "vim::Parentheses", ")": "vim::Parentheses", "b": "vim::Parentheses", + // "b": "vim::AnyBrackets", "[": "vim::SquareBrackets", "]": "vim::SquareBrackets", "r": "vim::SquareBrackets", diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index bb04117742f829..d59d59a26ea5c0 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -29,6 +29,7 @@ pub enum Object { AnyQuotes, DoubleQuotes, VerticalBars, + AnyBrackets, Parentheses, SquareBrackets, CurlyBrackets, @@ -74,6 +75,7 @@ actions!( DoubleQuotes, VerticalBars, Parentheses, + AnyBrackets, SquareBrackets, CurlyBrackets, AngleBrackets, @@ -115,6 +117,9 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &AnyQuotes, window, cx| { vim.object(Object::AnyQuotes, window, cx) }); + Vim::action(editor, cx, |vim, _: &AnyBrackets, window, cx| { + vim.object(Object::AnyBrackets, window, cx) + }); Vim::action(editor, cx, |vim, _: &DoubleQuotes, window, cx| { vim.object(Object::DoubleQuotes, window, cx) }); @@ -186,6 +191,7 @@ impl Object { | Object::DoubleQuotes => false, Object::Sentence | Object::Paragraph + | Object::AnyBrackets | Object::Parentheses | Object::Tag | Object::AngleBrackets @@ -212,6 +218,7 @@ impl Object { | Object::AnyQuotes | Object::DoubleQuotes | Object::VerticalBars + | Object::AnyBrackets | Object::Parentheses | Object::SquareBrackets | Object::Tag @@ -239,6 +246,7 @@ impl Object { } } Object::Parentheses + | Object::AnyBrackets | Object::SquareBrackets | Object::CurlyBrackets | Object::AngleBrackets @@ -306,16 +314,7 @@ impl Object { quote, ) }) - .min_by_key(|range| { - // Calculate proximity of ranges to the cursor - let start_distance = (relative_offset - - range.start.to_offset(map, Bias::Left) as isize) - .abs(); - let end_distance = (relative_offset - - range.end.to_offset(map, Bias::Right) as isize) - .abs(); - start_distance + end_distance - }) + .min_by_key(|range| calculate_range_distance(range, relative_offset, map)) } Object::DoubleQuotes => { surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"') @@ -331,6 +330,24 @@ impl Object { let range = selection.range(); surrounding_html_tag(map, head, range, around) } + Object::AnyBrackets => { + let bracket_pairs = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')]; + let relative_offset = relative_to.to_offset(map, Bias::Left) as isize; + + bracket_pairs + .iter() + .flat_map(|&(open_bracket, close_bracket)| { + surrounding_markers( + map, + relative_to, + around, + self.is_multiline(), + open_bracket, + close_bracket, + ) + }) + .min_by_key(|range| calculate_range_distance(range, relative_offset, map)) + } Object::SquareBrackets => { surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']') } @@ -584,6 +601,37 @@ fn around_word( } } +/// Calculate distance between a range and a cursor position +/// +/// Returns a score where: +/// - Lower values indicate better matches +/// - Range containing cursor gets priority (returns range length) +/// - For non-containing ranges, uses minimum distance to boundaries as primary factor +/// - Range length is used as secondary factor for tiebreaking +fn calculate_range_distance( + range: &Range, + cursor_offset: isize, + map: &DisplaySnapshot, +) -> isize { + let start_offset = range.start.to_offset(map, Bias::Left) as isize; + let end_offset = range.end.to_offset(map, Bias::Right) as isize; + let range_length = end_offset - start_offset; + + // If cursor is inside the range, return range length + if cursor_offset >= start_offset && cursor_offset <= end_offset { + return range_length; + } + + // Calculate minimum distance to range boundaries + let start_distance = (cursor_offset - start_offset).abs(); + let end_distance = (cursor_offset - end_offset).abs(); + let min_distance = start_distance.min(end_distance); + + // Use min_distance as primary factor, range_length as secondary + // Multiply by large number to ensure distance is primary factor + min_distance * 10000 + range_length +} + fn around_subword( map: &DisplaySnapshot, relative_to: DisplayPoint, @@ -1302,9 +1350,11 @@ fn surrounding_markers( #[cfg(test)] mod test { + use gpui::KeyBinding; use indoc::indoc; use crate::{ + object::AnyBrackets, state::Mode, test::{NeovimBackedTestContext, VimTestContext}, }; @@ -1914,6 +1964,30 @@ mod test { const TEST_CASES: &[(&str, &str, &str, Mode)] = &[ // Single quotes + ( + "c i q", + "Thisˇ is a 'quote' example.", + "This is a 'ˇ' example.", + Mode::Insert, + ), + ( + "c a q", + "Thisˇ is a 'quote' example.", + "This is a ˇexample.", + Mode::Insert, + ), + ( + "c i q", + "This is a \"simple 'qˇuote'\" example.", + "This is a \"simple 'ˇ'\" example.", + Mode::Insert, + ), + ( + "c a q", + "This is a \"simple 'qˇuote'\" example.", + "This is a \"simpleˇ\" example.", + Mode::Insert, + ), ( "c i q", "This is a 'qˇuote' example.", @@ -2022,6 +2096,151 @@ mod test { } } + #[gpui::test] + async fn test_anybrackets_object(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.update(|_, cx| { + cx.bind_keys([KeyBinding::new( + "b", + AnyBrackets, + Some("vim_operator == a || vim_operator == i || vim_operator == cs"), + )]); + }); + + const TEST_CASES: &[(&str, &str, &str, Mode)] = &[ + // Bracket (Parentheses) + ( + "c i b", + "Thisˇ is a (simple [quote]) example.", + "This is a (ˇ) example.", + Mode::Insert, + ), + ( + "c i b", + "This is a [simple (qˇuote)] example.", + "This is a [simple (ˇ)] example.", + Mode::Insert, + ), + ( + "c a b", + "This is a [simple (qˇuote)] example.", + "This is a [simple ˇ] example.", + Mode::Insert, + ), + ( + "c a b", + "Thisˇ is a (simple [quote]) example.", + "This is a ˇ example.", + Mode::Insert, + ), + ( + "c i b", + "This is a (qˇuote) example.", + "This is a (ˇ) example.", + Mode::Insert, + ), + ( + "c a b", + "This is a (qˇuote) example.", + "This is a ˇ example.", + Mode::Insert, + ), + ( + "d i b", + "This is a (qˇuote) example.", + "This is a (ˇ) example.", + Mode::Normal, + ), + ( + "d a b", + "This is a (qˇuote) example.", + "This is a ˇ example.", + Mode::Normal, + ), + // Square brackets + ( + "c i b", + "This is a [qˇuote] example.", + "This is a [ˇ] example.", + Mode::Insert, + ), + ( + "c a b", + "This is a [qˇuote] example.", + "This is a ˇ example.", + Mode::Insert, + ), + ( + "d i b", + "This is a [qˇuote] example.", + "This is a [ˇ] example.", + Mode::Normal, + ), + ( + "d a b", + "This is a [qˇuote] example.", + "This is a ˇ example.", + Mode::Normal, + ), + // Curly brackets + ( + "c i b", + "This is a {qˇuote} example.", + "This is a {ˇ} example.", + Mode::Insert, + ), + ( + "c a b", + "This is a {qˇuote} example.", + "This is a ˇ example.", + Mode::Insert, + ), + ( + "d i b", + "This is a {qˇuote} example.", + "This is a {ˇ} example.", + Mode::Normal, + ), + ( + "d a b", + "This is a {qˇuote} example.", + "This is a ˇ example.", + Mode::Normal, + ), + ]; + + for (keystrokes, initial_state, expected_state, expected_mode) in TEST_CASES { + cx.set_state(initial_state, Mode::Normal); + + cx.simulate_keystrokes(keystrokes); + + cx.assert_state(expected_state, *expected_mode); + } + + const INVALID_CASES: &[(&str, &str, Mode)] = &[ + ("c i b", "this is a (qˇuote example.", Mode::Normal), // Missing closing bracket + ("c a b", "this is a (qˇuote example.", Mode::Normal), // Missing closing bracket + ("d i b", "this is a (qˇuote example.", Mode::Normal), // Missing closing bracket + ("d a b", "this is a (qˇuote example.", Mode::Normal), // Missing closing bracket + ("c i b", "this is a [qˇuote example.", Mode::Normal), // Missing closing square bracket + ("c a b", "this is a [qˇuote example.", Mode::Normal), // Missing closing square bracket + ("d i b", "this is a [qˇuote example.", Mode::Normal), // Missing closing square bracket + ("d a b", "this is a [qˇuote example.", Mode::Normal), // Missing closing square bracket + ("c i b", "this is a {qˇuote example.", Mode::Normal), // Missing closing curly bracket + ("c a b", "this is a {qˇuote example.", Mode::Normal), // Missing closing curly bracket + ("d i b", "this is a {qˇuote example.", Mode::Normal), // Missing closing curly bracket + ("d a b", "this is a {qˇuote example.", Mode::Normal), // Missing closing curly bracket + ]; + + for (keystrokes, initial_state, mode) in INVALID_CASES { + cx.set_state(initial_state, Mode::Normal); + + cx.simulate_keystrokes(keystrokes); + + cx.assert_state(initial_state, *mode); + } + } + #[gpui::test] async fn test_tags(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new_html(cx).await; From 16004d4c6a4f1ecd3a68ad43438583f19d5edd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Wed, 29 Jan 2025 03:02:03 -0300 Subject: [PATCH 2/5] Fix deprecated alias for toggling hunks (#23818) Release Notes: - N/A --- crates/editor/src/actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 13f9d6f2318656..fe2ae0be49ed07 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -397,4 +397,4 @@ gpui::actions!( action_as!(go_to_line, ToggleGoToLine as Toggle); action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]); -action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleDiffHunk"]); +action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]); From 43f3491d50d3bfbc1777fc74209fc70e57582a16 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 28 Jan 2025 23:18:56 -0700 Subject: [PATCH 3/5] Add comment explaining why AddSurrounds target is not deserializable (#23820) See #23088 Release Notes: - N/A --- crates/vim/src/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 32c11b2d22e5b9..ddd83f4666b759 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -81,6 +81,7 @@ pub enum Operator { first_char: Option, }, AddSurrounds { + // Typically no need to configure this as `SendKeystrokes` can be used - see #23088. #[serde(skip)] target: Option, }, From 06936c69f663ee6dd588881f8ad0cf9af0ecfa4b Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 29 Jan 2025 01:48:07 -0500 Subject: [PATCH 4/5] Prompt users to use Discussions for feature requests (#23821) I'm moving forward on this - we can revert if it ends up being a bad move. Release Notes: - N/A --- .github/ISSUE_TEMPLATE/0_feature_request.yml | 15 --------------- .github/ISSUE_TEMPLATE/config.yml | 3 +++ crates/feedback/src/feedback.rs | 2 +- 3 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/0_feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/0_feature_request.yml b/.github/ISSUE_TEMPLATE/0_feature_request.yml deleted file mode 100644 index 0bf3a61757cab5..00000000000000 --- a/.github/ISSUE_TEMPLATE/0_feature_request.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Feature Request -description: "Tip: open this issue template from within Zed with the `request feature` command palette action" -type: "Feature" -body: - - type: textarea - attributes: - label: Describe the feature - description: A one line summary, and description of what you want to happen. - value: | - Summary: - - Description: - - Screenshots: - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9a51874331a4cc..991f2ce91a6553 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,6 +1,9 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json blank_issues_enabled: false contact_links: + - name: Feature Request + url: https://github.com/zed-industries/zed/discussions/new/choose + about: To request a feature, open a new Discussion in one of the appropriate Discussion categories - name: Zed Discussion Forum url: https://github.com/zed-industries/zed/discussions about: A community discussion forum diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 295114c0cb4b5d..ddac6f446ee159 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -22,7 +22,7 @@ const fn zed_repo_url() -> &'static str { } fn request_feature_url() -> String { - "https://github.com/zed-industries/zed/issues/new?template=0_feature_request.yml".to_string() + "https://github.com/zed-industries/zed/discussions/new/choose".to_string() } fn file_bug_report_url(specs: &SystemSpecs) -> String { From dbdf140ca16a5044e7bc92306ca9ff95553867f5 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Wed, 29 Jan 2025 00:05:33 -0700 Subject: [PATCH 5/5] Show settings file errors on startup (#23817) Required using a global `LazyLock>` instead of a context global because settings errors first occur before initialization of the notifications global. Release Notes: - Errors in settings file are now reported in UI on startup. --- crates/settings/src/settings_file.rs | 9 ++-- crates/workspace/src/notifications.rs | 63 ++++++++++++++------------- crates/workspace/src/workspace.rs | 1 - crates/zed/src/main.rs | 55 +++-------------------- crates/zed/src/zed.rs | 35 +++++++++++++-- 5 files changed, 74 insertions(+), 89 deletions(-) diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 15b5babf28f6e5..101695508f0ce9 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -3,7 +3,6 @@ use fs::Fs; use futures::{channel::mpsc, StreamExt}; use gpui::{App, BackgroundExecutor, ReadGlobal, UpdateGlobal}; use std::{path::PathBuf, sync::Arc, time::Duration}; -use util::ResultExt; pub const EMPTY_THEME_NAME: &str = "empty-theme"; @@ -73,9 +72,11 @@ pub fn handle_settings_file_changes( .block(user_settings_file_rx.next()) .unwrap(); SettingsStore::update_global(cx, |store, cx| { - store - .set_user_settings(&user_settings_content, cx) - .log_err(); + let result = store.set_user_settings(&user_settings_content, cx); + if let Err(err) = &result { + log::error!("Failed to load user settings: {err}"); + } + settings_changed(result.err(), cx); }); cx.spawn(move |cx| async move { while let Some(user_settings_content) = user_settings_file_rx.next().await { diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 72275f8b54c2b7..e805527b91f358 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -3,17 +3,12 @@ use gpui::{ svg, AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task, }; -use std::rc::Rc; +use parking_lot::Mutex; +use std::sync::{Arc, LazyLock}; use std::{any::TypeId, time::Duration}; use ui::{prelude::*, Tooltip}; use util::ResultExt; -pub fn init(cx: &mut App) { - cx.set_global(GlobalAppNotifications { - app_notifications: Vec::new(), - }) -} - #[derive(Debug, PartialEq, Clone)] pub enum NotificationId { Unique(TypeId), @@ -162,7 +157,7 @@ impl Workspace { pub fn show_initial_notifications(&mut self, cx: &mut Context) { // Allow absence of the global so that tests don't need to initialize it. let app_notifications = cx - .try_global::() + .try_global::() .iter() .flat_map(|global| global.app_notifications.iter().cloned()) .collect::>(); @@ -500,21 +495,27 @@ pub mod simple_message_notification { } } +static GLOBAL_APP_NOTIFICATIONS: LazyLock> = LazyLock::new(|| { + Mutex::new(AppNotifications { + app_notifications: Vec::new(), + }) +}); + /// Stores app notifications so that they can be shown in new workspaces. -struct GlobalAppNotifications { +struct AppNotifications { app_notifications: Vec<( NotificationId, - Rc) -> AnyView>, + Arc) -> AnyView + Send + Sync>, )>, } -impl Global for GlobalAppNotifications {} +impl Global for AppNotifications {} -impl GlobalAppNotifications { +impl AppNotifications { pub fn insert( &mut self, id: NotificationId, - build_notification: Rc) -> AnyView>, + build_notification: Arc) -> AnyView + Send + Sync>, ) { self.remove(&id); self.app_notifications.push((id, build_notification)) @@ -532,28 +533,30 @@ impl GlobalAppNotifications { pub fn show_app_notification( id: NotificationId, cx: &mut App, - build_notification: impl Fn(&mut Context) -> Entity + 'static, + build_notification: impl Fn(&mut Context) -> Entity + 'static + Send + Sync, ) { // Defer notification creation so that windows on the stack can be returned to GPUI cx.defer(move |cx| { // Handle dismiss events by removing the notification from all workspaces. - let build_notification: Rc) -> AnyView> = Rc::new({ - let id = id.clone(); - move |cx| { - let notification = build_notification(cx); - cx.subscribe(¬ification, { - let id = id.clone(); - move |_, _, _: &DismissEvent, cx| { - dismiss_app_notification(&id, cx); - } - }) - .detach(); - notification.into() - } - }); + let build_notification: Arc) -> AnyView + Send + Sync> = + Arc::new({ + let id = id.clone(); + move |cx| { + let notification = build_notification(cx); + cx.subscribe(¬ification, { + let id = id.clone(); + move |_, _, _: &DismissEvent, cx| { + dismiss_app_notification(&id, cx); + } + }) + .detach(); + notification.into() + } + }); // Store the notification so that new workspaces also receive it. - cx.global_mut::() + GLOBAL_APP_NOTIFICATIONS + .lock() .insert(id.clone(), build_notification.clone()); for window in cx.windows() { @@ -576,7 +579,7 @@ pub fn dismiss_app_notification(id: &NotificationId, cx: &mut App) { let id = id.clone(); // Defer notification dismissal so that windows on the stack can be returned to GPUI cx.defer(move |cx| { - cx.global_mut::().remove(&id); + GLOBAL_APP_NOTIFICATIONS.lock().remove(&id); for window in cx.windows() { if let Some(workspace_window) = window.downcast::() { let id = id.clone(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b901140d989012..bedadc41bdd4ce 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -365,7 +365,6 @@ fn prompt_and_open_paths(app_state: Arc, options: PathPromptOptions, c pub fn init(app_state: Arc, cx: &mut App) { init_settings(cx); - notifications::init(cx); theme_preview::init(cx); cx.on_action(Workspace::close_global); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 93ad29b236fdb2..8fc4cef8de8d20 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -18,7 +18,7 @@ use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::{future, StreamExt}; use git::GitHostingProviderRegistry; -use gpui::{Action, App, AppContext as _, Application, AsyncApp, DismissEvent, UpdateGlobal as _}; +use gpui::{App, AppContext as _, Application, AsyncApp, UpdateGlobal as _}; use http_client::{read_proxy_from_env, Uri}; use language::LanguageRegistry; @@ -33,9 +33,7 @@ use project::project_settings::ProjectSettings; use recent_projects::{open_ssh_project, SshSettings}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use session::{AppSession, Session}; -use settings::{ - handle_settings_file_changes, watch_config_file, InvalidSettingsError, Settings, SettingsStore, -}; +use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore}; use simplelog::ConfigBuilder; use std::{ env, @@ -50,18 +48,13 @@ use time::UtcOffset; use util::{maybe, ResultExt, TryFutureExt}; use uuid::Uuid; use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; -use workspace::{ - notifications::{simple_message_notification::MessageNotification, NotificationId}, - AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore, -}; +use workspace::{AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore}; use zed::{ app_menus, build_window_options, derive_paths_with_position, handle_cli_connection, - handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, OpenListener, - OpenRequest, + handle_keymap_file_changes, handle_settings_changed, initialize_workspace, + inline_completion_registry, open_paths_with_positions, OpenListener, OpenRequest, }; -use crate::zed::inline_completion_registry; - #[cfg(unix)] use util::{load_login_shell_environment, load_shell_from_passwd}; @@ -614,44 +607,6 @@ fn main() { }); } -fn handle_settings_changed(error: Option, cx: &mut App) { - struct SettingsParseErrorNotification; - let id = NotificationId::unique::(); - - for workspace in workspace::local_workspace_windows(cx) { - workspace - .update(cx, |workspace, _, cx| { - match error.as_ref() { - Some(error) => { - if let Some(InvalidSettingsError::LocalSettings { .. }) = - error.downcast_ref::() - { - // Local settings will be displayed by the projects - } else { - workspace.show_notification(id.clone(), cx, |cx| { - cx.new(|_cx| { - MessageNotification::new(format!( - "Invalid user settings file\n{error}" - )) - .with_click_message("Open settings file") - .on_click(|window, cx| { - window.dispatch_action( - zed_actions::OpenSettings.boxed_clone(), - cx, - ); - cx.emit(DismissEvent); - }) - }) - }); - } - } - None => workspace.dismiss_notification(&id, cx), - } - }) - .log_err(); - } -} - fn handle_open_request(request: OpenRequest, app_state: Arc, cx: &mut App) { if let Some(connection) = request.cli_connection { let app_state = app_state.clone(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 408ca87a22ab45..422695aa4db6a9 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -39,12 +39,12 @@ use release_channel::{AppCommitSha, ReleaseChannel}; use rope::Rope; use search::project_search::ProjectSearchBar; use settings::{ - initial_project_settings_content, initial_tasks_content, update_settings_file, KeymapFile, - KeymapFileLoadResult, Settings, SettingsStore, DEFAULT_KEYMAP_PATH, VIM_KEYMAP_PATH, + initial_project_settings_content, initial_tasks_content, update_settings_file, + InvalidSettingsError, KeymapFile, KeymapFileLoadResult, Settings, SettingsStore, + DEFAULT_KEYMAP_PATH, VIM_KEYMAP_PATH, }; use std::any::TypeId; use std::path::PathBuf; -use std::rc::Rc; use std::{borrow::Cow, ops::Deref, path::Path, sync::Arc}; use terminal_view::terminal_panel::{self, TerminalPanel}; use theme::{ActiveTheme, ThemeSettings}; @@ -1220,7 +1220,7 @@ fn show_keymap_file_load_error( }); cx.spawn(move |cx| async move { - let parsed_markdown = Rc::new(parsed_markdown.await); + let parsed_markdown = Arc::new(parsed_markdown.await); cx.update(|cx| { show_app_notification(notification_id, cx, move |cx| { let workspace_handle = cx.entity().downgrade(); @@ -1274,6 +1274,33 @@ pub fn load_default_keymap(cx: &mut App) { } } +pub fn handle_settings_changed(error: Option, cx: &mut App) { + struct SettingsParseErrorNotification; + let id = NotificationId::unique::(); + + match error { + Some(error) => { + if let Some(InvalidSettingsError::LocalSettings { .. }) = + error.downcast_ref::() + { + // Local settings errors are displayed by the projects + return; + } + show_app_notification(id, cx, move |cx| { + cx.new(|_cx| { + MessageNotification::new(format!("Invalid user settings file\n{error}")) + .with_click_message("Open settings file") + .on_click(|window, cx| { + window.dispatch_action(zed_actions::OpenSettings.boxed_clone(), cx); + cx.emit(DismissEvent); + }) + }) + }); + } + None => dismiss_app_notification(&id, cx), + } +} + pub fn open_new_ssh_project_from_project( workspace: &mut Workspace, paths: Vec,