From bdff8bf47c3fd8d8c2a494593e1891d84e783c60 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Mon, 9 Feb 2026 11:33:36 +0100 Subject: [PATCH] Ensure proper workspace is used for various actions (#48767) The multi workspace refactor **completely** broke the Vim mode, saving is not possible, and various other actions. This PR fixes this - [X] Code Reviewed - [X] Manual QA Release Notes: - N/A --- crates/agent_ui/src/inline_prompt_editor.rs | 2 +- crates/copilot_ui/src/sign_in.rs | 4 +-- crates/diagnostics/src/buffer_diagnostics.rs | 2 +- .../src/edit_prediction_button.rs | 2 +- crates/editor/src/hover_popover.rs | 2 +- .../src/test/editor_lsp_test_context.rs | 25 +++++++++++---- crates/outline/src/outline.rs | 27 ++++++++-------- crates/search/src/search.rs | 2 +- crates/vim/src/command.rs | 32 +++++++++---------- crates/vim/src/normal/mark.rs | 10 +++--- crates/vim/src/normal/repeat.rs | 4 +-- crates/vim/src/normal/search.rs | 2 +- crates/vim/src/vim.rs | 8 ++--- crates/workspace/src/workspace.rs | 7 ++-- 14 files changed, 73 insertions(+), 56 deletions(-) diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs index fd6b9bc5028c64f68dbed24dc44f3014376d1aff..2066a7ad886614373b200f4e45dd3bb0034f72a2 100644 --- a/crates/agent_ui/src/inline_prompt_editor.rs +++ b/crates/agent_ui/src/inline_prompt_editor.rs @@ -443,7 +443,7 @@ impl PromptEditor { self.mention_set .update(cx, |mention_set, _cx| mention_set.remove_invalid(&snapshot)); - if let Some(workspace) = window.root::().flatten() { + if let Some(workspace) = Workspace::for_window(window, cx) { workspace.update(cx, |workspace, cx| { let is_via_ssh = workspace.project().read(cx).is_via_remote_server(); diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index dd48f95e0af6daeaf2a0a15b7b9595cb4c08aba2..24b1218305474a29ac2d2e7c8e0a212d6d757522 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -35,7 +35,7 @@ pub fn initiate_sign_out(copilot: Entity, window: &mut Window, cx: &mut cx.update(|window, cx| copilot_toast(Some("Signed out of Copilot"), window, cx)) } Err(err) => cx.update(|window, cx| { - if let Some(workspace) = window.root::().flatten() { + if let Some(workspace) = Workspace::for_window(window, cx) { workspace.update(cx, |workspace, cx| { workspace.show_error(&err, cx); }) @@ -82,7 +82,7 @@ fn open_copilot_code_verification_window(copilot: &Entity, window: &Win fn copilot_toast(message: Option<&'static str>, window: &Window, cx: &mut App) { const NOTIFICATION_ID: NotificationId = NotificationId::unique::(); - let Some(workspace) = window.root::().flatten() else { + let Some(workspace) = Workspace::for_window(window, cx) else { return; }; diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 6360e868d88ddeec677935beeba536d04cbc9131..42139b697d40362578bac4fae6b58d2a1ca10b27 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -904,7 +904,7 @@ impl Render for BufferDiagnosticsEditor { .style(ButtonStyle::Transparent) .tooltip(Tooltip::text("Open File")) .on_click(cx.listener(|buffer_diagnostics, _, window, cx| { - if let Some(workspace) = window.root::().flatten() { + if let Some(workspace) = Workspace::for_window(window, cx) { workspace.update(cx, |workspace, cx| { workspace .open_path( diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 8835dd5507dc9deccb57ad4f4ba15d8af017bfd3..cd95929e206696cd13942e9e37092ee8d621d0f2 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -119,7 +119,7 @@ impl Render for EditPredictionButton { IconButton::new("copilot-error", icon) .icon_size(IconSize::Small) .on_click(cx.listener(move |_, _, window, cx| { - if let Some(workspace) = window.root::().flatten() { + if let Some(workspace) = Workspace::for_window(window, cx) { workspace.update(cx, |workspace, cx| { let copilot = copilot.clone(); workspace.show_toast( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 1e8b42988cc7abaa4fb8a55e3580a70566d8046c..b3039a1545f634302e2767f3c0a2073f3a772827 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -719,7 +719,7 @@ pub fn diagnostics_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) { if let Ok(uri) = Url::parse(&link) && uri.scheme() == "file" - && let Some(workspace) = window.root::().flatten() + && let Some(workspace) = Workspace::for_window(window, cx) { workspace.update(cx, |workspace, cx| { let task = workspace.open_abs_path( diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index e372fdbe4ac93325532b96e43f11d501977418d4..d1e5270d6c76e166a33a41df2843138f4b12c411 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -22,7 +22,7 @@ use language::{ use lsp::{notification, request}; use project::Project; use smol::stream::StreamExt; -use workspace::{AppState, Workspace, WorkspaceHandle}; +use workspace::{AppState, MultiWorkspace, Workspace, WorkspaceHandle}; use super::editor_test_context::{AssertionContextManager, EditorTestContext}; @@ -95,7 +95,8 @@ impl EditorLspTestContext { ) .await; - let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let window = + cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); let workspace = window.root(cx).unwrap(); @@ -106,12 +107,20 @@ impl EditorLspTestContext { }) .await .unwrap(); - cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) - .await; - let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone()); + cx.read(|cx| { + workspace + .read(cx) + .workspace() + .read(cx) + .worktree_scans_complete(cx) + }) + .await; + let file = cx.read(|cx| workspace.read(cx).workspace().file_project_paths(cx)[0].clone()); let item = workspace .update_in(&mut cx, |workspace, window, cx| { - workspace.open_path(file, None, true, window, cx) + workspace.workspace().update(cx, |workspace, cx| { + workspace.open_path(file, None, true, window, cx) + }) }) .await .expect("Could not open test file"); @@ -121,6 +130,8 @@ impl EditorLspTestContext { }); editor.update_in(&mut cx, |editor, window, cx| { let nav_history = workspace + .read(cx) + .workspace() .read(cx) .active_pane() .read(cx) @@ -134,6 +145,8 @@ impl EditorLspTestContext { // Ensure the language server is fully registered with the buffer cx.executor().run_until_parked(); + let workspace = cx.read(|cx| workspace.read(cx).workspace().clone()); + Self { cx: EditorTestContext { cx, diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 6c5edb996c8ed87e1c42c899271b28c9b0cca80b..7344a7369f56887274c42467af2e287093ffd619 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -20,7 +20,7 @@ use settings::Settings; use theme::{ActiveTheme, ThemeSettings}; use ui::{ListItem, ListItemSpacing, prelude::*}; use util::ResultExt; -use workspace::{DismissDecision, ModalView}; +use workspace::{DismissDecision, ModalView, Workspace}; pub fn init(cx: &mut App) { cx.observe_new(OutlineView::register).detach(); @@ -41,16 +41,15 @@ pub fn toggle( window: &mut Window, cx: &mut App, ) { - let outline = editor - .read(cx) - .buffer() - .read(cx) - .snapshot(cx) - .outline(Some(cx.theme().syntax())); - - let workspace = editor.read(cx).workspace(); - - if let Some((workspace, outline)) = workspace.zip(outline) { + if let Some((workspace, outline)) = Workspace::for_window(window, cx).and_then(|workspace| { + editor + .read(cx) + .buffer() + .read(cx) + .snapshot(cx) + .outline(Some(cx.theme().syntax())) + .map(|outline| (workspace, outline)) + }) { workspace.update(cx, |workspace, cx| { workspace.toggle_modal(window, cx, |window, cx| { OutlineView::new(outline, editor, window, cx) @@ -397,7 +396,7 @@ mod tests { use project::{FakeFs, Project}; use serde_json::json; use util::{path, rel_path::rel_path}; - use workspace::{AppState, Workspace}; + use workspace::{AppState, MultiWorkspace, Workspace}; #[gpui::test] async fn test_outline_view_row_highlights(cx: &mut TestAppContext) { @@ -425,7 +424,9 @@ mod tests { }); let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + + let workspace = cx.read(|cx| workspace.read(cx).workspace().clone()); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index ac337fb4c8f53e407178d9ccf1be7e91d89fadcb..d2104492bebf529821f8ad8571fd3fbb8bdbc69e 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -191,7 +191,7 @@ pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) { struct NotifType(); let notification_id = NotificationId::unique::(); - let Some(workspace) = window.root::().flatten() else { + let Some(workspace) = Workspace::for_window(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 423c3b387b197edd2d8e86398b09157fdcb7711a..ae0461339d2f7fccb1ddc8f7a0e40fafa727483b 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -318,7 +318,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { } }); Vim::action(editor, cx, |vim, _: &VisualCommand, window, cx| { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -327,7 +327,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { }); Vim::action(editor, cx, |vim, _: &ShellCommand, window, cx| { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -346,7 +346,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { }); Vim::action(editor, cx, |vim, _: &ShellCommand, window, cx| { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -398,7 +398,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { if action.filename.is_empty() { if whole_buffer { - if let Some(workspace) = vim.workspace(window) { + if let Some(workspace) = vim.workspace(window, cx) { workspace.update(cx, |workspace, cx| { workspace .save_active_item( @@ -472,7 +472,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { return; } if action.filename.is_empty() { - if let Some(workspace) = vim.workspace(window) { + if let Some(workspace) = vim.workspace(window, cx) { workspace.update(cx, |workspace, cx| { workspace .save_active_item( @@ -549,7 +549,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { }); Vim::action(editor, cx, |vim, action: &VimSplit, window, cx| { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; @@ -647,7 +647,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, action: &VimEdit, window, cx| { vim.update_editor(cx, |vim, editor, cx| { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; let Some(project) = editor.project().cloned() else { @@ -814,7 +814,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { } }; - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; let task = workspace.update(cx, |workspace, cx| { @@ -855,7 +855,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { }); Vim::action(editor, cx, |vim, _: &CountCommand, window, cx| { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); @@ -888,7 +888,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { anyhow::Ok(()) }); if let Some(e @ Err(_)) = result { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -932,7 +932,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { let range = match result { None => return, Some(e @ Err(_)) => { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -2132,7 +2132,7 @@ impl OnMatchingLines { let range = match result { None => return, Some(e @ Err(_)) => { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -2149,7 +2149,7 @@ impl OnMatchingLines { let mut regexes = match Regex::new(&self.search) { Ok(regex) => vec![(regex, !self.invert)], e @ Err(_) => { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -2347,7 +2347,7 @@ impl Vim { cx: &mut Context, ) { self.stop_recording(cx); - let Some(workspace) = self.workspace(window) else { + let Some(workspace) = self.workspace(window, cx) else { return; }; let command = self.update_editor(cx, |_, editor, cx| { @@ -2396,7 +2396,7 @@ impl Vim { cx: &mut Context, ) { self.stop_recording(cx); - let Some(workspace) = self.workspace(window) else { + let Some(workspace) = self.workspace(window, cx) else { return; }; let command = self.update_editor(cx, |_, editor, cx| { @@ -2448,7 +2448,7 @@ impl ShellExec { } pub fn run(&self, vim: &mut Vim, window: &mut Window, cx: &mut Context) { - let Some(workspace) = vim.workspace(window) else { + let Some(workspace) = vim.workspace(window, cx) else { return; }; diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index a4d85e87b24fa6e2753f0dbcfcbb43be9488f41a..48cf8739b725f64e1dd5930b23e046e92fd72392 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -81,7 +81,7 @@ impl Vim { window: &mut Window, cx: &mut Context, ) { - let Some(workspace) = self.workspace(window) else { + let Some(workspace) = self.workspace(window, cx) else { return; }; workspace.update(cx, |workspace, cx| { @@ -133,7 +133,7 @@ impl Vim { window: &mut Window, cx: &mut Context, ) { - let Some(workspace) = self.workspace(window) else { + let Some(workspace) = self.workspace(window, cx) else { return; }; let task = workspace.update(cx, |workspace, cx| { @@ -272,7 +272,7 @@ impl Vim { window: &mut Window, cx: &mut App, ) { - let Some(workspace) = self.workspace(window) else { + let Some(workspace) = self.workspace(window, cx) else { return; }; if name == "`" { @@ -324,7 +324,7 @@ impl Vim { return Some(Mark::Local(anchors)); } VimGlobals::update_global(cx, |globals, cx| { - let workspace_id = self.workspace(window)?.entity_id(); + let workspace_id = self.workspace(window, cx)?.entity_id(); globals .marks .get_mut(&workspace_id)? @@ -339,7 +339,7 @@ impl Vim { window: &mut Window, cx: &mut App, ) { - let Some(workspace) = self.workspace(window) else { + let Some(workspace) = self.workspace(window, cx) else { return; }; if name == "`" || name == "'" { diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index 5b480a86d846ff719d8784f619be861db9e44c9f..8a4bfc241d1b0c62b17464bfb1dd5076015ac638 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -112,7 +112,7 @@ impl Replayer { let this = self.clone(); window.defer(cx, move |window, cx| { this.next(window, cx); - let Some(Some(workspace)) = window.root::() else { + let Some(workspace) = Workspace::for_window(window, cx) else { return; }; let Some(editor) = workspace @@ -165,7 +165,7 @@ impl Replayer { text, utf16_range_to_replace, } => { - let Some(Some(workspace)) = window.root::() else { + let Some(workspace) = Workspace::for_window(window, cx) else { return; }; let Some(editor) = workspace diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index c11784d163e18451129656aa92d23dba568bd723..248f43c08192182cb266dbfc43a5a769f87429cd 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -555,7 +555,7 @@ impl Vim { let replacement = action.replacement.clone(); let Some(((pane, workspace), editor)) = self .pane(window, cx) - .zip(self.workspace(window)) + .zip(self.workspace(window, cx)) .zip(self.editor()) else { return; diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 8e21b2b7a795a20947d5697c034a2bb6ee425f55..1100db4585e286a4e100b9d62b8be552471c2095 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -1003,12 +1003,12 @@ impl Vim { self.editor.upgrade() } - pub fn workspace(&self, window: &mut Window) -> Option> { - window.root::().flatten() + pub fn workspace(&self, window: &Window, cx: &App) -> Option> { + Workspace::for_window(window, cx) } - pub fn pane(&self, window: &mut Window, cx: &mut Context) -> Option> { - self.workspace(window) + pub fn pane(&self, window: &Window, cx: &Context) -> Option> { + self.workspace(window, cx) .map(|workspace| workspace.read(cx).focused_pane(window, cx)) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5879143443adb753becbcb9b25aa1d965184baf4..ed7030bd428517f834c315465c534ecf0deb968c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -6731,8 +6731,11 @@ impl Workspace { ) } - pub fn for_window(window: &mut Window, _: &mut App) -> Option> { - window.root().flatten() + pub fn for_window(window: &Window, cx: &App) -> Option> { + window + .root::() + .flatten() + .map(|multi_workspace| multi_workspace.read(cx).workspace().clone()) } pub fn zoomed_item(&self) -> Option<&AnyWeakView> {