mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Re-add MultiWorkspace (#48800)
Release Notes: - Added agent panel restoration. Now restarting your editor won't cause your thread to be forgotten. --------- Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Co-authored-by: Eric Holk <eric@zed.dev> Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Co-authored-by: Anthony Eid <anthony@zed.dev> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> Co-authored-by: Cameron Mcloughlin <cameron.studdstreet@gmail.com>
This commit is contained in:
parent
83de8a25e0
commit
ee3f40fe25
133 changed files with 9290 additions and 4100 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
|
@ -4942,6 +4942,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
|
|
@ -8481,7 +8482,6 @@ dependencies = [
|
|||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"platform_title_bar",
|
||||
"project",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
|
|
@ -12371,6 +12371,7 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
|||
name = "platform_title_bar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"feature_flags",
|
||||
"gpui",
|
||||
"settings",
|
||||
"smallvec",
|
||||
|
|
@ -15361,6 +15362,30 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "sidebar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acp_thread",
|
||||
"agent_ui",
|
||||
"db",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"picker",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
|
|
@ -17240,6 +17265,7 @@ dependencies = [
|
|||
"cloud_api_types",
|
||||
"collections",
|
||||
"db",
|
||||
"feature_flags",
|
||||
"git_ui",
|
||||
"gpui",
|
||||
"http_client",
|
||||
|
|
@ -21127,6 +21153,7 @@ dependencies = [
|
|||
"settings_profile_selector",
|
||||
"settings_ui",
|
||||
"shellexpand 2.1.2",
|
||||
"sidebar",
|
||||
"smol",
|
||||
"snippet_provider",
|
||||
"snippets_ui",
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ members = [
|
|||
"crates/schema_generator",
|
||||
"crates/search",
|
||||
"crates/session",
|
||||
"crates/sidebar",
|
||||
"crates/settings",
|
||||
"crates/settings_content",
|
||||
"crates/settings_json",
|
||||
|
|
@ -396,6 +397,7 @@ rules_library = { path = "crates/rules_library" }
|
|||
scheduler = { path = "crates/scheduler" }
|
||||
search = { path = "crates/search" }
|
||||
session = { path = "crates/session" }
|
||||
sidebar = { path = "crates/sidebar" }
|
||||
settings = { path = "crates/settings" }
|
||||
settings_content = { path = "crates/settings_content" }
|
||||
settings_json = { path = "crates/settings_json" }
|
||||
|
|
@ -855,6 +857,7 @@ refineable = { codegen-units = 1 }
|
|||
release_channel = { codegen-units = 1 }
|
||||
reqwest_client = { codegen-units = 1 }
|
||||
session = { codegen-units = 1 }
|
||||
sidebar = { codegen-units = 1 }
|
||||
snippet = { codegen-units = 1 }
|
||||
snippets_ui = { codegen-units = 1 }
|
||||
story = { codegen-units = 1 }
|
||||
|
|
|
|||
5
assets/icons/workspace_nav_closed.svg
Normal file
5
assets/icons/workspace_nav_closed.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.2" width="7" height="12" rx="2" transform="matrix(-1 0 0 1 9 2)" fill="#C6CAD0"/>
|
||||
<path d="M9 2V14" stroke="#C6CAD0" stroke-width="1.2"/>
|
||||
<rect x="2" y="2" width="12" height="12" rx="2" stroke="#C6CAD0" stroke-width="1.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
5
assets/icons/workspace_nav_open.svg
Normal file
5
assets/icons/workspace_nav_open.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="7" height="12" rx="2" transform="matrix(-1 0 0 1 9 2)" fill="#C6CAD0"/>
|
||||
<path d="M9 2V14" stroke="#C6CAD0" stroke-width="1.2"/>
|
||||
<rect x="2" y="2" width="12" height="12" rx="2" stroke="#C6CAD0" stroke-width="1.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 330 B |
|
|
@ -603,6 +603,8 @@
|
|||
"ctrl-alt-b": "workspace::ToggleRightDock",
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-j": "multi_workspace::ToggleWorkspaceSidebar",
|
||||
"ctrl-alt-;": "multi_workspace::FocusWorkspaceSidebar",
|
||||
"ctrl-alt-y": "workspace::ToggleAllDocks",
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
|
|
@ -662,6 +664,13 @@
|
|||
"ctrl-w": "workspace::CloseActiveDock",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "WorkspaceSidebar",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-n": "multi_workspace::NewWorkspaceInWindow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "Workspace && debugger_running",
|
||||
"bindings": {
|
||||
|
|
|
|||
|
|
@ -664,6 +664,8 @@
|
|||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"cmd-alt-j": "multi_workspace::ToggleWorkspaceSidebar",
|
||||
"cmd-alt-;": "multi_workspace::FocusWorkspaceSidebar",
|
||||
"alt-cmd-y": "workspace::ToggleAllDocks",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
|
|
@ -723,6 +725,13 @@
|
|||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "WorkspaceSidebar",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "multi_workspace::NewWorkspaceInWindow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "Workspace && debugger_running",
|
||||
"use_key_equivalents": true,
|
||||
|
|
|
|||
|
|
@ -598,6 +598,8 @@
|
|||
"ctrl-alt-b": "workspace::ToggleRightDock",
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-j": "multi_workspace::ToggleWorkspaceSidebar",
|
||||
"ctrl-alt-;": "multi_workspace::FocusWorkspaceSidebar",
|
||||
"ctrl-shift-y": "workspace::ToggleAllDocks",
|
||||
"alt-r": "workspace::ResetActiveDockSize",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
|
|
@ -666,6 +668,13 @@
|
|||
"f5": "debugger::Continue",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "WorkspaceSidebar",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-n": "multi_workspace::NewWorkspaceInWindow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "ApplicationMenu",
|
||||
"use_key_equivalents": true,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ mod mode_selector;
|
|||
mod model_selector;
|
||||
mod model_selector_popover;
|
||||
mod thread_history;
|
||||
mod thread_view;
|
||||
pub(crate) mod thread_view;
|
||||
|
||||
pub use mode_selector::ModeSelector;
|
||||
pub use model_selector::AcpModelSelector;
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
use workspace::Workspace;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diff_sync(cx: &mut TestAppContext) {
|
||||
|
|
@ -434,8 +434,9 @@ mod tests {
|
|||
.await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tool_call = acp::ToolCall::new("tool", "Tool call")
|
||||
.status(acp::ToolCallStatus::InProgress)
|
||||
|
|
|
|||
|
|
@ -815,8 +815,13 @@ impl MessageEditor {
|
|||
}
|
||||
|
||||
if self.prompt_capabilities.borrow().image
|
||||
&& let Some(task) =
|
||||
paste_images_as_context(self.editor.clone(), self.mention_set.clone(), window, cx)
|
||||
&& let Some(task) = paste_images_as_context(
|
||||
self.editor.clone(),
|
||||
self.mention_set.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
{
|
||||
task.detach();
|
||||
return;
|
||||
|
|
@ -1084,6 +1089,7 @@ impl MessageEditor {
|
|||
|
||||
let editor = self.editor.clone();
|
||||
let mention_set = self.mention_set.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
let paths_receiver = cx.prompt_for_paths(gpui::PathPromptOptions {
|
||||
files: true,
|
||||
|
|
@ -1134,7 +1140,14 @@ impl MessageEditor {
|
|||
images.push(gpui::Image::from_bytes(format, content));
|
||||
}
|
||||
|
||||
crate::mention_set::insert_images_as_context(images, editor, mention_set, cx).await;
|
||||
crate::mention_set::insert_images_as_context(
|
||||
images,
|
||||
editor,
|
||||
mention_set,
|
||||
workspace,
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
|
@ -1450,7 +1463,7 @@ mod tests {
|
|||
use text::Point;
|
||||
use ui::{App, Context, IntoElement, Render, SharedString, Window};
|
||||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||
use workspace::{AppState, Item, Workspace};
|
||||
use workspace::{AppState, Item, MultiWorkspace};
|
||||
|
||||
use crate::acp::{
|
||||
message_editor::{Mention, MessageEditor, parse_mention_links},
|
||||
|
|
@ -1558,8 +1571,9 @@ mod tests {
|
|||
fs.insert_tree("/project", json!({"file": ""})).await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = None;
|
||||
let history = cx
|
||||
|
|
@ -1673,8 +1687,9 @@ mod tests {
|
|||
// Start with no available commands - simulating Claude which doesn't support slash commands
|
||||
let available_commands = Rc::new(RefCell::new(vec![]));
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let history = cx
|
||||
.update(|window, cx| cx.new(|cx| crate::acp::AcpThreadHistory::new(None, window, cx)));
|
||||
let workspace_handle = workspace.downgrade();
|
||||
|
|
@ -1822,10 +1837,13 @@ mod tests {
|
|||
});
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
let mut cx = VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
let thread_store = None;
|
||||
let history = cx
|
||||
|
|
@ -2014,8 +2032,11 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let worktree = project.update(cx, |project, cx| {
|
||||
let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
|
||||
|
|
@ -2024,7 +2045,7 @@ mod tests {
|
|||
});
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
let mut cx = VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
let paths = vec![
|
||||
rel_path("a/one.txt"),
|
||||
|
|
@ -2551,8 +2572,9 @@ mod tests {
|
|||
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx
|
||||
|
|
@ -2651,8 +2673,9 @@ mod tests {
|
|||
fs.insert_tree("/project", json!({"file": ""})).await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx
|
||||
|
|
@ -2732,8 +2755,9 @@ mod tests {
|
|||
fs.insert_tree("/project", json!({"file": ""})).await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = None;
|
||||
let history = cx
|
||||
|
|
@ -2791,8 +2815,9 @@ mod tests {
|
|||
fs.insert_tree("/project", json!({"file": ""})).await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = None;
|
||||
let history = cx
|
||||
|
|
@ -2845,8 +2870,9 @@ mod tests {
|
|||
fs.insert_tree("/project", json!({"file": ""})).await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx
|
||||
|
|
@ -2900,8 +2926,9 @@ mod tests {
|
|||
.await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx
|
||||
|
|
@ -2964,8 +2991,9 @@ mod tests {
|
|||
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx
|
||||
|
|
@ -3085,8 +3113,11 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let worktree = project.update(cx, |project, cx| {
|
||||
let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
|
||||
|
|
@ -3095,7 +3126,7 @@ mod tests {
|
|||
});
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
let mut cx = VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
// Open a regular editor with the created file, and select a portion of
|
||||
// the text that will be used for the selections that are meant to be
|
||||
|
|
@ -3237,10 +3268,13 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
let mut cx = VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
let thread_store = cx.new(|cx| ThreadStore::new(cx));
|
||||
let history = cx
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ use ui::{
|
|||
};
|
||||
use util::defer;
|
||||
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
||||
use workspace::{CollaboratorId, NewTerminal, Toast, Workspace, notifications::NotificationId};
|
||||
use workspace::{
|
||||
CollaboratorId, MultiWorkspace, NewTerminal, Toast, Workspace, notifications::NotificationId,
|
||||
};
|
||||
use zed_actions::agent::{Chat, ToggleModelSelector};
|
||||
use zed_actions::assistant::OpenRulesLibrary;
|
||||
|
||||
|
|
@ -2161,9 +2163,30 @@ impl AcpServerView {
|
|||
self.show_notification(caption, icon, window, cx);
|
||||
}
|
||||
|
||||
fn agent_is_visible(&self, window: &Window, cx: &App) -> bool {
|
||||
if window.is_window_active() {
|
||||
let workspace_is_foreground = window
|
||||
.root::<MultiWorkspace>()
|
||||
.flatten()
|
||||
.and_then(|mw| {
|
||||
let mw = mw.read(cx);
|
||||
self.workspace.upgrade().map(|ws| mw.workspace() == &ws)
|
||||
})
|
||||
.unwrap_or(true);
|
||||
|
||||
if workspace_is_foreground {
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
return AgentPanel::is_visible(&workspace, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn play_notification_sound(&self, window: &Window, cx: &mut App) {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
if settings.play_sound_when_agent_done && !window.is_window_active() {
|
||||
if settings.play_sound_when_agent_done && !self.agent_is_visible(window, cx) {
|
||||
Audio::play_sound(Sound::AgentDone, cx);
|
||||
}
|
||||
}
|
||||
|
|
@ -2181,14 +2204,7 @@ impl AcpServerView {
|
|||
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let window_is_inactive = !window.is_window_active();
|
||||
let panel_is_hidden = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
.map(|workspace| AgentPanel::is_hidden(&workspace, cx))
|
||||
.unwrap_or(true);
|
||||
|
||||
let should_notify = window_is_inactive || panel_is_hidden;
|
||||
let should_notify = !self.agent_is_visible(window, cx);
|
||||
|
||||
if !should_notify {
|
||||
return;
|
||||
|
|
@ -2251,19 +2267,22 @@ impl AcpServerView {
|
|||
.push(cx.subscribe_in(&pop_up, window, {
|
||||
|this, _, event, window, cx| match event {
|
||||
AgentNotificationEvent::Accepted => {
|
||||
let handle = window.window_handle();
|
||||
let Some(handle) = window.window_handle().downcast::<MultiWorkspace>()
|
||||
else {
|
||||
log::error!("root view should be a MultiWorkspace");
|
||||
return;
|
||||
};
|
||||
cx.activate(true);
|
||||
|
||||
let workspace_handle = this.workspace.clone();
|
||||
|
||||
// If there are multiple Zed windows, activate the correct one.
|
||||
cx.defer(move |cx| {
|
||||
handle
|
||||
.update(cx, |_view, window, _cx| {
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
window.activate_window();
|
||||
|
||||
if let Some(workspace) = workspace_handle.upgrade() {
|
||||
workspace.update(_cx, |workspace, cx| {
|
||||
multi_workspace.activate(workspace.clone(), cx);
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AgentPanel>(window, cx);
|
||||
});
|
||||
}
|
||||
|
|
@ -2288,12 +2307,12 @@ impl AcpServerView {
|
|||
.push({
|
||||
let pop_up_weak = pop_up.downgrade();
|
||||
|
||||
cx.observe_window_activation(window, move |_, window, cx| {
|
||||
if window.is_window_active()
|
||||
cx.observe_window_activation(window, move |this, window, cx| {
|
||||
if this.agent_is_visible(window, cx)
|
||||
&& let Some(pop_up) = pop_up_weak.upgrade()
|
||||
{
|
||||
pop_up.update(cx, |_, cx| {
|
||||
cx.emit(AgentNotificationEvent::Dismissed);
|
||||
pop_up.update(cx, |notification, cx| {
|
||||
notification.dismiss(cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
@ -2545,6 +2564,7 @@ pub(crate) mod tests {
|
|||
use action_log::ActionLog;
|
||||
use agent::{AgentTool, EditFileTool, FetchTool, TerminalTool, ToolPermissionContext};
|
||||
use agent_client_protocol::SessionId;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use editor::MultiBufferOffset;
|
||||
use fs::FakeFs;
|
||||
use gpui::{EventEmitter, TestAppContext, VisualTestContext};
|
||||
|
|
@ -2556,7 +2576,9 @@ pub(crate) mod tests {
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use workspace::Item;
|
||||
use workspace::{Item, MultiWorkspace};
|
||||
|
||||
use crate::agent_panel;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -2628,8 +2650,9 @@ pub(crate) mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
|
||||
// Create history without an initial session list - it will be set after connection
|
||||
|
|
@ -2700,8 +2723,9 @@ pub(crate) mod tests {
|
|||
let session = AgentSessionInfo::new(SessionId::new("resume-session"));
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx.update(|window, cx| cx.new(|cx| AcpThreadHistory::new(None, window, cx)));
|
||||
|
|
@ -2747,8 +2771,9 @@ pub(crate) mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new("/project")], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let connection = CwdCapturingConnection::new();
|
||||
let captured_cwd = connection.captured_cwd.clone();
|
||||
|
|
@ -2798,8 +2823,9 @@ pub(crate) mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new("/project")], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let connection = CwdCapturingConnection::new();
|
||||
let captured_cwd = connection.captured_cwd.clone();
|
||||
|
|
@ -2849,8 +2875,9 @@ pub(crate) mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new("/project")], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let connection = CwdCapturingConnection::new();
|
||||
let captured_cwd = connection.captured_cwd.clone();
|
||||
|
|
@ -3011,6 +3038,137 @@ pub(crate) mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_notification_when_workspace_is_background_in_multi_workspace(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
// Enable multi-workspace feature flag and init globals needed by AgentPanel
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_flags(true, vec!["agent-v2".to_string()]);
|
||||
agent::ThreadStore::init_global(cx);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
<dyn Fs>::set_global(fs.clone(), cx);
|
||||
});
|
||||
|
||||
let project1 = Project::test(fs.clone(), [], cx).await;
|
||||
|
||||
// Create a MultiWorkspace window with one workspace
|
||||
let multi_workspace_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project1.clone(), window, cx));
|
||||
|
||||
// Get workspace 1 (the initial workspace)
|
||||
let workspace1 = multi_workspace_handle
|
||||
.read_with(cx, |mw, _cx| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(multi_workspace_handle.into(), cx);
|
||||
|
||||
workspace1.update_in(cx, |workspace, window, cx| {
|
||||
let text_thread_store =
|
||||
cx.new(|cx| TextThreadStore::fake(workspace.project().clone(), cx));
|
||||
let panel =
|
||||
cx.new(|cx| crate::AgentPanel::new(workspace, text_thread_store, None, window, cx));
|
||||
workspace.add_panel(panel, window, cx);
|
||||
|
||||
// Open the dock and activate the agent panel so it's visible
|
||||
workspace.focus_panel::<crate::AgentPanel>(window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.read(|cx| {
|
||||
assert!(
|
||||
crate::AgentPanel::is_visible(&workspace1, cx),
|
||||
"AgentPanel should be visible in workspace1's dock"
|
||||
);
|
||||
});
|
||||
|
||||
// Set up thread view in workspace 1
|
||||
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx.update(|window, cx| cx.new(|cx| AcpThreadHistory::new(None, window, cx)));
|
||||
|
||||
let agent = StubAgentServer::default_response();
|
||||
let thread_view = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
AcpServerView::new(
|
||||
Rc::new(agent),
|
||||
None,
|
||||
None,
|
||||
workspace1.downgrade(),
|
||||
project1.clone(),
|
||||
Some(thread_store),
|
||||
None,
|
||||
history,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let message_editor = message_editor(&thread_view, cx);
|
||||
message_editor.update_in(cx, |editor, window, cx| {
|
||||
editor.set_text("Hello", window, cx);
|
||||
});
|
||||
|
||||
// Create a second workspace and switch to it.
|
||||
// This makes workspace1 the "background" workspace.
|
||||
let project2 = Project::test(fs, [], cx).await;
|
||||
multi_workspace_handle
|
||||
.update(cx, |mw, window, cx| {
|
||||
mw.test_add_workspace(project2, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// Verify workspace1 is no longer the active workspace
|
||||
multi_workspace_handle
|
||||
.read_with(cx, |mw, _cx| {
|
||||
assert_eq!(mw.active_workspace_index(), 1);
|
||||
assert_ne!(mw.workspace(), &workspace1);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Window is active, agent panel is visible in workspace1, but workspace1
|
||||
// is in the background. The notification should show because the user
|
||||
// can't actually see the agent panel.
|
||||
active_thread(&thread_view, cx).update_in(cx, |view, window, cx| view.send(window, cx));
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
assert!(
|
||||
cx.windows()
|
||||
.iter()
|
||||
.any(|window| window.downcast::<AgentNotification>().is_some()),
|
||||
"Expected notification when workspace is in background within MultiWorkspace"
|
||||
);
|
||||
|
||||
// Also verify: clicking "View Panel" should switch to workspace1.
|
||||
cx.windows()
|
||||
.iter()
|
||||
.find_map(|window| window.downcast::<AgentNotification>())
|
||||
.unwrap()
|
||||
.update(cx, |window, _, cx| window.accept(cx))
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
multi_workspace_handle
|
||||
.read_with(cx, |mw, _cx| {
|
||||
assert_eq!(
|
||||
mw.workspace(),
|
||||
&workspace1,
|
||||
"Expected workspace1 to become the active workspace after accepting notification"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_notification_respects_never_setting(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
@ -3103,8 +3261,9 @@ pub(crate) mod tests {
|
|||
) -> (Entity<AcpServerView>, &mut VisualTestContext) {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx.update(|window, cx| cx.new(|cx| AcpThreadHistory::new(None, window, cx)));
|
||||
|
|
@ -3173,18 +3332,18 @@ pub(crate) mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
struct StubAgentServer<C> {
|
||||
pub(crate) struct StubAgentServer<C> {
|
||||
connection: C,
|
||||
}
|
||||
|
||||
impl<C> StubAgentServer<C> {
|
||||
fn new(connection: C) -> Self {
|
||||
pub(crate) fn new(connection: C) -> Self {
|
||||
Self { connection }
|
||||
}
|
||||
}
|
||||
|
||||
impl StubAgentServer<StubAgentConnection> {
|
||||
fn default_response() -> Self {
|
||||
pub(crate) fn default_response() -> Self {
|
||||
let conn = StubAgentConnection::new();
|
||||
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk::new("Default response".into()),
|
||||
|
|
@ -3580,6 +3739,7 @@ pub(crate) mod tests {
|
|||
cx.set_global(settings_store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
editor::init(cx);
|
||||
agent_panel::init(cx);
|
||||
release_channel::init(semver::Version::new(0, 0, 0), cx);
|
||||
prompt_store::init(cx)
|
||||
});
|
||||
|
|
@ -3614,8 +3774,9 @@ pub(crate) mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new("/project")], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
|
||||
let history = cx.update(|window, cx| cx.new(|cx| AcpThreadHistory::new(None, window, cx)));
|
||||
|
|
|
|||
|
|
@ -599,6 +599,7 @@ mod tests {
|
|||
use project::Project;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_save_provider_invalid_inputs(cx: &mut TestAppContext) {
|
||||
|
|
@ -815,8 +816,9 @@ mod tests {
|
|||
let fs = FakeFs::new(cx.executor());
|
||||
cx.update(|cx| <dyn Fs>::set_global(fs.clone(), cx));
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (_, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let _workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
cx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1352,10 +1352,10 @@ impl AgentDiff {
|
|||
self.update_reviewing_editors(workspace, window, cx);
|
||||
}
|
||||
}
|
||||
AcpThreadEvent::Stopped
|
||||
| AcpThreadEvent::Error
|
||||
| AcpThreadEvent::LoadError(_)
|
||||
| AcpThreadEvent::Refusal => {
|
||||
AcpThreadEvent::Stopped => {
|
||||
self.update_reviewing_editors(workspace, window, cx);
|
||||
}
|
||||
AcpThreadEvent::Error | AcpThreadEvent::LoadError(_) | AcpThreadEvent::Refusal => {
|
||||
self.update_reviewing_editors(workspace, window, cx);
|
||||
}
|
||||
AcpThreadEvent::TitleUpdated
|
||||
|
|
@ -1734,6 +1734,7 @@ mod tests {
|
|||
use settings::SettingsStore;
|
||||
use std::{path::Path, rc::Rc};
|
||||
use util::path;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multibuffer_agent_diff(cx: &mut TestAppContext) {
|
||||
|
|
@ -1770,8 +1771,9 @@ mod tests {
|
|||
|
||||
let action_log = cx.read(|cx| thread.read(cx).action_log().clone());
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let agent_diff = cx.new_window_entity(|window, cx| {
|
||||
AgentDiffPane::new(thread.clone(), workspace.downgrade(), window, cx)
|
||||
});
|
||||
|
|
@ -1929,8 +1931,9 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
// Add the diff toolbar to the active pane
|
||||
let diff_toolbar = cx.new_window_entity(|_, cx| AgentDiffToolbar::new(cx));
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ use ui::{
|
|||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
|
||||
WorkspaceId,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
};
|
||||
use zed_actions::{
|
||||
|
|
@ -81,10 +82,50 @@ const AGENT_PANEL_KEY: &str = "agent_panel";
|
|||
const RECENTLY_UPDATED_MENU_LIMIT: usize = 6;
|
||||
const DEFAULT_THREAD_TITLE: &str = "New Thread";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
fn read_serialized_panel(workspace_id: workspace::WorkspaceId) -> Option<SerializedAgentPanel> {
|
||||
let scope = KEY_VALUE_STORE.scoped(AGENT_PANEL_KEY);
|
||||
let key = i64::from(workspace_id).to_string();
|
||||
scope
|
||||
.read(&key)
|
||||
.log_err()
|
||||
.flatten()
|
||||
.and_then(|json| serde_json::from_str::<SerializedAgentPanel>(&json).log_err())
|
||||
}
|
||||
|
||||
async fn save_serialized_panel(
|
||||
workspace_id: workspace::WorkspaceId,
|
||||
panel: SerializedAgentPanel,
|
||||
) -> Result<()> {
|
||||
let scope = KEY_VALUE_STORE.scoped(AGENT_PANEL_KEY);
|
||||
let key = i64::from(workspace_id).to_string();
|
||||
scope.write(key, serde_json::to_string(&panel)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Migration: reads the original single-panel format stored under the
|
||||
/// `"agent_panel"` KVP key before per-workspace keying was introduced.
|
||||
fn read_legacy_serialized_panel() -> Option<SerializedAgentPanel> {
|
||||
KEY_VALUE_STORE
|
||||
.read_kvp(AGENT_PANEL_KEY)
|
||||
.log_err()
|
||||
.flatten()
|
||||
.and_then(|json| serde_json::from_str::<SerializedAgentPanel>(&json).log_err())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct SerializedAgentPanel {
|
||||
width: Option<Pixels>,
|
||||
selected_agent: Option<AgentType>,
|
||||
#[serde(default)]
|
||||
last_active_thread: Option<SerializedActiveThread>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct SerializedActiveThread {
|
||||
session_id: String,
|
||||
agent_type: AgentType,
|
||||
title: Option<String>,
|
||||
cwd: Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
|
|
@ -128,7 +169,9 @@ pub fn init(cx: &mut App) {
|
|||
.register_action(|workspace, _: &NewTextThread, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
workspace.focus_panel::<AgentPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| panel.new_text_thread(window, cx));
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.new_text_thread(window, cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, action: &NewExternalAgentThread, window, cx| {
|
||||
|
|
@ -413,6 +456,8 @@ impl ActiveView {
|
|||
|
||||
pub struct AgentPanel {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
/// Workspace id is used as a database key
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
user_store: Entity<UserStore>,
|
||||
project: Entity<Project>,
|
||||
fs: Arc<dyn Fs>,
|
||||
|
|
@ -428,6 +473,7 @@ pub struct AgentPanel {
|
|||
focus_handle: FocusHandle,
|
||||
active_view: ActiveView,
|
||||
previous_view: Option<ActiveView>,
|
||||
_active_view_observation: Option<Subscription>,
|
||||
new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
agent_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
|
|
@ -444,19 +490,39 @@ pub struct AgentPanel {
|
|||
}
|
||||
|
||||
impl AgentPanel {
|
||||
fn serialize(&mut self, cx: &mut Context<Self>) {
|
||||
fn serialize(&mut self, cx: &mut App) {
|
||||
let Some(workspace_id) = self.workspace_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let width = self.width;
|
||||
let selected_agent = self.selected_agent.clone();
|
||||
|
||||
let last_active_thread = self.active_agent_thread(cx).map(|thread| {
|
||||
let thread = thread.read(cx);
|
||||
let title = thread.title();
|
||||
SerializedActiveThread {
|
||||
session_id: thread.session_id().0.to_string(),
|
||||
agent_type: self.selected_agent.clone(),
|
||||
title: if title.as_ref() != DEFAULT_THREAD_TITLE {
|
||||
Some(title.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
cwd: None,
|
||||
}
|
||||
});
|
||||
|
||||
self.pending_serialization = Some(cx.background_spawn(async move {
|
||||
KEY_VALUE_STORE
|
||||
.write_kvp(
|
||||
AGENT_PANEL_KEY.into(),
|
||||
serde_json::to_string(&SerializedAgentPanel {
|
||||
width,
|
||||
selected_agent: Some(selected_agent),
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
save_serialized_panel(
|
||||
workspace_id,
|
||||
SerializedAgentPanel {
|
||||
width,
|
||||
selected_agent: Some(selected_agent),
|
||||
last_active_thread,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
}));
|
||||
}
|
||||
|
|
@ -472,16 +538,18 @@ impl AgentPanel {
|
|||
Ok(prompt_store) => prompt_store.await.ok(),
|
||||
Err(_) => None,
|
||||
};
|
||||
let serialized_panel = if let Some(panel) = cx
|
||||
.background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
{
|
||||
serde_json::from_str::<SerializedAgentPanel>(&panel).log_err()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let workspace_id = workspace
|
||||
.read_with(cx, |workspace, _| workspace.database_id())
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let serialized_panel = cx
|
||||
.background_spawn(async move {
|
||||
workspace_id
|
||||
.and_then(read_serialized_panel)
|
||||
.or_else(read_legacy_serialized_panel)
|
||||
})
|
||||
.await;
|
||||
|
||||
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
||||
let text_thread_store = workspace
|
||||
|
|
@ -500,15 +568,30 @@ impl AgentPanel {
|
|||
let panel =
|
||||
cx.new(|cx| Self::new(workspace, text_thread_store, prompt_store, window, cx));
|
||||
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
if let Some(serialized_panel) = &serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
if let Some(selected_agent) = serialized_panel.selected_agent {
|
||||
if let Some(selected_agent) = serialized_panel.selected_agent.clone() {
|
||||
panel.selected_agent = selected_agent;
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(thread_info) = serialized_panel.and_then(|p| p.last_active_thread) {
|
||||
let agent_type = thread_info.agent_type.clone();
|
||||
let session_info = AgentSessionInfo {
|
||||
session_id: acp::SessionId::new(thread_info.session_id),
|
||||
cwd: thread_info.cwd,
|
||||
title: thread_info.title.map(SharedString::from),
|
||||
updated_at: None,
|
||||
meta: None,
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.selected_agent = agent_type;
|
||||
panel.load_agent_thread(session_info, window, cx);
|
||||
});
|
||||
}
|
||||
panel
|
||||
})?;
|
||||
|
||||
|
|
@ -516,7 +599,7 @@ impl AgentPanel {
|
|||
})
|
||||
}
|
||||
|
||||
fn new(
|
||||
pub(crate) fn new(
|
||||
workspace: &Workspace,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
|
|
@ -528,6 +611,7 @@ impl AgentPanel {
|
|||
let project = workspace.project();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let client = workspace.client().clone();
|
||||
let workspace_id = workspace.database_id();
|
||||
let workspace = workspace.weak_handle();
|
||||
|
||||
let context_server_registry =
|
||||
|
|
@ -633,6 +717,7 @@ impl AgentPanel {
|
|||
};
|
||||
|
||||
let mut panel = Self {
|
||||
workspace_id,
|
||||
active_view,
|
||||
workspace,
|
||||
user_store,
|
||||
|
|
@ -646,6 +731,7 @@ impl AgentPanel {
|
|||
focus_handle: cx.focus_handle(),
|
||||
context_server_registry,
|
||||
previous_view: None,
|
||||
_active_view_observation: None,
|
||||
new_thread_menu_handle: PopoverMenuHandle::default(),
|
||||
agent_panel_menu_handle: PopoverMenuHandle::default(),
|
||||
agent_navigation_menu_handle: PopoverMenuHandle::default(),
|
||||
|
|
@ -714,7 +800,7 @@ impl AgentPanel {
|
|||
&self.context_server_registry
|
||||
}
|
||||
|
||||
pub fn is_hidden(workspace: &Entity<Workspace>, cx: &App) -> bool {
|
||||
pub fn is_visible(workspace: &Entity<Workspace>, cx: &App) -> bool {
|
||||
let workspace_read = workspace.read(cx);
|
||||
|
||||
workspace_read
|
||||
|
|
@ -722,15 +808,13 @@ impl AgentPanel {
|
|||
.map(|panel| {
|
||||
let panel_id = Entity::entity_id(&panel);
|
||||
|
||||
let is_visible = workspace_read.all_docks().iter().any(|dock| {
|
||||
workspace_read.all_docks().iter().any(|dock| {
|
||||
dock.read(cx)
|
||||
.visible_panel()
|
||||
.is_some_and(|visible_panel| visible_panel.panel_id() == panel_id)
|
||||
});
|
||||
|
||||
!is_visible
|
||||
})
|
||||
})
|
||||
.unwrap_or(true)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn active_thread_view(&self) -> Option<&Entity<AcpServerView>> {
|
||||
|
|
@ -1023,6 +1107,7 @@ impl AgentPanel {
|
|||
ActiveView::Configuration | ActiveView::History { .. } => {
|
||||
if let Some(previous_view) = self.previous_view.take() {
|
||||
self.active_view = previous_view;
|
||||
cx.emit(AgentPanelEvent::ActiveViewChanged);
|
||||
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => {
|
||||
|
|
@ -1419,7 +1504,7 @@ impl AgentPanel {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
|
||||
pub fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view, .. } => thread_view
|
||||
.read(cx)
|
||||
|
|
@ -1475,9 +1560,21 @@ impl AgentPanel {
|
|||
self.active_view = new_view;
|
||||
}
|
||||
|
||||
self._active_view_observation = match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => {
|
||||
Some(cx.observe(thread_view, |this, _, cx| {
|
||||
cx.emit(AgentPanelEvent::ActiveViewChanged);
|
||||
this.serialize(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if focus {
|
||||
self.focus_handle(cx).focus(window, cx);
|
||||
}
|
||||
cx.emit(AgentPanelEvent::ActiveViewChanged);
|
||||
}
|
||||
|
||||
fn populate_recently_updated_menu_section(
|
||||
|
|
@ -1750,7 +1847,12 @@ fn agent_panel_dock_position(cx: &App) -> DockPosition {
|
|||
AgentSettings::get_global(cx).dock.into()
|
||||
}
|
||||
|
||||
pub enum AgentPanelEvent {
|
||||
ActiveViewChanged,
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for AgentPanel {}
|
||||
impl EventEmitter<AgentPanelEvent> for AgentPanel {}
|
||||
|
||||
impl Panel for AgentPanel {
|
||||
fn persistent_name() -> &'static str {
|
||||
|
|
@ -3251,7 +3353,8 @@ impl Dismissable for TrialEndUpsell {
|
|||
const KEY: &'static str = "dismissed-trial-end-upsell";
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
/// Test-only helper methods
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl AgentPanel {
|
||||
/// Opens an external thread using an arbitrary AgentServer.
|
||||
///
|
||||
|
|
@ -3284,3 +3387,196 @@ impl AgentPanel {
|
|||
self.active_thread_view()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::acp::thread_view::tests::{StubAgentServer, init_test};
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::FakeFs;
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use project::Project;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_active_thread_serialize_and_load_round_trip(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.update(|cx| {
|
||||
cx.update_flags(true, vec!["agent-v2".to_string()]);
|
||||
agent::ThreadStore::init_global(cx);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
});
|
||||
|
||||
// --- Create a MultiWorkspace window with two workspaces ---
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project_a = Project::test(fs.clone(), [], cx).await;
|
||||
let project_b = Project::test(fs, [], cx).await;
|
||||
|
||||
let multi_workspace =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
|
||||
|
||||
let workspace_a = multi_workspace
|
||||
.read_with(cx, |multi_workspace, _cx| {
|
||||
multi_workspace.workspace().clone()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let workspace_b = multi_workspace
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.test_add_workspace(project_b.clone(), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
workspace_a.update(cx, |workspace, _cx| {
|
||||
workspace.set_random_database_id();
|
||||
});
|
||||
workspace_b.update(cx, |workspace, _cx| {
|
||||
workspace.set_random_database_id();
|
||||
});
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx);
|
||||
|
||||
// --- Set up workspace A: width=300, with an active thread ---
|
||||
let panel_a = workspace_a.update_in(cx, |workspace, window, cx| {
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_a.clone(), cx));
|
||||
cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
|
||||
});
|
||||
|
||||
panel_a.update(cx, |panel, _cx| {
|
||||
panel.width = Some(px(300.0));
|
||||
});
|
||||
|
||||
panel_a.update_in(cx, |panel, window, cx| {
|
||||
panel.open_external_thread_with_server(
|
||||
Rc::new(StubAgentServer::default_response()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
panel_a.read_with(cx, |panel, cx| {
|
||||
assert!(
|
||||
panel.active_agent_thread(cx).is_some(),
|
||||
"workspace A should have an active thread after connection"
|
||||
);
|
||||
});
|
||||
|
||||
let agent_type_a = panel_a.read_with(cx, |panel, _cx| panel.selected_agent.clone());
|
||||
|
||||
// --- Set up workspace B: ClaudeCode, width=400, no active thread ---
|
||||
let panel_b = workspace_b.update_in(cx, |workspace, window, cx| {
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_b.clone(), cx));
|
||||
cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
|
||||
});
|
||||
|
||||
panel_b.update(cx, |panel, _cx| {
|
||||
panel.width = Some(px(400.0));
|
||||
panel.selected_agent = AgentType::ClaudeCode;
|
||||
});
|
||||
|
||||
// --- Serialize both panels ---
|
||||
panel_a.update(cx, |panel, cx| panel.serialize(cx));
|
||||
panel_b.update(cx, |panel, cx| panel.serialize(cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
// --- Load fresh panels for each workspace and verify independent state ---
|
||||
let prompt_builder = Arc::new(prompt_store::PromptBuilder::new(None).unwrap());
|
||||
|
||||
let async_cx = cx.update(|window, cx| window.to_async(cx));
|
||||
let loaded_a = AgentPanel::load(workspace_a.downgrade(), prompt_builder.clone(), async_cx)
|
||||
.await
|
||||
.expect("panel A load should succeed");
|
||||
cx.run_until_parked();
|
||||
|
||||
let async_cx = cx.update(|window, cx| window.to_async(cx));
|
||||
let loaded_b = AgentPanel::load(workspace_b.downgrade(), prompt_builder.clone(), async_cx)
|
||||
.await
|
||||
.expect("panel B load should succeed");
|
||||
cx.run_until_parked();
|
||||
|
||||
// Workspace A should restore its thread, width, and agent type
|
||||
loaded_a.read_with(cx, |panel, _cx| {
|
||||
assert_eq!(
|
||||
panel.width,
|
||||
Some(px(300.0)),
|
||||
"workspace A width should be restored"
|
||||
);
|
||||
assert_eq!(
|
||||
panel.selected_agent, agent_type_a,
|
||||
"workspace A agent type should be restored"
|
||||
);
|
||||
assert!(
|
||||
panel.active_thread_view().is_some(),
|
||||
"workspace A should have its active thread restored"
|
||||
);
|
||||
});
|
||||
|
||||
// Workspace B should restore its own width and agent type, with no thread
|
||||
loaded_b.read_with(cx, |panel, _cx| {
|
||||
assert_eq!(
|
||||
panel.width,
|
||||
Some(px(400.0)),
|
||||
"workspace B width should be restored"
|
||||
);
|
||||
assert_eq!(
|
||||
panel.selected_agent,
|
||||
AgentType::ClaudeCode,
|
||||
"workspace B agent type should be restored"
|
||||
);
|
||||
assert!(
|
||||
panel.active_thread_view().is_none(),
|
||||
"workspace B should have no active thread"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Simple regression test
|
||||
#[gpui::test]
|
||||
async fn test_new_text_thread_action_handler(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_flags(true, vec!["agent-v2".to_string()]);
|
||||
agent::ThreadStore::init_global(cx);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
let slash_command_registry =
|
||||
assistant_slash_command::SlashCommandRegistry::default_global(cx);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
<dyn fs::Fs>::set_global(fs.clone(), cx);
|
||||
});
|
||||
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
|
||||
let multi_workspace =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let workspace_a = multi_workspace
|
||||
.read_with(cx, |multi_workspace, _cx| {
|
||||
multi_workspace.workspace().clone()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx);
|
||||
|
||||
workspace_a.update_in(cx, |workspace, window, cx| {
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let panel =
|
||||
cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
|
||||
workspace.add_panel(panel, window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace_a.update_in(cx, |_, window, cx| {
|
||||
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ use std::any::TypeId;
|
|||
use workspace::Workspace;
|
||||
|
||||
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::agent_panel::{AgentPanel, AgentPanelEvent, ConcreteAssistantPanelDelegate};
|
||||
use crate::agent_registry_ui::AgentRegistryPage;
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
|
|
@ -422,6 +422,12 @@ fn update_command_palette_filter(cx: &mut App) {
|
|||
filter.hide_action_types(&[TypeId::of::<zed_actions::agent::ToggleAgentPane>()]);
|
||||
}
|
||||
}
|
||||
|
||||
if agent_v2_enabled {
|
||||
filter.show_namespace("multi_workspace");
|
||||
} else {
|
||||
filter.hide_namespace("multi_workspace");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2354,7 +2354,7 @@ mod tests {
|
|||
use project::Project;
|
||||
use serde_json::json;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::AppState;
|
||||
use workspace::{AppState, MultiWorkspace};
|
||||
|
||||
let app_state = cx.update(|cx| {
|
||||
let state = AppState::test(cx);
|
||||
|
|
@ -2379,8 +2379,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| workspace::Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let worktree_id = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
|
|
|
|||
|
|
@ -417,8 +417,13 @@ impl<T: 'static> PromptEditor<T> {
|
|||
|
||||
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if inline_assistant_model_supports_images(cx)
|
||||
&& let Some(task) =
|
||||
paste_images_as_context(self.editor.clone(), self.mention_set.clone(), window, cx)
|
||||
&& let Some(task) = paste_images_as_context(
|
||||
self.editor.clone(),
|
||||
self.mention_set.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
{
|
||||
task.detach();
|
||||
}
|
||||
|
|
@ -438,7 +443,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||
self.mention_set
|
||||
.update(cx, |mention_set, _cx| mention_set.remove_invalid(&snapshot));
|
||||
|
||||
if let Some(workspace) = window.root::<Workspace>().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();
|
||||
|
||||
|
|
|
|||
|
|
@ -297,8 +297,9 @@ impl MentionSet {
|
|||
self.mentions.insert(crease_id, (mention_uri, task.clone()));
|
||||
|
||||
// Notify the user if we failed to load the mentioned context
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let result = task.await.notify_async_err(cx);
|
||||
let workspace = workspace.downgrade();
|
||||
cx.spawn(async move |this, mut cx| {
|
||||
let result = task.await.notify_workspace_async_err(workspace, &mut cx);
|
||||
drop(tx);
|
||||
if result.is_none() {
|
||||
this.update(cx, |this, cx| {
|
||||
|
|
@ -644,6 +645,7 @@ pub(crate) async fn insert_images_as_context(
|
|||
images: Vec<gpui::Image>,
|
||||
editor: Entity<Editor>,
|
||||
mention_set: Entity<MentionSet>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut gpui::AsyncWindowContext,
|
||||
) {
|
||||
if images.is_empty() {
|
||||
|
|
@ -718,7 +720,11 @@ pub(crate) async fn insert_images_as_context(
|
|||
mention_set.insert_mention(crease_id, MentionUri::PastedImage, task.clone())
|
||||
});
|
||||
|
||||
if task.await.notify_async_err(cx).is_none() {
|
||||
if task
|
||||
.await
|
||||
.notify_workspace_async_err(workspace.clone(), cx)
|
||||
.is_none()
|
||||
{
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.edit([(start_anchor..end_anchor, "")], cx);
|
||||
});
|
||||
|
|
@ -732,11 +738,12 @@ pub(crate) async fn insert_images_as_context(
|
|||
pub(crate) fn paste_images_as_context(
|
||||
editor: Entity<Editor>,
|
||||
mention_set: Entity<MentionSet>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<()>> {
|
||||
let clipboard = cx.read_from_clipboard()?;
|
||||
Some(window.spawn(cx, async move |cx| {
|
||||
Some(window.spawn(cx, async move |mut cx| {
|
||||
use itertools::Itertools;
|
||||
let (mut images, paths) = clipboard
|
||||
.into_entries()
|
||||
|
|
@ -783,7 +790,7 @@ pub(crate) fn paste_images_as_context(
|
|||
})
|
||||
.ok();
|
||||
|
||||
insert_images_as_context(images, editor, mention_set, cx).await;
|
||||
insert_images_as_context(images, editor, mention_set, workspace, &mut cx).await;
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3168,6 +3168,7 @@ mod tests {
|
|||
use text::OffsetRangeExt;
|
||||
use unindent::Unindent;
|
||||
use util::path;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
|
||||
|
|
@ -3337,25 +3338,27 @@ mod tests {
|
|||
let text_thread = create_text_thread_with_messages(messages, cx);
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
let text_thread_editor = window
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
TextThreadEditor::for_text_thread(
|
||||
text_thread.clone(),
|
||||
fs,
|
||||
workspace.downgrade(),
|
||||
project,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let mut cx = VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let text_thread_editor = workspace.update_in(&mut cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
TextThreadEditor::for_text_thread(
|
||||
text_thread.clone(),
|
||||
fs,
|
||||
weak_workspace,
|
||||
project,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
(text_thread, text_thread_editor, cx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ pub enum AgentNotificationEvent {
|
|||
|
||||
impl EventEmitter<AgentNotificationEvent> for AgentNotification {}
|
||||
|
||||
impl AgentNotification {
|
||||
pub fn accept(&mut self, cx: &mut Context<Self>) {
|
||||
cx.emit(AgentNotificationEvent::Accepted);
|
||||
}
|
||||
|
||||
pub fn dismiss(&mut self, cx: &mut Context<Self>) {
|
||||
cx.emit(AgentNotificationEvent::Dismissed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AgentNotification {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
|
|
@ -174,14 +184,14 @@ impl Render for AgentNotification {
|
|||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||
.full_width()
|
||||
.on_click({
|
||||
cx.listener(move |_this, _event, _, cx| {
|
||||
cx.emit(AgentNotificationEvent::Accepted);
|
||||
cx.listener(move |this, _event, _, cx| {
|
||||
this.accept(cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(Button::new("dismiss", "Dismiss").full_width().on_click({
|
||||
cx.listener(move |_, _event, _, cx| {
|
||||
cx.emit(AgentNotificationEvent::Dismissed);
|
||||
cx.listener(move |this, _event, _, cx| {
|
||||
this.dismiss(cx);
|
||||
})
|
||||
})),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@ async fn test_channel_guests(
|
|||
cx_a.executor().run_until_parked();
|
||||
|
||||
// Client B joins channel A as a guest
|
||||
cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_b.update(|cx| {
|
||||
workspace::join_channel(channel_id, client_b.app_state.clone(), None, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// b should be following a in the shared project.
|
||||
// B is a guest,
|
||||
|
|
@ -76,9 +78,11 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
|||
.await;
|
||||
|
||||
let project_a = client_a.build_test_project(cx_a).await;
|
||||
cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.update(|cx| {
|
||||
workspace::join_channel(channel_id, client_a.app_state.clone(), None, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A shares a project in the channel
|
||||
active_call_a
|
||||
|
|
@ -88,9 +92,11 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
|||
cx_a.run_until_parked();
|
||||
|
||||
// Client B joins channel A as a guest
|
||||
cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_b.update(|cx| {
|
||||
workspace::join_channel(channel_id, client_b.app_state.clone(), None, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
|
||||
// client B opens 1.txt as a guest
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ use fs::Fs;
|
|||
use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
|
||||
use git::repository::repo_path;
|
||||
use gpui::{
|
||||
App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext,
|
||||
App, AppContext as _, Entity, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext,
|
||||
VisualTestContext,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{FakeLspAdapter, language_settings::language_settings, rust_lang};
|
||||
|
|
@ -52,7 +53,7 @@ use std::{
|
|||
use text::Point;
|
||||
use util::{path, rel_path::rel_path, uri};
|
||||
use workspace::item::Item as _;
|
||||
use workspace::{CloseIntent, Workspace};
|
||||
use workspace::{CloseIntent, MultiWorkspace, Workspace};
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_host_disconnect(
|
||||
|
|
@ -96,34 +97,46 @@ async fn test_host_disconnect(
|
|||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
|
||||
let workspace_b = cx_b.add_window(|window, cx| {
|
||||
Workspace::new(
|
||||
None,
|
||||
project_b.clone(),
|
||||
client_b.app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let window_b = cx_b.add_window(|window, cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
Workspace::new(
|
||||
None,
|
||||
project_b.clone(),
|
||||
client_b.app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
|
||||
let workspace_b_view = workspace_b.root(cx_b).unwrap();
|
||||
let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b);
|
||||
let workspace_b = window_b
|
||||
.root(cx_b)
|
||||
.unwrap()
|
||||
.read_with(cx_b, |multi_workspace, _| {
|
||||
multi_workspace.workspace().clone()
|
||||
});
|
||||
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, window, cx| {
|
||||
let editor_b: Entity<Editor> = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, rel_path("b.txt")), None, true, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
//TODO: focus
|
||||
assert!(cx_b.update_window_entity(&editor_b, |editor, window, _| editor.is_focused(window)));
|
||||
editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
|
||||
assert!(
|
||||
cx_b.update_window_entity(&editor_b, |editor: &mut Editor, window, _| editor
|
||||
.is_focused(window))
|
||||
);
|
||||
editor_b.update_in(cx_b, |editor: &mut Editor, window, cx| {
|
||||
editor.insert("X", window, cx)
|
||||
});
|
||||
|
||||
cx_b.update(|_, cx| {
|
||||
assert!(workspace_b_view.read(cx).is_edited());
|
||||
assert!(workspace_b.read(cx).is_edited());
|
||||
});
|
||||
|
||||
// Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
|
||||
|
|
@ -141,19 +154,16 @@ async fn test_host_disconnect(
|
|||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
|
||||
|
||||
// Ensure client B's edited state is reset and that the whole window is blurred.
|
||||
workspace_b
|
||||
.update(cx_b, |workspace, _, cx| {
|
||||
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
|
||||
assert!(!workspace.is_edited());
|
||||
})
|
||||
.unwrap();
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
|
||||
assert!(!workspace.is_edited());
|
||||
});
|
||||
|
||||
// Ensure client B is not prompted to save edits when closing window after disconnecting.
|
||||
let can_close = workspace_b
|
||||
.update(cx_b, |workspace, window, cx| {
|
||||
let can_close: bool = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(can_close);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use serde_json::json;
|
|||
use settings::SettingsStore;
|
||||
use text::{Point, ToPoint};
|
||||
use util::{path, rel_path::rel_path, test::sample_text};
|
||||
use workspace::{CollaboratorId, SplitDirection, Workspace, item::ItemHandle as _};
|
||||
use workspace::{CollaboratorId, MultiWorkspace, SplitDirection, Workspace, item::ItemHandle as _};
|
||||
|
||||
use super::TestClient;
|
||||
|
||||
|
|
@ -1555,9 +1555,9 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
let mut cx_b2 = VisualTestContext::from_window(window_b_project_a, cx_b);
|
||||
|
||||
let workspace_b_project_a = window_b_project_a
|
||||
.downcast::<Workspace>()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.unwrap()
|
||||
.root(cx_b)
|
||||
.read_with(cx_b, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
// assert that b is following a in project a in w.rs
|
||||
|
|
@ -1657,9 +1657,9 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
.unwrap();
|
||||
let cx_a2 = &mut VisualTestContext::from_window(window_a_project_b, cx_a);
|
||||
let workspace_a_project_b = window_a_project_b
|
||||
.downcast::<Workspace>()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.unwrap()
|
||||
.root(cx_a)
|
||||
.read_with(cx_a, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
|
@ -2144,7 +2144,7 @@ pub(crate) async fn join_channel(
|
|||
client: &TestClient,
|
||||
cx: &mut TestAppContext,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, cx))
|
||||
cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, None, cx))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use std::path::Path;
|
|||
use call::ActiveCall;
|
||||
use git::status::{FileStatus, StatusCode, TrackedStatus};
|
||||
use git_ui::project_diff::ProjectDiff;
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use gpui::{AppContext as _, TestAppContext, VisualTestContext};
|
||||
use project::ProjectPath;
|
||||
use serde_json::json;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::Workspace;
|
||||
use workspace::{MultiWorkspace, Workspace};
|
||||
|
||||
//
|
||||
use crate::TestServer;
|
||||
|
|
@ -57,17 +57,25 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
|
|||
cx_b.update(editor::init);
|
||||
cx_b.update(git_ui::init);
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let workspace_b = cx_b.add_window(|window, cx| {
|
||||
Workspace::new(
|
||||
None,
|
||||
project_b.clone(),
|
||||
client_b.app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let window_b = cx_b.add_window(|window, cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
Workspace::new(
|
||||
None,
|
||||
project_b.clone(),
|
||||
client_b.app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
|
||||
let workspace_b = workspace_b.root(cx_b).unwrap();
|
||||
let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b);
|
||||
let workspace_b = window_b
|
||||
.root(cx_b)
|
||||
.unwrap()
|
||||
.read_with(cx_b, |multi_workspace, _| {
|
||||
multi_workspace.workspace().clone()
|
||||
});
|
||||
|
||||
cx_b.update(|window, cx| {
|
||||
window
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ use editor::{Editor, EditorMode, MultiBuffer};
|
|||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs as _, RemoveOptions};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal as _, VisualContext};
|
||||
use gpui::{
|
||||
AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal as _, VisualContext as _,
|
||||
};
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{
|
||||
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
|
||||
|
|
@ -663,7 +665,7 @@ async fn test_remote_server_debugger(
|
|||
|
||||
let workspace_window = cx_a
|
||||
.window_handle()
|
||||
.downcast::<workspace::Workspace>()
|
||||
.downcast::<workspace::MultiWorkspace>()
|
||||
.unwrap();
|
||||
|
||||
let session = debugger_ui::tests::start_debug_session(&workspace_window, cx_a, |_| {}).unwrap();
|
||||
|
|
@ -671,13 +673,16 @@ async fn test_remote_server_debugger(
|
|||
debug_panel.update(cx_a, |debug_panel, cx| {
|
||||
assert_eq!(
|
||||
debug_panel.active_session().unwrap().read(cx).session(cx),
|
||||
session
|
||||
session.clone()
|
||||
)
|
||||
});
|
||||
|
||||
session.update(cx_a, |session, _| {
|
||||
assert_eq!(session.binary().unwrap().command.as_deref(), Some("mock"));
|
||||
});
|
||||
session.update(
|
||||
cx_a,
|
||||
|session: &mut project::debugger::session::Session, _| {
|
||||
assert_eq!(session.binary().unwrap().command.as_deref(), Some("mock"));
|
||||
},
|
||||
);
|
||||
|
||||
let shutdown_session = workspace.update(cx_a, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
|
|
@ -772,7 +777,7 @@ async fn test_slow_adapter_startup_retries(
|
|||
|
||||
let workspace_window = cx_a
|
||||
.window_handle()
|
||||
.downcast::<workspace::Workspace>()
|
||||
.downcast::<workspace::MultiWorkspace>()
|
||||
.unwrap();
|
||||
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
|
|
@ -804,7 +809,10 @@ async fn test_slow_adapter_startup_retries(
|
|||
.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
|
||||
let client = session.update(cx_a, |session, _| session.adapter_client().unwrap());
|
||||
let client = session.update(
|
||||
cx_a,
|
||||
|session: &mut project::debugger::session::Session, _| session.adapter_client().unwrap(),
|
||||
);
|
||||
client
|
||||
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
|
||||
reason: dap::StoppedEventReason::Pause,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ use std::{
|
|||
},
|
||||
};
|
||||
use util::path;
|
||||
use workspace::{Workspace, WorkspaceStore};
|
||||
use workspace::{MultiWorkspace, Workspace, WorkspaceStore};
|
||||
|
||||
use livekit_client::test::TestServer as LivekitTestServer;
|
||||
|
||||
|
|
@ -827,7 +827,7 @@ impl TestClient {
|
|||
channel_id: ChannelId,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
|
||||
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
|
||||
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
|
@ -881,10 +881,19 @@ impl TestClient {
|
|||
project: &Entity<Project>,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
|
||||
cx.add_window_view(|window, cx| {
|
||||
let app_state = self.app_state.clone();
|
||||
let project = project.clone();
|
||||
let window = cx.add_window(|window, cx| {
|
||||
window.activate_window();
|
||||
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
|
||||
})
|
||||
let workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
let cx = VisualTestContext::from_window(*window, cx).into_mut();
|
||||
cx.run_until_parked();
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
(workspace, cx)
|
||||
}
|
||||
|
||||
pub async fn build_test_workspace<'a>(
|
||||
|
|
@ -892,19 +901,33 @@ impl TestClient {
|
|||
cx: &'a mut TestAppContext,
|
||||
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
|
||||
let project = self.build_test_project(cx).await;
|
||||
cx.add_window_view(|window, cx| {
|
||||
let app_state = self.app_state.clone();
|
||||
let window = cx.add_window(|window, cx| {
|
||||
window.activate_window();
|
||||
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
|
||||
})
|
||||
let workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
let cx = VisualTestContext::from_window(*window, cx).into_mut();
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
(workspace, cx)
|
||||
}
|
||||
|
||||
pub fn active_workspace<'a>(
|
||||
&'a self,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
let window = cx.update(|cx| {
|
||||
cx.active_window()
|
||||
.unwrap()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let entity = window.root(cx).unwrap();
|
||||
let entity = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = VisualTestContext::from_window(*window.deref(), cx).into_mut();
|
||||
// it might be nice to try and cleanup these at the end of each test.
|
||||
(entity, cx)
|
||||
|
|
@ -915,8 +938,15 @@ pub fn open_channel_notes(
|
|||
channel_id: ChannelId,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Task<anyhow::Result<Entity<ChannelView>>> {
|
||||
let window = cx.update(|_, cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
let entity = window.root(cx).unwrap();
|
||||
let window = cx.update(|_, cx| {
|
||||
cx.active_window()
|
||||
.unwrap()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.unwrap()
|
||||
});
|
||||
let entity = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
cx.update(|window, cx| ChannelView::open(channel_id, None, entity.clone(), window, cx))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ use ui::{
|
|||
};
|
||||
use util::{ResultExt, TryFutureExt, maybe};
|
||||
use workspace::{
|
||||
CopyRoomId, Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
|
||||
CopyRoomId, Deafen, LeaveCall, MultiWorkspace, Mute, OpenChannelNotes, ScreenShare,
|
||||
ShareProject, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, NotifyResultExt},
|
||||
};
|
||||
|
|
@ -120,6 +121,7 @@ pub fn init(cx: &mut App) {
|
|||
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
|
||||
let romo_id_fut = room.read(cx).room_id();
|
||||
let workspace_handle = cx.weak_entity();
|
||||
cx.spawn(async move |workspace, cx| {
|
||||
let room_id = romo_id_fut.await.context("Failed to get livekit room")?;
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
|
|
@ -134,7 +136,7 @@ pub fn init(cx: &mut App) {
|
|||
);
|
||||
})
|
||||
})
|
||||
.detach_and_notify_err(window, cx);
|
||||
.detach_and_notify_err(workspace_handle, window, cx);
|
||||
} else {
|
||||
workspace.show_error(&"There’s no active call; join one first.", cx);
|
||||
}
|
||||
|
|
@ -2189,12 +2191,13 @@ impl CollabPanel {
|
|||
&["Remove", "Cancel"],
|
||||
cx,
|
||||
);
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn_in(window, async move |this, mut cx| {
|
||||
if answer.await? == 0 {
|
||||
channel_store
|
||||
.update(cx, |channels, _| channels.remove_channel(channel_id))
|
||||
.await
|
||||
.notify_async_err(cx);
|
||||
.notify_workspace_async_err(workspace, &mut cx);
|
||||
this.update_in(cx, |_, window, cx| cx.focus_self(window))
|
||||
.ok();
|
||||
}
|
||||
|
|
@ -2223,12 +2226,13 @@ impl CollabPanel {
|
|||
&["Remove", "Cancel"],
|
||||
cx,
|
||||
);
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn_in(window, async move |_, mut cx| {
|
||||
if answer.await? == 0 {
|
||||
user_store
|
||||
.update(cx, |store, cx| store.remove_contact(user_id, cx))
|
||||
.await
|
||||
.notify_async_err(cx);
|
||||
.notify_workspace_async_err(workspace, &mut cx);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
|
|
@ -2279,13 +2283,15 @@ impl CollabPanel {
|
|||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(handle) = window.window_handle().downcast::<Workspace>() else {
|
||||
|
||||
let Some(handle) = window.window_handle().downcast::<MultiWorkspace>() else {
|
||||
return;
|
||||
};
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
workspace.read(cx).app_state().clone(),
|
||||
Some(handle),
|
||||
Some(self.workspace.clone()),
|
||||
cx,
|
||||
)
|
||||
.detach_and_prompt_err("Failed to join channel", window, cx, |_, _, _| None)
|
||||
|
|
@ -2328,12 +2334,13 @@ impl CollabPanel {
|
|||
.full_width()
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
let client = this.client.clone();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
let workspace = this.workspace.clone();
|
||||
cx.spawn_in(window, async move |_, mut cx| {
|
||||
client
|
||||
.connect(true, cx)
|
||||
.connect(true, &mut cx)
|
||||
.await
|
||||
.into_response()
|
||||
.notify_async_err(cx);
|
||||
.notify_workspace_async_err(workspace, &mut cx);
|
||||
})
|
||||
.detach()
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ mod tests {
|
|||
use language::Point;
|
||||
use project::Project;
|
||||
use settings::KeymapFile;
|
||||
use workspace::{AppState, Workspace};
|
||||
use workspace::{AppState, MultiWorkspace, Workspace};
|
||||
|
||||
#[test]
|
||||
fn test_humanize_action_name() {
|
||||
|
|
@ -777,8 +777,9 @@ mod tests {
|
|||
.unwrap();
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let editor = cx.new_window_entity(|window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
|
|
@ -848,8 +849,9 @@ mod tests {
|
|||
async fn test_normalized_matches(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let editor = cx.new_window_entity(|window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
|
|
@ -884,8 +886,9 @@ mod tests {
|
|||
async fn test_go_to_line(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
cx.simulate_keystrokes("cmd-n");
|
||||
|
||||
|
|
@ -974,8 +977,9 @@ mod tests {
|
|||
async fn test_history_navigation_basic(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let palette = open_palette_with_history(&workspace, &["backspace", "select all"], cx);
|
||||
|
||||
|
|
@ -1017,8 +1021,9 @@ mod tests {
|
|||
async fn test_history_mode_exit_on_typing(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let palette = open_palette_with_history(&workspace, &["backspace"], cx);
|
||||
|
||||
|
|
@ -1041,8 +1046,9 @@ mod tests {
|
|||
async fn test_history_navigation_with_suggestions(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let palette = open_palette_with_history(&workspace, &["editor: close", "editor: open"], cx);
|
||||
|
||||
|
|
@ -1083,8 +1089,9 @@ mod tests {
|
|||
async fn test_history_prefix_search(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let palette = open_palette_with_history(
|
||||
&workspace,
|
||||
|
|
@ -1136,8 +1143,9 @@ mod tests {
|
|||
async fn test_history_prefix_search_no_matches(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let palette =
|
||||
open_palette_with_history(&workspace, &["open file", "backspace", "select all"], cx);
|
||||
|
|
@ -1158,8 +1166,9 @@ mod tests {
|
|||
async fn test_history_empty_prefix_searches_all(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let palette = open_palette_with_history(&workspace, &["alpha", "beta", "gamma"], cx);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ pub fn initiate_sign_out(copilot: Entity<Copilot>, 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::<Workspace>().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<Copilot>, window: &Win
|
|||
fn copilot_toast(message: Option<&'static str>, window: &Window, cx: &mut App) {
|
||||
const NOTIFICATION_ID: NotificationId = NotificationId::unique::<CopilotStatusToast>();
|
||||
|
||||
let Some(workspace) = window.root::<Workspace>().flatten() else {
|
||||
let Some(workspace) = Workspace::for_window(window, cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use anyhow::Context as _;
|
||||
use gpui::App;
|
||||
use sqlez_macros::sql;
|
||||
use util::ResultExt as _;
|
||||
|
|
@ -13,12 +14,22 @@ pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnect
|
|||
impl Domain for KeyValueStore {
|
||||
const NAME: &str = stringify!(KeyValueStore);
|
||||
|
||||
const MIGRATIONS: &[&str] = &[sql!(
|
||||
CREATE TABLE IF NOT EXISTS kv_store(
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
) STRICT;
|
||||
)];
|
||||
const MIGRATIONS: &[&str] = &[
|
||||
sql!(
|
||||
CREATE TABLE IF NOT EXISTS kv_store(
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
) STRICT;
|
||||
),
|
||||
sql!(
|
||||
CREATE TABLE IF NOT EXISTS scoped_kv_store(
|
||||
namespace TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
PRIMARY KEY(namespace, key)
|
||||
) STRICT;
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);
|
||||
|
|
@ -69,6 +80,64 @@ impl KeyValueStore {
|
|||
DELETE FROM kv_store WHERE key = (?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scoped<'a>(&'a self, namespace: &'a str) -> ScopedKeyValueStore<'a> {
|
||||
ScopedKeyValueStore {
|
||||
store: self,
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScopedKeyValueStore<'a> {
|
||||
store: &'a KeyValueStore,
|
||||
namespace: &'a str,
|
||||
}
|
||||
|
||||
impl ScopedKeyValueStore<'_> {
|
||||
pub fn read(&self, key: &str) -> anyhow::Result<Option<String>> {
|
||||
self.store.select_row_bound::<(&str, &str), String>(
|
||||
"SELECT value FROM scoped_kv_store WHERE namespace = (?) AND key = (?)",
|
||||
)?((self.namespace, key))
|
||||
.context("Failed to read from scoped_kv_store")
|
||||
}
|
||||
|
||||
pub async fn write(&self, key: String, value: String) -> anyhow::Result<()> {
|
||||
let namespace = self.namespace.to_owned();
|
||||
self.store
|
||||
.write(move |connection| {
|
||||
connection.exec_bound::<(&str, &str, &str)>(
|
||||
"INSERT OR REPLACE INTO scoped_kv_store(namespace, key, value) VALUES ((?), (?), (?))",
|
||||
)?((&namespace, &key, &value))
|
||||
.context("Failed to write to scoped_kv_store")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(&self, key: String) -> anyhow::Result<()> {
|
||||
let namespace = self.namespace.to_owned();
|
||||
self.store
|
||||
.write(move |connection| {
|
||||
connection.exec_bound::<(&str, &str)>(
|
||||
"DELETE FROM scoped_kv_store WHERE namespace = (?) AND key = (?)",
|
||||
)?((&namespace, &key))
|
||||
.context("Failed to delete from scoped_kv_store")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all(&self) -> anyhow::Result<()> {
|
||||
let namespace = self.namespace.to_owned();
|
||||
self.store
|
||||
.write(move |connection| {
|
||||
connection
|
||||
.exec_bound::<&str>("DELETE FROM scoped_kv_store WHERE namespace = (?)")?(
|
||||
&namespace,
|
||||
)
|
||||
.context("Failed to delete_all from scoped_kv_store")
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -99,6 +168,52 @@ mod tests {
|
|||
db.delete_kvp("key-1".to_string()).await.unwrap();
|
||||
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_scoped_kvp() {
|
||||
let db = KeyValueStore::open_test_db("test_scoped_kvp").await;
|
||||
|
||||
let scope_a = db.scoped("namespace-a");
|
||||
let scope_b = db.scoped("namespace-b");
|
||||
|
||||
// Reading a missing key returns None
|
||||
assert_eq!(scope_a.read("key-1").unwrap(), None);
|
||||
|
||||
// Writing and reading back a key works
|
||||
scope_a
|
||||
.write("key-1".to_string(), "value-a1".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(scope_a.read("key-1").unwrap(), Some("value-a1".to_string()));
|
||||
|
||||
// Two namespaces with the same key don't collide
|
||||
scope_b
|
||||
.write("key-1".to_string(), "value-b1".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(scope_a.read("key-1").unwrap(), Some("value-a1".to_string()));
|
||||
assert_eq!(scope_b.read("key-1").unwrap(), Some("value-b1".to_string()));
|
||||
|
||||
// delete removes a single key without affecting others in the namespace
|
||||
scope_a
|
||||
.write("key-2".to_string(), "value-a2".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
scope_a.delete("key-1".to_string()).await.unwrap();
|
||||
assert_eq!(scope_a.read("key-1").unwrap(), None);
|
||||
assert_eq!(scope_a.read("key-2").unwrap(), Some("value-a2".to_string()));
|
||||
assert_eq!(scope_b.read("key-1").unwrap(), Some("value-b1".to_string()));
|
||||
|
||||
// delete_all removes all keys in a namespace without affecting other namespaces
|
||||
scope_a
|
||||
.write("key-3".to_string(), "value-a3".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
scope_a.delete_all().await.unwrap();
|
||||
assert_eq!(scope_a.read("key-2").unwrap(), None);
|
||||
assert_eq!(scope_a.read("key-3").unwrap(), None);
|
||||
assert_eq!(scope_b.read("key-1").unwrap(), Some("value-b1".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalKeyValueStore(ThreadSafeConnection);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use project::{Project, debugger::session::Session};
|
|||
use settings::SettingsStore;
|
||||
use task::SharedTaskContext;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use workspace::Workspace;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
use crate::{debugger_panel::DebugPanel, session::DebugSession};
|
||||
|
||||
|
|
@ -52,14 +52,16 @@ pub fn init_test(cx: &mut gpui::TestAppContext) {
|
|||
pub async fn init_test_workspace(
|
||||
project: &Entity<Project>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> WindowHandle<Workspace> {
|
||||
) -> WindowHandle<MultiWorkspace> {
|
||||
let workspace_handle =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let debugger_panel = workspace_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
DebugPanel::load(this, cx).await
|
||||
.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |_workspace, cx| {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
DebugPanel::load(this, cx).await
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
|
|
@ -67,9 +69,10 @@ pub async fn init_test_workspace(
|
|||
.expect("Failed to load debug panel");
|
||||
|
||||
let terminal_panel = workspace_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.spawn_in(window, async |this, cx| {
|
||||
TerminalPanel::load(this, cx.clone()).await
|
||||
.update(cx, |multi, window, cx| {
|
||||
let weak_workspace = multi.workspace().downgrade();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
TerminalPanel::load(weak_workspace, cx.clone()).await
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
|
|
@ -77,9 +80,11 @@ pub async fn init_test_workspace(
|
|||
.expect("Failed to load terminal panel");
|
||||
|
||||
workspace_handle
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.add_panel(debugger_panel, window, cx);
|
||||
workspace.add_panel(terminal_panel, window, cx);
|
||||
.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
workspace.add_panel(debugger_panel, window, cx);
|
||||
workspace.add_panel(terminal_panel, window, cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
workspace_handle
|
||||
|
|
@ -87,39 +92,45 @@ pub async fn init_test_workspace(
|
|||
|
||||
#[track_caller]
|
||||
pub fn active_debug_session_panel(
|
||||
workspace: WindowHandle<Workspace>,
|
||||
workspace: WindowHandle<MultiWorkspace>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Entity<DebugSession> {
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
debug_panel
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap()
|
||||
.update(cx, |multi, _window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
debug_panel
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap()
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
workspace: &WindowHandle<MultiWorkspace>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
config: DebugTaskDefinition,
|
||||
configure: T,
|
||||
) -> Result<Entity<Session>> {
|
||||
let _subscription = project::debugger::test::intercept_debug_sessions(cx, configure);
|
||||
workspace.update(cx, |workspace, window, cx| {
|
||||
workspace.start_debug_session(
|
||||
config.to_scenario(),
|
||||
SharedTaskContext::default(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
workspace.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
workspace.start_debug_session(
|
||||
config.to_scenario(),
|
||||
SharedTaskContext::default(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
cx.run_until_parked();
|
||||
let session = workspace.read_with(cx, |workspace, cx| {
|
||||
workspace
|
||||
.workspace()
|
||||
.read(cx)
|
||||
.panel::<DebugPanel>(cx)
|
||||
.and_then(|panel| panel.read(cx).active_session())
|
||||
.map(|session| session.read(cx).running_state().read(cx).session())
|
||||
|
|
@ -131,7 +142,7 @@ pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
|||
}
|
||||
|
||||
pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
workspace: &WindowHandle<MultiWorkspace>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Result<Entity<Session>> {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,13 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
|||
// assert we didn't show the attach modal
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
assert!(workspace.active_modal::<AttachModal>(cx).is_none());
|
||||
assert!(
|
||||
workspace
|
||||
.workspace()
|
||||
.read(cx)
|
||||
.active_modal::<AttachModal>(cx)
|
||||
.is_none()
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -97,9 +103,9 @@ async fn test_show_attach_modal_and_select_process(
|
|||
});
|
||||
});
|
||||
let attach_modal = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let workspace_handle = cx.weak_entity();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
.update(cx, |multi, window, cx| {
|
||||
let workspace_handle = multi.workspace().downgrade();
|
||||
multi.toggle_modal(window, cx, |window, cx| {
|
||||
AttachModal::with_processes(
|
||||
workspace_handle,
|
||||
vec![
|
||||
|
|
@ -133,7 +139,7 @@ async fn test_show_attach_modal_and_select_process(
|
|||
)
|
||||
});
|
||||
|
||||
workspace.active_modal::<AttachModal>(cx).unwrap()
|
||||
multi.active_modal::<AttachModal>(cx).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -208,24 +214,26 @@ async fn test_attach_with_pick_pid_variable(executor: BackgroundExecutor, cx: &m
|
|||
|
||||
let pick_pid_placeholder = task::VariableName::PickProcessId.template_value();
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.start_debug_session(
|
||||
DebugTaskDefinition {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
label: "attach with picker".into(),
|
||||
config: json!({
|
||||
"request": "attach",
|
||||
"process_id": pick_pid_placeholder,
|
||||
}),
|
||||
tcp_connection: None,
|
||||
}
|
||||
.to_scenario(),
|
||||
SharedTaskContext::default(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
workspace.start_debug_session(
|
||||
DebugTaskDefinition {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
label: "attach with picker".into(),
|
||||
config: json!({
|
||||
"request": "attach",
|
||||
"process_id": pick_pid_placeholder,
|
||||
}),
|
||||
tcp_connection: None,
|
||||
}
|
||||
.to_scenario(),
|
||||
SharedTaskContext::default(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -145,15 +145,17 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
|||
};
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.start_debug_session(
|
||||
scenario,
|
||||
task_context.clone(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
workspace.start_debug_session(
|
||||
scenario,
|
||||
task_context.clone(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -182,8 +184,10 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -324,8 +328,10 @@ async fn test_debug_modal_subtitles_with_multiple_worktrees(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
.update(cx, |multi, window, cx| {
|
||||
multi.workspace().update(cx, |workspace, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -1113,8 +1113,8 @@ async fn test_stack_frame_filter_persistence(
|
|||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
workspace
|
||||
.update(cx, |workspace, _, _| {
|
||||
workspace.set_random_database_id();
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace.set_random_database_id(cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1211,7 +1211,7 @@ async fn test_stack_frame_filter_persistence(
|
|||
cx.run_until_parked();
|
||||
|
||||
let workspace_id = workspace
|
||||
.update(cx, |workspace, _window, _cx| workspace.database_id())
|
||||
.update(cx, |workspace, _window, cx| workspace.database_id(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("workspace id has to be some for this test to work properly");
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@ worktree.workspace = true
|
|||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
fs.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
theme.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
worktree = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
workspace = true
|
||||
|
|
@ -2,19 +2,17 @@ use std::{
|
|||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use gpui::AsyncWindowContext;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde::Deserialize;
|
||||
use settings::{DevContainerConnection, Settings as _};
|
||||
use settings::DevContainerConnection;
|
||||
use smol::{fs, process::Command};
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::Workspace;
|
||||
use worktree::Snapshot;
|
||||
|
||||
use crate::{DevContainerFeature, DevContainerSettings, DevContainerTemplate};
|
||||
use crate::{DevContainerContext, DevContainerFeature, DevContainerTemplate};
|
||||
|
||||
/// Represents a discovered devcontainer configuration
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
@ -67,6 +65,31 @@ pub(crate) struct DevContainerConfigurationOutput {
|
|||
configuration: DevContainerConfiguration,
|
||||
}
|
||||
|
||||
pub(crate) struct DevContainerCli {
|
||||
pub path: PathBuf,
|
||||
node_runtime_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl DevContainerCli {
|
||||
fn command(&self, use_podman: bool) -> Command {
|
||||
let mut command = if let Some(node_runtime_path) = &self.node_runtime_path {
|
||||
let mut command = util::command::new_smol_command(
|
||||
node_runtime_path.as_os_str().display().to_string(),
|
||||
);
|
||||
command.arg(self.path.display().to_string());
|
||||
command
|
||||
} else {
|
||||
util::command::new_smol_command(self.path.display().to_string())
|
||||
};
|
||||
|
||||
if use_podman {
|
||||
command.arg("--docker-path");
|
||||
command.arg("podman");
|
||||
}
|
||||
command
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DevContainerError {
|
||||
DockerNotAvailable,
|
||||
|
|
@ -107,87 +130,23 @@ impl Display for DevContainerError {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn read_devcontainer_configuration_for_project(
|
||||
cx: &mut AsyncWindowContext,
|
||||
node_runtime: &NodeRuntime,
|
||||
) -> Result<DevContainerConfigurationOutput, DevContainerError> {
|
||||
let (path_to_devcontainer_cli, found_in_path) = ensure_devcontainer_cli(&node_runtime).await?;
|
||||
|
||||
let Some(directory) = project_directory(cx) else {
|
||||
return Err(DevContainerError::NotInValidProject);
|
||||
};
|
||||
|
||||
devcontainer_read_configuration(
|
||||
&path_to_devcontainer_cli,
|
||||
found_in_path,
|
||||
node_runtime,
|
||||
&directory,
|
||||
None,
|
||||
use_podman(cx),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_dev_container_template(
|
||||
template: &DevContainerTemplate,
|
||||
options_selected: &HashMap<String, String>,
|
||||
features_selected: &HashSet<DevContainerFeature>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
node_runtime: &NodeRuntime,
|
||||
) -> Result<DevContainerApply, DevContainerError> {
|
||||
let (path_to_devcontainer_cli, found_in_path) = ensure_devcontainer_cli(&node_runtime).await?;
|
||||
|
||||
let Some(directory) = project_directory(cx) else {
|
||||
return Err(DevContainerError::NotInValidProject);
|
||||
};
|
||||
|
||||
devcontainer_template_apply(
|
||||
template,
|
||||
options_selected,
|
||||
features_selected,
|
||||
&path_to_devcontainer_cli,
|
||||
found_in_path,
|
||||
node_runtime,
|
||||
&directory,
|
||||
false, // devcontainer template apply does not use --docker-path option
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn use_podman(cx: &mut AsyncWindowContext) -> bool {
|
||||
cx.update(|_, cx| DevContainerSettings::get_global(cx).use_podman)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Finds all available devcontainer configurations in the project.
|
||||
///
|
||||
/// See [`find_configs_in_snapshot`] for the locations that are scanned.
|
||||
pub fn find_devcontainer_configs(cx: &mut AsyncWindowContext) -> Vec<DevContainerConfig> {
|
||||
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
|
||||
log::debug!("find_devcontainer_configs: No workspace found");
|
||||
pub fn find_devcontainer_configs(workspace: &Workspace, cx: &gpui::App) -> Vec<DevContainerConfig> {
|
||||
let project = workspace.project().read(cx);
|
||||
|
||||
let worktree = project
|
||||
.visible_worktrees(cx)
|
||||
.find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
|
||||
|
||||
let Some(worktree) = worktree else {
|
||||
log::debug!("find_devcontainer_configs: No worktree found");
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let Ok(configs) = workspace.update(cx, |workspace, _, cx| {
|
||||
let project = workspace.project().read(cx);
|
||||
|
||||
let worktree = project
|
||||
.visible_worktrees(cx)
|
||||
.find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
|
||||
|
||||
let Some(worktree) = worktree else {
|
||||
log::debug!("find_devcontainer_configs: No worktree found");
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let worktree = worktree.read(cx);
|
||||
find_configs_in_snapshot(worktree)
|
||||
}) else {
|
||||
log::debug!("find_devcontainer_configs: Failed to update workspace");
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
configs
|
||||
let worktree = worktree.read(cx);
|
||||
find_configs_in_snapshot(worktree)
|
||||
}
|
||||
|
||||
/// Scans a worktree snapshot for devcontainer configurations.
|
||||
|
|
@ -280,60 +239,36 @@ pub fn find_configs_in_snapshot(snapshot: &Snapshot) -> Vec<DevContainerConfig>
|
|||
}
|
||||
|
||||
pub async fn start_dev_container_with_config(
|
||||
cx: &mut AsyncWindowContext,
|
||||
node_runtime: NodeRuntime,
|
||||
context: DevContainerContext,
|
||||
config: Option<DevContainerConfig>,
|
||||
) -> Result<(DevContainerConnection, String), DevContainerError> {
|
||||
let use_podman = use_podman(cx);
|
||||
check_for_docker(use_podman).await?;
|
||||
check_for_docker(context.use_podman).await?;
|
||||
let cli = ensure_devcontainer_cli(&context.node_runtime).await?;
|
||||
let config_path = config.map(|c| context.project_directory.join(&c.config_path));
|
||||
|
||||
let (path_to_devcontainer_cli, found_in_path) = ensure_devcontainer_cli(&node_runtime).await?;
|
||||
|
||||
let Some(directory) = project_directory(cx) else {
|
||||
return Err(DevContainerError::NotInValidProject);
|
||||
};
|
||||
|
||||
let config_path = config.map(|c| directory.join(&c.config_path));
|
||||
|
||||
match devcontainer_up(
|
||||
&path_to_devcontainer_cli,
|
||||
found_in_path,
|
||||
&node_runtime,
|
||||
directory.clone(),
|
||||
config_path.clone(),
|
||||
use_podman,
|
||||
)
|
||||
.await
|
||||
{
|
||||
match devcontainer_up(&context, &cli, config_path.as_deref()).await {
|
||||
Ok(DevContainerUp {
|
||||
container_id,
|
||||
remote_workspace_folder,
|
||||
remote_user,
|
||||
..
|
||||
}) => {
|
||||
let project_name = match devcontainer_read_configuration(
|
||||
&path_to_devcontainer_cli,
|
||||
found_in_path,
|
||||
&node_runtime,
|
||||
&directory,
|
||||
config_path.as_ref(),
|
||||
use_podman,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(DevContainerConfigurationOutput {
|
||||
configuration:
|
||||
DevContainerConfiguration {
|
||||
name: Some(project_name),
|
||||
},
|
||||
}) => project_name,
|
||||
_ => get_backup_project_name(&remote_workspace_folder, &container_id),
|
||||
};
|
||||
let project_name =
|
||||
match read_devcontainer_configuration(&context, &cli, config_path.as_deref()).await
|
||||
{
|
||||
Ok(DevContainerConfigurationOutput {
|
||||
configuration:
|
||||
DevContainerConfiguration {
|
||||
name: Some(project_name),
|
||||
},
|
||||
}) => project_name,
|
||||
_ => get_backup_project_name(&remote_workspace_folder, &container_id),
|
||||
};
|
||||
|
||||
let connection = DevContainerConnection {
|
||||
name: project_name,
|
||||
container_id: container_id,
|
||||
use_podman,
|
||||
container_id,
|
||||
use_podman: context.use_podman,
|
||||
remote_user,
|
||||
};
|
||||
|
||||
|
|
@ -377,9 +312,9 @@ async fn check_for_docker(use_podman: bool) -> Result<(), DevContainerError> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn ensure_devcontainer_cli(
|
||||
pub(crate) async fn ensure_devcontainer_cli(
|
||||
node_runtime: &NodeRuntime,
|
||||
) -> Result<(PathBuf, bool), DevContainerError> {
|
||||
) -> Result<DevContainerCli, DevContainerError> {
|
||||
let mut command = util::command::new_smol_command(&dev_container_cli());
|
||||
command.arg("--version");
|
||||
|
||||
|
|
@ -417,7 +352,10 @@ async fn ensure_devcontainer_cli(
|
|||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
log::info!("Found devcontainer CLI in Data dir");
|
||||
return Ok((datadir_cli_path.clone(), false));
|
||||
return Ok(DevContainerCli {
|
||||
path: datadir_cli_path.clone(),
|
||||
node_runtime_path: Some(node_runtime_path.clone()),
|
||||
});
|
||||
} else {
|
||||
log::error!(
|
||||
"Could not run devcontainer CLI from data_dir. Will try once more to install. Output: {:?}",
|
||||
|
|
@ -457,32 +395,29 @@ async fn ensure_devcontainer_cli(
|
|||
);
|
||||
Err(DevContainerError::DevContainerCliNotAvailable)
|
||||
} else {
|
||||
Ok((datadir_cli_path, false))
|
||||
Ok(DevContainerCli {
|
||||
path: datadir_cli_path,
|
||||
node_runtime_path: Some(node_runtime_path),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
log::info!("Found devcontainer cli on $PATH, using it");
|
||||
Ok((PathBuf::from(&dev_container_cli()), true))
|
||||
Ok(DevContainerCli {
|
||||
path: PathBuf::from(&dev_container_cli()),
|
||||
node_runtime_path: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn devcontainer_up(
|
||||
path_to_cli: &PathBuf,
|
||||
found_in_path: bool,
|
||||
node_runtime: &NodeRuntime,
|
||||
path: Arc<Path>,
|
||||
config_path: Option<PathBuf>,
|
||||
use_podman: bool,
|
||||
context: &DevContainerContext,
|
||||
cli: &DevContainerCli,
|
||||
config_path: Option<&Path>,
|
||||
) -> Result<DevContainerUp, DevContainerError> {
|
||||
let Ok(node_runtime_path) = node_runtime.binary_path().await else {
|
||||
log::error!("Unable to find node runtime path");
|
||||
return Err(DevContainerError::NodeRuntimeNotAvailable);
|
||||
};
|
||||
|
||||
let mut command =
|
||||
devcontainer_cli_command(path_to_cli, found_in_path, &node_runtime_path, use_podman);
|
||||
let mut command = cli.command(context.use_podman);
|
||||
command.arg("up");
|
||||
command.arg("--workspace-folder");
|
||||
command.arg(path.display().to_string());
|
||||
command.arg(context.project_directory.display().to_string());
|
||||
|
||||
if let Some(config) = config_path {
|
||||
command.arg("--config");
|
||||
|
|
@ -515,24 +450,15 @@ async fn devcontainer_up(
|
|||
}
|
||||
}
|
||||
|
||||
async fn devcontainer_read_configuration(
|
||||
path_to_cli: &PathBuf,
|
||||
found_in_path: bool,
|
||||
node_runtime: &NodeRuntime,
|
||||
path: &Arc<Path>,
|
||||
config_path: Option<&PathBuf>,
|
||||
use_podman: bool,
|
||||
pub(crate) async fn read_devcontainer_configuration(
|
||||
context: &DevContainerContext,
|
||||
cli: &DevContainerCli,
|
||||
config_path: Option<&Path>,
|
||||
) -> Result<DevContainerConfigurationOutput, DevContainerError> {
|
||||
let Ok(node_runtime_path) = node_runtime.binary_path().await else {
|
||||
log::error!("Unable to find node runtime path");
|
||||
return Err(DevContainerError::NodeRuntimeNotAvailable);
|
||||
};
|
||||
|
||||
let mut command =
|
||||
devcontainer_cli_command(path_to_cli, found_in_path, &node_runtime_path, use_podman);
|
||||
let mut command = cli.command(context.use_podman);
|
||||
command.arg("read-configuration");
|
||||
command.arg("--workspace-folder");
|
||||
command.arg(path.display().to_string());
|
||||
command.arg(context.project_directory.display().to_string());
|
||||
|
||||
if let Some(config) = config_path {
|
||||
command.arg("--config");
|
||||
|
|
@ -562,23 +488,14 @@ async fn devcontainer_read_configuration(
|
|||
}
|
||||
}
|
||||
|
||||
async fn devcontainer_template_apply(
|
||||
pub(crate) async fn apply_dev_container_template(
|
||||
template: &DevContainerTemplate,
|
||||
template_options: &HashMap<String, String>,
|
||||
features_selected: &HashSet<DevContainerFeature>,
|
||||
path_to_cli: &PathBuf,
|
||||
found_in_path: bool,
|
||||
node_runtime: &NodeRuntime,
|
||||
path: &Arc<Path>,
|
||||
use_podman: bool,
|
||||
context: &DevContainerContext,
|
||||
cli: &DevContainerCli,
|
||||
) -> Result<DevContainerApply, DevContainerError> {
|
||||
let Ok(node_runtime_path) = node_runtime.binary_path().await else {
|
||||
log::error!("Unable to find node runtime path");
|
||||
return Err(DevContainerError::NodeRuntimeNotAvailable);
|
||||
};
|
||||
|
||||
let mut command =
|
||||
devcontainer_cli_command(path_to_cli, found_in_path, &node_runtime_path, use_podman);
|
||||
let mut command = cli.command(context.use_podman);
|
||||
|
||||
let Ok(serialized_options) = serde_json::to_string(template_options) else {
|
||||
log::error!("Unable to serialize options for {:?}", template_options);
|
||||
|
|
@ -588,7 +505,7 @@ async fn devcontainer_template_apply(
|
|||
command.arg("templates");
|
||||
command.arg("apply");
|
||||
command.arg("--workspace-folder");
|
||||
command.arg(path.display().to_string());
|
||||
command.arg(context.project_directory.display().to_string());
|
||||
command.arg("--template-id");
|
||||
command.arg(format!(
|
||||
"{}/{}",
|
||||
|
|
@ -652,28 +569,6 @@ fn parse_json_from_cli<T: serde::de::DeserializeOwned>(raw: &str) -> Result<T, D
|
|||
})
|
||||
}
|
||||
|
||||
fn devcontainer_cli_command(
|
||||
path_to_cli: &PathBuf,
|
||||
found_in_path: bool,
|
||||
node_runtime_path: &PathBuf,
|
||||
use_podman: bool,
|
||||
) -> Command {
|
||||
let mut command = if found_in_path {
|
||||
util::command::new_smol_command(path_to_cli.display().to_string())
|
||||
} else {
|
||||
let mut command =
|
||||
util::command::new_smol_command(node_runtime_path.as_os_str().display().to_string());
|
||||
command.arg(path_to_cli.display().to_string());
|
||||
command
|
||||
};
|
||||
|
||||
if use_podman {
|
||||
command.arg("--docker-path");
|
||||
command.arg("podman");
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
fn get_backup_project_name(remote_workspace_folder: &str, container_id: &str) -> String {
|
||||
Path::new(remote_workspace_folder)
|
||||
.file_name()
|
||||
|
|
@ -682,22 +577,6 @@ fn get_backup_project_name(remote_workspace_folder: &str, container_id: &str) ->
|
|||
.unwrap_or_else(|| container_id.to_string())
|
||||
}
|
||||
|
||||
fn project_directory(cx: &mut AsyncWindowContext) -> Option<Arc<Path>> {
|
||||
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match workspace.update(cx, |workspace, _, cx| {
|
||||
workspace.project().read(cx).active_project_directory(cx)
|
||||
}) {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
log::error!("Error getting project directory from workspace: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn template_features_to_json(features_selected: &HashSet<DevContainerFeature>) -> String {
|
||||
let features_map = features_selected
|
||||
.iter()
|
||||
|
|
@ -725,6 +604,9 @@ fn template_features_to_json(features_selected: &HashSet<DevContainerFeature>) -
|
|||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::devcontainer_api::{
|
||||
DevContainerConfig, DevContainerUp, find_configs_in_snapshot, parse_json_from_cli,
|
||||
};
|
||||
use fs::FakeFs;
|
||||
use gpui::TestAppContext;
|
||||
use project::Project;
|
||||
|
|
@ -732,10 +614,6 @@ mod tests {
|
|||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
|
||||
use crate::devcontainer_api::{
|
||||
DevContainerConfig, DevContainerUp, find_configs_in_snapshot, parse_json_from_cli,
|
||||
};
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::Path;
|
||||
|
||||
use gpui::AppContext;
|
||||
use gpui::Entity;
|
||||
use gpui::Task;
|
||||
|
|
@ -41,7 +43,8 @@ use http_client::{AsyncBody, HttpClient};
|
|||
|
||||
mod devcontainer_api;
|
||||
|
||||
use devcontainer_api::read_devcontainer_configuration_for_project;
|
||||
use devcontainer_api::ensure_devcontainer_cli;
|
||||
use devcontainer_api::read_devcontainer_configuration;
|
||||
|
||||
use crate::devcontainer_api::DevContainerError;
|
||||
use crate::devcontainer_api::apply_dev_container_template;
|
||||
|
|
@ -51,11 +54,34 @@ pub use devcontainer_api::{
|
|||
start_dev_container_with_config,
|
||||
};
|
||||
|
||||
pub struct DevContainerContext {
|
||||
pub project_directory: Arc<Path>,
|
||||
pub use_podman: bool,
|
||||
pub node_runtime: node_runtime::NodeRuntime,
|
||||
}
|
||||
|
||||
impl DevContainerContext {
|
||||
pub fn from_workspace(workspace: &Workspace, cx: &App) -> Option<Self> {
|
||||
let project_directory = workspace.project().read(cx).active_project_directory(cx)?;
|
||||
let use_podman = DevContainerSettings::get_global(cx).use_podman;
|
||||
let node_runtime = workspace.app_state().node_runtime.clone();
|
||||
Some(Self {
|
||||
project_directory,
|
||||
use_podman,
|
||||
node_runtime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RegisterSetting)]
|
||||
struct DevContainerSettings {
|
||||
use_podman: bool,
|
||||
}
|
||||
|
||||
pub fn use_podman(cx: &App) -> bool {
|
||||
DevContainerSettings::get_global(cx).use_podman
|
||||
}
|
||||
|
||||
impl Settings for DevContainerSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
Self {
|
||||
|
|
@ -1420,22 +1446,41 @@ fn dispatch_apply_templates(
|
|||
cx: &mut Context<DevContainerModal>,
|
||||
) {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Some(tree_id) = workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let worktree = project.read(cx).visible_worktrees(cx).find_map(|tree| {
|
||||
tree.read(cx)
|
||||
.root_entry()?
|
||||
.is_dir()
|
||||
.then_some(tree.read(cx))
|
||||
});
|
||||
worktree.map(|w| w.id())
|
||||
}) {
|
||||
let node_runtime = workspace.read_with(cx, |workspace, _| {
|
||||
workspace.app_state().node_runtime.clone()
|
||||
});
|
||||
let Some((tree_id, context)) = workspace.update(cx, |workspace, cx| {
|
||||
let worktree = workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
.find_map(|tree| {
|
||||
tree.read(cx)
|
||||
.root_entry()?
|
||||
.is_dir()
|
||||
.then_some(tree.read(cx))
|
||||
});
|
||||
let tree_id = worktree.map(|w| w.id())?;
|
||||
let context = DevContainerContext::from_workspace(workspace, cx)?;
|
||||
Some((tree_id, context))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(cli) = ensure_devcontainer_cli(&context.node_runtime).await else {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.accept_message(
|
||||
DevContainerMessage::FailedToWriteTemplate(
|
||||
DevContainerError::DevContainerCliNotAvailable,
|
||||
),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
return;
|
||||
};
|
||||
|
||||
{
|
||||
if check_for_existing
|
||||
&& read_devcontainer_configuration_for_project(cx, &node_runtime)
|
||||
&& read_devcontainer_configuration(&context, &cli, None)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
|
|
@ -1454,8 +1499,8 @@ fn dispatch_apply_templates(
|
|||
&template_entry.template,
|
||||
&template_entry.options_selected,
|
||||
&template_entry.features_selected,
|
||||
cx,
|
||||
&node_runtime,
|
||||
&context,
|
||||
&cli,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
@ -1497,8 +1542,6 @@ fn dispatch_apply_templates(
|
|||
this.dismiss(&menu::Cancel, window, cx);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
|
|
|||
|
|
@ -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::<Workspace>().flatten() {
|
||||
if let Some(workspace) = Workspace::for_window(window, cx) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_path(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use std::{
|
|||
};
|
||||
use unindent::Unindent as _;
|
||||
use util::{RandomCharIter, path, post_inc, rel_path::rel_path};
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
|
|
@ -68,9 +69,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
let language_server_id = LanguageServerId(0);
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let uri = lsp::Uri::from_file_path(path!("/test/main.rs")).unwrap();
|
||||
|
||||
// Create some diagnostics
|
||||
|
|
@ -344,9 +347,11 @@ async fn test_diagnostics_with_folds(cx: &mut TestAppContext) {
|
|||
let server_id_2 = LanguageServerId(101);
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let diagnostics = window.build_entity(cx, |window, cx| {
|
||||
ProjectDiagnosticsEditor::new(true, project.clone(), workspace.downgrade(), window, cx)
|
||||
|
|
@ -453,9 +458,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
let server_id_2 = LanguageServerId(101);
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let diagnostics = window.build_entity(cx, |window, cx| {
|
||||
ProjectDiagnosticsEditor::new(true, project.clone(), workspace.downgrade(), window, cx)
|
||||
|
|
@ -663,9 +670,11 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng
|
|||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let mutated_diagnostics = window.build_entity(cx, |window, cx| {
|
||||
ProjectDiagnosticsEditor::new(true, project.clone(), workspace.downgrade(), window, cx)
|
||||
|
|
@ -836,9 +845,11 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
|
|||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let mutated_diagnostics = window.build_entity(cx, |window, cx| {
|
||||
ProjectDiagnosticsEditor::new(true, project.clone(), workspace.downgrade(), window, cx)
|
||||
|
|
@ -1389,9 +1400,11 @@ async fn test_diagnostics_with_code(cx: &mut TestAppContext) {
|
|||
let language_server_id = LanguageServerId(0);
|
||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let uri = lsp::Uri::from_file_path(path!("/root/main.js")).unwrap();
|
||||
|
||||
// Create diagnostics with code fields
|
||||
|
|
@ -1618,8 +1631,8 @@ async fn test_buffer_diagnostics(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let project_path = project::ProjectPath {
|
||||
worktree_id: project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
@ -1772,8 +1785,8 @@ async fn test_buffer_diagnostics_without_warnings(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let project_path = project::ProjectPath {
|
||||
worktree_id: project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
@ -1901,8 +1914,8 @@ async fn test_buffer_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let project_path = project::ProjectPath {
|
||||
worktree_id: project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
|
|||
|
|
@ -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::<Workspace>().flatten() {
|
||||
if let Some(workspace) = Workspace::for_window(window, cx) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let copilot = copilot.clone();
|
||||
workspace.show_toast(
|
||||
|
|
|
|||
|
|
@ -415,14 +415,14 @@ mod tests {
|
|||
};
|
||||
|
||||
use futures::StreamExt;
|
||||
use gpui::{Rgba, TestAppContext, VisualTestContext};
|
||||
use gpui::{Rgba, TestAppContext};
|
||||
use language::FakeLspAdapter;
|
||||
use languages::rust_lang;
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{
|
||||
CloseActiveItem, MoveItemToPaneInDirection, OpenOptions,
|
||||
CloseActiveItem, MoveItemToPaneInDirection, MultiWorkspace, OpenOptions,
|
||||
item::{Item as _, SaveOptions},
|
||||
};
|
||||
|
||||
|
|
@ -460,9 +460,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| workspace::Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
|
|
@ -490,7 +490,7 @@ mod tests {
|
|||
);
|
||||
|
||||
let editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from(path!("/a/first.rs")),
|
||||
OpenOptions::default(),
|
||||
|
|
@ -498,7 +498,6 @@ mod tests {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
|
|
@ -579,53 +578,49 @@ mod tests {
|
|||
});
|
||||
|
||||
// opening another file in a split should not influence the LSP query counter
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(
|
||||
workspace.panes().len(),
|
||||
1,
|
||||
"Should have one pane with one editor"
|
||||
);
|
||||
workspace.move_item_to_pane_in_direction(
|
||||
&MoveItemToPaneInDirection {
|
||||
direction: workspace::SplitDirection::Right,
|
||||
focus: false,
|
||||
clone: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
assert_eq!(
|
||||
workspace.panes().len(),
|
||||
1,
|
||||
"Should have one pane with one editor"
|
||||
);
|
||||
workspace.move_item_to_pane_in_direction(
|
||||
&MoveItemToPaneInDirection {
|
||||
direction: workspace::SplitDirection::Right,
|
||||
focus: false,
|
||||
clone: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
let panes = workspace.panes();
|
||||
assert_eq!(panes.len(), 2, "Should have two panes after splitting");
|
||||
for pane in panes {
|
||||
let editor = pane
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<Editor>())
|
||||
.expect("Should have opened an editor in each split");
|
||||
let editor_file = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.expect("test deals with singleton buffers")
|
||||
.read(cx)
|
||||
.file()
|
||||
.expect("test buffese should have a file")
|
||||
.path();
|
||||
assert_eq!(
|
||||
editor_file.as_ref(),
|
||||
rel_path("first.rs"),
|
||||
"Both editors should be opened for the same file"
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, _, cx| {
|
||||
let panes = workspace.panes();
|
||||
assert_eq!(panes.len(), 2, "Should have two panes after splitting");
|
||||
for pane in panes {
|
||||
let editor = pane
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<Editor>())
|
||||
.expect("Should have opened an editor in each split");
|
||||
let editor_file = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.expect("test deals with singleton buffers")
|
||||
.read(cx)
|
||||
.file()
|
||||
.expect("test buffese should have a file")
|
||||
.path();
|
||||
assert_eq!(
|
||||
editor_file.as_ref(),
|
||||
rel_path("first.rs"),
|
||||
"Both editors should be opened for the same file"
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(500));
|
||||
let save = editor.update_in(cx, |editor, window, cx| {
|
||||
|
|
@ -652,54 +647,44 @@ mod tests {
|
|||
);
|
||||
|
||||
drop(editor);
|
||||
let close = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.close_active_item(&CloseActiveItem::default(), window, cx)
|
||||
})
|
||||
let close = workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.close_active_item(&CloseActiveItem::default(), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
close.await.unwrap();
|
||||
let close = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.close_active_item(&CloseActiveItem::default(), window, cx)
|
||||
})
|
||||
let close = workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.close_active_item(&CloseActiveItem::default(), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
close.await.unwrap();
|
||||
assert_eq!(
|
||||
2,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After saving and closing all editors, no extra requests should be made"
|
||||
);
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
assert!(
|
||||
workspace.active_item(cx).is_none(),
|
||||
"Should close all editors"
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, _, cx| {
|
||||
assert!(
|
||||
workspace.active_item(cx).is_none(),
|
||||
"Should close all editors"
|
||||
)
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.navigate_backward(&workspace::GoBack, window, cx);
|
||||
})
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.navigate_backward(&workspace::GoBack, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
cx.executor().advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
|
||||
cx.run_until_parked();
|
||||
let editor = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.expect("Should have reopened the editor again after navigating back")
|
||||
.downcast::<Editor>()
|
||||
.expect("Should be an editor")
|
||||
})
|
||||
.unwrap();
|
||||
let editor = workspace.update_in(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.expect("Should have reopened the editor again after navigating back")
|
||||
.downcast::<Editor>()
|
||||
.expect("Should be an editor")
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
2,
|
||||
|
|
|
|||
|
|
@ -3114,6 +3114,24 @@ impl Editor {
|
|||
self.workspace.as_ref()?.0.upgrade()
|
||||
}
|
||||
|
||||
/// Detaches a task and shows an error notification in the workspace if available,
|
||||
/// otherwise just logs the error.
|
||||
pub fn detach_and_notify_err<R, E>(
|
||||
&self,
|
||||
task: Task<Result<R, E>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) where
|
||||
E: std::fmt::Debug + std::fmt::Display + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
if let Some(workspace) = self.workspace() {
|
||||
task.detach_and_notify_err(workspace.downgrade(), window, cx);
|
||||
} else {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the workspace serialization ID if this editor should be serialized.
|
||||
fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
|
||||
self.workspace
|
||||
|
|
@ -11481,8 +11499,8 @@ impl Editor {
|
|||
let Some(project) = self.project.clone() else {
|
||||
return;
|
||||
};
|
||||
self.reload(project, window, cx)
|
||||
.detach_and_notify_err(window, cx);
|
||||
let task = self.reload(project, window, cx);
|
||||
self.detach_and_notify_err(task, window, cx);
|
||||
}
|
||||
|
||||
pub fn restore_file(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -99,7 +99,6 @@ use workspace::{
|
|||
CollaboratorId, ItemHandle, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel,
|
||||
Workspace,
|
||||
item::{BreadcrumbText, Item, ItemBufferKind},
|
||||
notifications::NotifyTaskExt,
|
||||
};
|
||||
|
||||
/// Determines what kinds of highlights should be applied to a lines background.
|
||||
|
|
@ -541,21 +540,21 @@ impl EditorElement {
|
|||
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.format(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.format_selections(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.organize_imports(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
|
|
@ -565,49 +564,49 @@ impl EditorElement {
|
|||
register_action(editor, window, Editor::show_character_palette);
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_completion(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.compose_completion(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_code_action(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.rename(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_rename(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
editor.detach_and_notify_err(task, window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::<Workspace>().flatten()
|
||||
&& let Some(workspace) = Workspace::for_window(window, cx)
|
||||
{
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let task = workspace.open_abs_path(
|
||||
|
|
|
|||
|
|
@ -2017,6 +2017,7 @@ fn restore_serialized_buffer_contents(
|
|||
mod tests {
|
||||
use crate::editor_tests::init_test;
|
||||
use fs::Fs;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
use super::*;
|
||||
use fs::MTime;
|
||||
|
|
@ -2071,8 +2072,10 @@ mod tests {
|
|||
// Test case 1: Deserialize with path and contents
|
||||
{
|
||||
let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
|
||||
let item_id = 1234 as ItemId;
|
||||
let mtime = fs
|
||||
|
|
@ -2108,8 +2111,10 @@ mod tests {
|
|||
// Test case 2: Deserialize with only path
|
||||
{
|
||||
let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
|
||||
|
||||
|
|
@ -2146,8 +2151,10 @@ mod tests {
|
|||
project.languages().add(languages::rust_lang())
|
||||
});
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
|
||||
|
||||
|
|
@ -2182,8 +2189,10 @@ mod tests {
|
|||
// Test case 4: Deserialize with path, content, and old mtime
|
||||
{
|
||||
let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
|
||||
|
||||
|
|
@ -2212,8 +2221,10 @@ mod tests {
|
|||
// Test case 5: Deserialize with no path, no content, no language, and no old mtime (new, empty, unsaved buffer)
|
||||
{
|
||||
let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
|
||||
|
||||
|
|
@ -2252,8 +2263,10 @@ mod tests {
|
|||
|
||||
// Create an empty project with no worktrees
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
|
||||
let item_id = 11000 as ItemId;
|
||||
|
|
|
|||
|
|
@ -417,14 +417,12 @@ fn convert_token(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
ops::{Deref as _, Range},
|
||||
ops::Range,
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{
|
||||
AppContext as _, Entity, Focusable as _, HighlightStyle, TestAppContext, VisualTestContext,
|
||||
};
|
||||
use gpui::{AppContext as _, Entity, Focusable as _, HighlightStyle, TestAppContext};
|
||||
use language::{Language, LanguageConfig, LanguageMatcher};
|
||||
use languages::FakeLspAdapter;
|
||||
use multi_buffer::{
|
||||
|
|
@ -434,7 +432,7 @@ mod tests {
|
|||
use rope::Point;
|
||||
use serde_json::json;
|
||||
use settings::{LanguageSettingsContent, SemanticTokenRules, SemanticTokens, SettingsStore};
|
||||
use workspace::{Workspace, WorkspaceHandle as _};
|
||||
use workspace::{MultiWorkspace, WorkspaceHandle as _};
|
||||
|
||||
use crate::{
|
||||
Capability,
|
||||
|
|
@ -854,12 +852,11 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(EditorLspTestContext::root_path(), true, cx)
|
||||
})
|
||||
.await
|
||||
|
|
@ -869,7 +866,7 @@ mod tests {
|
|||
|
||||
let toml_file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let toml_item = workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(toml_file, None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
|
|
@ -881,7 +878,7 @@ mod tests {
|
|||
.expect("Opened test file wasn't an editor")
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
let nav_history = workspace
|
||||
.read(cx)
|
||||
.active_pane()
|
||||
|
|
@ -895,11 +892,11 @@ mod tests {
|
|||
let _toml_server_2 = toml_server_2.next().await.unwrap();
|
||||
|
||||
// Trigger semantic tokens.
|
||||
editor.update_in(&mut cx, |editor, _, cx| {
|
||||
editor.update_in(cx, |editor, _, cx| {
|
||||
editor.edit([(MultiBufferOffset(0)..MultiBufferOffset(1), "b")], cx);
|
||||
});
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
let task = editor.update_in(&mut cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
cx.run_until_parked();
|
||||
task.await;
|
||||
|
||||
|
|
@ -1074,12 +1071,11 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(EditorLspTestContext::root_path(), true, cx)
|
||||
})
|
||||
.await
|
||||
|
|
@ -1089,7 +1085,7 @@ mod tests {
|
|||
|
||||
let toml_file = cx.read(|cx| workspace.file_project_paths(cx)[1].clone());
|
||||
let rust_file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let (toml_item, rust_item) = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
let (toml_item, rust_item) = workspace.update_in(cx, |workspace, window, cx| {
|
||||
(
|
||||
workspace.open_path(toml_file, None, true, window, cx),
|
||||
workspace.open_path(rust_file, None, true, window, cx),
|
||||
|
|
@ -1139,12 +1135,12 @@ mod tests {
|
|||
multibuffer
|
||||
});
|
||||
|
||||
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
let editor = workspace.update_in(cx, |workspace, window, cx| {
|
||||
let editor = cx.new(|cx| build_editor_with_project(project, multibuffer, window, cx));
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
|
||||
editor
|
||||
});
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
let nav_history = workspace
|
||||
.read(cx)
|
||||
.active_pane()
|
||||
|
|
@ -1159,7 +1155,7 @@ mod tests {
|
|||
|
||||
// Initial request.
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
let task = editor.update_in(&mut cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
cx.run_until_parked();
|
||||
task.await;
|
||||
assert_eq!(full_counter_toml.load(atomic::Ordering::Acquire), 1);
|
||||
|
|
@ -1174,8 +1170,8 @@ mod tests {
|
|||
|
||||
// Get the excerpt id for the TOML excerpt and expand it down by 2 lines.
|
||||
let toml_excerpt_id =
|
||||
editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).excerpt_ids()[0]);
|
||||
editor.update_in(&mut cx, |editor, _, cx| {
|
||||
editor.read_with(cx, |editor, cx| editor.buffer().read(cx).excerpt_ids()[0]);
|
||||
editor.update_in(cx, |editor, _, cx| {
|
||||
editor.buffer().update(cx, |buffer, cx| {
|
||||
buffer.expand_excerpts([toml_excerpt_id], 2, ExpandExcerptDirection::Down, cx);
|
||||
});
|
||||
|
|
@ -1183,7 +1179,7 @@ mod tests {
|
|||
|
||||
// Wait for semantic tokens to be re-fetched after expansion.
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
let task = editor.update_in(&mut cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
cx.run_until_parked();
|
||||
task.await;
|
||||
|
||||
|
|
@ -1306,12 +1302,11 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(EditorLspTestContext::root_path(), true, cx)
|
||||
})
|
||||
.await
|
||||
|
|
@ -1321,7 +1316,7 @@ mod tests {
|
|||
|
||||
let toml_file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let toml_item = workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(toml_file, None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
|
|
@ -1355,10 +1350,10 @@ mod tests {
|
|||
multibuffer
|
||||
});
|
||||
|
||||
let editor = workspace.update_in(&mut cx, |_, window, cx| {
|
||||
let editor = workspace.update_in(cx, |_, window, cx| {
|
||||
cx.new(|cx| build_editor_with_project(project, multibuffer, window, cx))
|
||||
});
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
let nav_history = workspace
|
||||
.read(cx)
|
||||
.active_pane()
|
||||
|
|
@ -1372,7 +1367,7 @@ mod tests {
|
|||
|
||||
// Initial request.
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
let task = editor.update_in(&mut cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
cx.run_until_parked();
|
||||
task.await;
|
||||
assert_eq!(full_counter_toml.load(atomic::Ordering::Acquire), 1);
|
||||
|
|
@ -1381,12 +1376,12 @@ mod tests {
|
|||
//
|
||||
// Without debouncing, this grabs semantic tokens 4 times (twice for the
|
||||
// toml editor, and twice for the multibuffer).
|
||||
editor.update_in(&mut cx, |editor, _, cx| {
|
||||
editor.update_in(cx, |editor, _, cx| {
|
||||
editor.edit([(MultiBufferOffset(0)..MultiBufferOffset(1), "b")], cx);
|
||||
editor.edit([(MultiBufferOffset(12)..MultiBufferOffset(13), "c")], cx);
|
||||
});
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
let task = editor.update_in(&mut cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
|
||||
cx.run_until_parked();
|
||||
task.await;
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -2087,7 +2087,7 @@ mod tests {
|
|||
use rand::rngs::StdRng;
|
||||
use settings::{DiffViewStyle, SettingsStore};
|
||||
use ui::{VisualContext as _, div, px};
|
||||
use workspace::Workspace;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
use crate::SplittableEditor;
|
||||
use crate::display_map::{BlockPlacement, BlockProperties, BlockStyle};
|
||||
|
|
@ -2105,8 +2105,9 @@ mod tests {
|
|||
crate::init(cx);
|
||||
});
|
||||
let project = Project::test(FakeFs::new(cx.executor()), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let rhs_multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ pub struct AgentV2FeatureFlag;
|
|||
|
||||
impl FeatureFlag for AgentV2FeatureFlag {
|
||||
const NAME: &'static str = "agent-v2";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcpBetaFeatureFlag;
|
||||
|
|
|
|||
|
|
@ -1566,9 +1566,12 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
.unwrap_or(0)
|
||||
.saturating_sub(1);
|
||||
let finder = self.file_finder.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
let item = open_task.await.notify_async_err(cx)?;
|
||||
cx.spawn_in(window, async move |_, mut cx| {
|
||||
let item = open_task
|
||||
.await
|
||||
.notify_workspace_async_err(workspace, &mut cx)?;
|
||||
if let Some(row) = row
|
||||
&& let Some(active_editor) = item.downcast::<Editor>()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use project::{FS_WATCH_LATENCY, RemoveOptions};
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{AppState, CloseActiveItem, OpenOptions, ToggleFileFinder, Workspace, open_paths};
|
||||
use workspace::{
|
||||
AppState, CloseActiveItem, MultiWorkspace, OpenOptions, ToggleFileFinder, Workspace, open_paths,
|
||||
};
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
|
|
@ -1109,7 +1111,9 @@ async fn test_history_items_uniqueness_for_multiple_worktree(cx: &mut TestAppCon
|
|||
)
|
||||
.await;
|
||||
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let (worktree_id1, worktree_id2) = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
|
||||
|
|
@ -1207,7 +1211,9 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) {
|
|||
)
|
||||
.await;
|
||||
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let (_worktree_id1, worktree_id2) = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
|
||||
|
|
@ -1282,7 +1288,9 @@ async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppCon
|
|||
)
|
||||
.await;
|
||||
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let (_worktree_id1, worktree_id2) = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
|
||||
|
|
@ -1334,7 +1342,9 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let worktree_id = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
|
|
@ -1423,7 +1433,9 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
assert_eq!(worktrees.len(), 1);
|
||||
|
|
@ -1565,7 +1577,9 @@ async fn test_history_match_positions(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
workspace.update_in(cx, |_workspace, window, cx| window.focused(cx));
|
||||
|
||||
|
|
@ -1642,7 +1656,9 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
|
|||
.detach();
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
assert_eq!(worktrees.len(), 1,);
|
||||
|
|
@ -1741,7 +1757,9 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
// generate some history to select from
|
||||
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
|
||||
|
|
@ -1797,7 +1815,9 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
assert_eq!(worktrees.len(), 1,);
|
||||
|
|
@ -1903,7 +1923,9 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
// generate some history to select from
|
||||
open_close_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
|
||||
open_close_queried_buffer("2", 1, "2_second", &workspace, cx).await;
|
||||
|
|
@ -1957,7 +1979,9 @@ async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppCon
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
// Open new buffer
|
||||
open_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
|
||||
|
||||
|
|
@ -1991,7 +2015,9 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
|
||||
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
|
||||
|
|
@ -2099,7 +2125,9 @@ async fn test_setting_auto_select_first_and_select_active_file(cx: &mut TestAppC
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
|
||||
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
|
||||
|
|
@ -2155,7 +2183,9 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
|
||||
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
|
||||
|
|
@ -2250,7 +2280,9 @@ async fn test_history_items_shown_in_order_of_open(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
|
||||
|
|
@ -2308,7 +2340,9 @@ async fn test_selected_history_item_stays_selected_on_worktree_updated(cx: &mut
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_close_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
open_close_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
|
||||
|
|
@ -2369,7 +2403,9 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
// generate some history to select from
|
||||
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
|
||||
open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
|
||||
|
|
@ -2414,7 +2450,9 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); // generate some history to select from
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); // generate some history to select from
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
|
||||
open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
|
||||
open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
|
||||
|
|
@ -2462,8 +2500,9 @@ async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAp
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
|
|
@ -2534,8 +2573,14 @@ async fn test_search_results_refreshed_on_standalone_file_creation(cx: &mut gpui
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let window = cx.add_window({
|
||||
let project = project.clone();
|
||||
|window, cx| MultiWorkspace::test_new(project, window, cx)
|
||||
});
|
||||
let cx = VisualTestContext::from_window(*window, cx).into_mut();
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
cx.update(|_, cx| {
|
||||
open_paths(
|
||||
|
|
@ -2589,8 +2634,9 @@ async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_1_id = project.update(cx, |project, cx| {
|
||||
let worktree = project.worktrees(cx).last().expect("worktree not found");
|
||||
worktree.read(cx).id()
|
||||
|
|
@ -2680,7 +2726,9 @@ async fn test_history_items_uniqueness_for_multiple_worktree_open_all_files(
|
|||
)
|
||||
.await;
|
||||
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let (worktree_id1, worktree_id2) = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
|
||||
|
|
@ -2804,8 +2852,9 @@ async fn test_selected_match_stays_selected_after_matches_refreshed(cx: &mut gpu
|
|||
}
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
|
|
@ -2863,8 +2912,9 @@ async fn test_first_match_selected_if_previous_one_is_not_in_the_match_list(
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
|
|
@ -2902,7 +2952,9 @@ async fn test_keeps_file_finder_open_after_modifier_keys_release(cx: &mut gpui::
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
|
||||
|
|
@ -2930,7 +2982,9 @@ async fn test_opens_file_on_modifier_keys_release(cx: &mut gpui::TestAppContext)
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
|
||||
|
|
@ -2970,7 +3024,9 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
|
||||
|
|
@ -3026,7 +3082,9 @@ async fn test_switches_between_release_norelease_modes_on_backward_nav(
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
|
||||
|
|
@ -3081,7 +3139,9 @@ async fn test_extending_modifiers_does_not_confirm_selection(cx: &mut gpui::Test
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
|
||||
|
||||
|
|
@ -3112,7 +3172,9 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
let picker = active_file_picker(&workspace, cx);
|
||||
|
|
@ -3231,7 +3293,9 @@ fn build_find_picker(
|
|||
Entity<Workspace>,
|
||||
&mut VisualTestContext,
|
||||
) {
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
(picker, workspace, cx)
|
||||
}
|
||||
|
|
@ -3469,7 +3533,9 @@ async fn test_clear_navigation_history(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
workspace.update_in(cx, |_workspace, window, cx| window.focused(cx));
|
||||
|
||||
|
|
|
|||
|
|
@ -1295,6 +1295,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
|
|
@ -1347,13 +1348,17 @@ mod tests {
|
|||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let branch_list = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let branch_list = window_handle
|
||||
.update(cx, |_multi_workspace, window, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut delegate = BranchListDelegate::new(
|
||||
workspace.weak_handle(),
|
||||
workspace.downgrade(),
|
||||
repository,
|
||||
BranchListStyle::Modal,
|
||||
cx,
|
||||
|
|
@ -1380,7 +1385,7 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
let cx = VisualTestContext::from_window(*workspace, cx);
|
||||
let cx = VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
(branch_list, cx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -774,7 +774,7 @@ impl CommitView {
|
|||
callback(repo, &sha, stash, commit_view_entity, workspace_weak, cx).await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_notify_err(window, cx);
|
||||
.detach_and_notify_err(workspace.weak_handle(), window, cx);
|
||||
}
|
||||
|
||||
async fn close_commit_view(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use editor::{Editor, EditorEvent, MultiBuffer};
|
|||
use futures::{FutureExt, select_biased};
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, IntoElement, Render, Task, Window,
|
||||
Focusable, IntoElement, Render, Task, WeakEntity, Window,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use project::Project;
|
||||
|
|
@ -40,11 +40,10 @@ impl FileDiffView {
|
|||
pub fn open(
|
||||
old_path: PathBuf,
|
||||
new_path: PathBuf,
|
||||
workspace: &Workspace,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let workspace = workspace.weak_handle();
|
||||
window.spawn(cx, async move |cx| {
|
||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
||||
let old_buffer = project
|
||||
|
|
@ -374,7 +373,7 @@ mod tests {
|
|||
use std::path::PathBuf;
|
||||
use unindent::unindent;
|
||||
use util::path;
|
||||
use workspace::Workspace;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
|
|
@ -400,15 +399,16 @@ mod tests {
|
|||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let diff_view = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
FileDiffView::open(
|
||||
path!("/test/old_file.txt").into(),
|
||||
path!("/test/new_file.txt").into(),
|
||||
workspace,
|
||||
workspace.weak_handle(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
@ -534,15 +534,16 @@ mod tests {
|
|||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let diff_view = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
FileDiffView::open(
|
||||
PathBuf::from(path!("/test/old_file.txt")),
|
||||
PathBuf::from(path!("/test/new_file.txt")),
|
||||
workspace,
|
||||
workspace.weak_handle(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1274,10 +1274,11 @@ impl GitPanel {
|
|||
})
|
||||
.ok()?;
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn_in(window, async move |_, mut cx| {
|
||||
let item = open_task
|
||||
.await
|
||||
.notify_async_err(&mut cx)
|
||||
.notify_workspace_async_err(workspace, &mut cx)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to open file"))?;
|
||||
if let Some(active_editor) = item.downcast::<Editor>() {
|
||||
if let Some(diff_task) =
|
||||
|
|
@ -6262,6 +6263,8 @@ mod tests {
|
|||
use util::path;
|
||||
use util::rel_path::rel_path;
|
||||
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
|
|
@ -6308,9 +6311,12 @@ mod tests {
|
|||
|
||||
let project =
|
||||
Project::test(fs.clone(), [path!("/root/zed/crates/gpui").as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
cx.read(|cx| {
|
||||
project
|
||||
|
|
@ -6327,7 +6333,7 @@ mod tests {
|
|||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
let handle = cx.update_window_entity(&panel, |panel, _, _| {
|
||||
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
|
||||
|
|
@ -6429,9 +6435,12 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
cx.read(|cx| {
|
||||
project
|
||||
|
|
@ -6448,7 +6457,7 @@ mod tests {
|
|||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
let handle = cx.update_window_entity(&panel, |panel, _, _| {
|
||||
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
|
||||
|
|
@ -6621,9 +6630,12 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
cx.read(|cx| {
|
||||
project
|
||||
|
|
@ -6640,7 +6652,7 @@ mod tests {
|
|||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
let handle = cx.update_window_entity(&panel, |panel, _, _| {
|
||||
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
|
||||
|
|
@ -6832,11 +6844,14 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
// Test: User has commit message, enables amend (saves message), then disables (restores message)
|
||||
panel.update(cx, |panel, cx| {
|
||||
|
|
@ -6901,16 +6916,19 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
// Wait for the project scanning to finish so that `head_commit(cx)` is
|
||||
// actually set, otherwise no head commit would be available from which
|
||||
// to fetch the latest commit message from.
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
panel.read_with(cx, |panel, cx| {
|
||||
assert!(panel.active_repository.is_some());
|
||||
assert!(panel.head_commit(cx).is_some());
|
||||
|
|
@ -6987,10 +7005,13 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
// Enable the `sort_by_path` setting and wait for entries to be updated,
|
||||
// as there should no longer be separators between Tracked and Untracked
|
||||
|
|
@ -7016,7 +7037,7 @@ mod tests {
|
|||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let _ = workspace.update(cx, |workspace, _window, cx| {
|
||||
workspace.update_in(cx, |workspace, _window, cx| {
|
||||
let active_path = workspace
|
||||
.item_of_type::<ProjectDiff>(cx)
|
||||
.expect("ProjectDiff should exist")
|
||||
|
|
@ -7060,9 +7081,12 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
|
||||
cx.read(|cx| {
|
||||
project
|
||||
|
|
@ -7087,7 +7111,7 @@ mod tests {
|
|||
});
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
let handle = cx.update_window_entity(&panel, |panel, _, _| {
|
||||
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
|
||||
|
|
@ -7246,10 +7270,13 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window_handle
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
|
||||
let panel = workspace.update_in(cx, GitPanel::new);
|
||||
|
||||
let handle = cx.update_window_entity(&panel, |panel, _, _| {
|
||||
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ impl ProjectDiff {
|
|||
return;
|
||||
}
|
||||
let workspace = cx.entity();
|
||||
let workspace_weak = workspace.downgrade();
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let this = cx
|
||||
|
|
@ -138,7 +139,7 @@ impl ProjectDiff {
|
|||
.ok();
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_notify_err(window, cx);
|
||||
.detach_and_notify_err(workspace_weak, window, cx);
|
||||
}
|
||||
|
||||
pub fn deploy_at(
|
||||
|
|
@ -1851,6 +1852,8 @@ mod tests {
|
|||
rel_path::{RelPath, rel_path},
|
||||
};
|
||||
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[ctor::ctor]
|
||||
|
|
@ -1898,8 +1901,9 @@ mod tests {
|
|||
&[("foo.txt", "foo\n".into())],
|
||||
);
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
|
|
@ -1946,8 +1950,9 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
|
|
@ -2016,8 +2021,9 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("foo", "original\n".into())],
|
||||
|
|
@ -2146,8 +2152,9 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs, [Path::new(path!("/a"))], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
|
|
@ -2260,8 +2267,9 @@ mod tests {
|
|||
);
|
||||
|
||||
let project = Project::test(fs, [Path::new(path!("/a"))], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
|
|
@ -2315,8 +2323,9 @@ mod tests {
|
|||
)],
|
||||
);
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
|
|
@ -2395,8 +2404,9 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
|
|
@ -2511,8 +2521,9 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let diff = cx
|
||||
.update(|window, cx| {
|
||||
ProjectDiff::new_with_default_branch(project.clone(), workspace, window, cx)
|
||||
|
|
@ -2608,8 +2619,9 @@ mod tests {
|
|||
let worktree_id = project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
cx.run_until_parked();
|
||||
|
||||
let _editor = workspace
|
||||
|
|
@ -2693,8 +2705,9 @@ mod tests {
|
|||
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
|
||||
});
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
cx.run_until_parked();
|
||||
|
||||
// Select project A via the dropdown override and open the diff.
|
||||
|
|
|
|||
|
|
@ -594,7 +594,7 @@ mod tests {
|
|||
use picker::PickerDelegate;
|
||||
use project::{FakeFs, Project};
|
||||
use settings::SettingsStore;
|
||||
use workspace::Workspace;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
|
|
@ -626,25 +626,27 @@ mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let multi_workspace =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*multi_workspace, cx);
|
||||
let workspace = multi_workspace
|
||||
.update(cx, |workspace, _, _| workspace.workspace().clone())
|
||||
.unwrap();
|
||||
let stash_entries = vec![
|
||||
stash_entry(0, "stash #0", Some("main")),
|
||||
stash_entry(1, "stash #1", Some("develop")),
|
||||
];
|
||||
|
||||
let stash_list = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let weak_workspace = workspace.weak_handle();
|
||||
let stash_list = workspace.update_in(cx, |workspace, window, cx| {
|
||||
let weak_workspace = workspace.weak_handle();
|
||||
|
||||
workspace.toggle_modal(window, cx, move |window, cx| {
|
||||
StashList::new(None, weak_workspace, rems(34.), window, cx)
|
||||
});
|
||||
workspace.toggle_modal(window, cx, move |window, cx| {
|
||||
StashList::new(None, weak_workspace, rems(34.), window, cx)
|
||||
});
|
||||
|
||||
assert!(workspace.active_modal::<StashList>(cx).is_some());
|
||||
workspace.active_modal::<StashList>(cx).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
assert!(workspace.active_modal::<StashList>(cx).is_some());
|
||||
workspace.active_modal::<StashList>(cx).unwrap()
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
stash_list.update(cx, |stash_list, cx| {
|
||||
|
|
@ -667,10 +669,8 @@ mod tests {
|
|||
stash_list.handle_show_stash(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
assert!(workspace.active_modal::<StashList>(cx).is_none());
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.active_modal::<StashList>(cx).is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ mod tests {
|
|||
use settings::SettingsStore;
|
||||
use unindent::unindent;
|
||||
use util::{path, test::marked_text_ranges};
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
|
|
@ -675,8 +676,9 @@ mod tests {
|
|||
|
||||
let project = Project::test(fs, [project_root.as_ref()], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer(file_path, cx))
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use fuzzy::StringMatchCandidate;
|
|||
|
||||
use git::repository::Worktree as GitWorktree;
|
||||
use gpui::{
|
||||
Action, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement,
|
||||
Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement,
|
||||
PathPromptOptions, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window,
|
||||
actions, rems,
|
||||
};
|
||||
|
|
@ -20,7 +20,7 @@ use remote_connection::{RemoteConnectionModal, connect};
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr};
|
||||
use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr};
|
||||
|
||||
actions!(git, [WorktreeFromDefault, WorktreeFromDefaultOnWindow]);
|
||||
|
||||
|
|
@ -289,7 +289,6 @@ impl WorktreeListDelegate {
|
|||
};
|
||||
|
||||
let branch = worktree_branch.to_string();
|
||||
let window_handle = window.window_handle();
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
let Some(paths) = worktree_path.await? else {
|
||||
|
|
@ -355,7 +354,7 @@ impl WorktreeListDelegate {
|
|||
connection_options,
|
||||
vec![new_worktree_path],
|
||||
app_state,
|
||||
window_handle,
|
||||
workspace.clone(),
|
||||
replace_current_window,
|
||||
cx,
|
||||
)
|
||||
|
|
@ -407,13 +406,12 @@ impl WorktreeListDelegate {
|
|||
|e, _, _| Some(e.to_string()),
|
||||
);
|
||||
} else if let Some(connection_options) = connection_options {
|
||||
let window_handle = window.window_handle();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
open_remote_worktree(
|
||||
connection_options,
|
||||
vec![path],
|
||||
app_state,
|
||||
window_handle,
|
||||
workspace,
|
||||
replace_current_window,
|
||||
cx,
|
||||
)
|
||||
|
|
@ -441,15 +439,16 @@ async fn open_remote_worktree(
|
|||
connection_options: RemoteConnectionOptions,
|
||||
paths: Vec<PathBuf>,
|
||||
app_state: Arc<workspace::AppState>,
|
||||
window: gpui::AnyWindowHandle,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
replace_current_window: bool,
|
||||
cx: &mut AsyncApp,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let workspace_window = window
|
||||
.downcast::<Workspace>()
|
||||
let workspace_window = cx
|
||||
.window_handle()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.ok_or_else(|| anyhow::anyhow!("Window is not a Workspace window"))?;
|
||||
|
||||
let connect_task = workspace_window.update(cx, |workspace, window, cx| {
|
||||
let connect_task = workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
RemoteConnectionModal::new(&connection_options, Vec::new(), window, cx)
|
||||
});
|
||||
|
|
@ -473,17 +472,19 @@ async fn open_remote_worktree(
|
|||
|
||||
let session = connect_task.await;
|
||||
|
||||
workspace_window.update(cx, |workspace, _window, cx| {
|
||||
if let Some(prompt) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
prompt.update(cx, |prompt, cx| prompt.finished(cx))
|
||||
}
|
||||
})?;
|
||||
workspace
|
||||
.update_in(cx, |workspace, _window, cx| {
|
||||
if let Some(prompt) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
prompt.update(cx, |prompt, cx| prompt.finished(cx))
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
let Some(Some(session)) = session else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let new_project: Entity<project::Project> = cx.update(|cx| {
|
||||
let new_project: Entity<project::Project> = cx.update(|_, cx| {
|
||||
project::Project::remote(
|
||||
session,
|
||||
app_state.client.clone(),
|
||||
|
|
@ -494,29 +495,30 @@ async fn open_remote_worktree(
|
|||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})?;
|
||||
|
||||
let window_to_use = if replace_current_window {
|
||||
workspace_window
|
||||
} else {
|
||||
let workspace_position = cx
|
||||
.update(|cx| {
|
||||
.update(|_, cx| {
|
||||
workspace::remote_workspace_position_from_db(connection_options.clone(), &paths, cx)
|
||||
})
|
||||
})?
|
||||
.await
|
||||
.context("fetching workspace position from db")?;
|
||||
|
||||
let mut options =
|
||||
cx.update(|cx| (app_state.build_window_options)(workspace_position.display, cx));
|
||||
cx.update(|_, cx| (app_state.build_window_options)(workspace_position.display, cx))?;
|
||||
options.window_bounds = workspace_position.window_bounds;
|
||||
|
||||
cx.open_window(options, |window, cx| {
|
||||
cx.new(|cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
let mut workspace =
|
||||
Workspace::new(None, new_project.clone(), app_state.clone(), window, cx);
|
||||
workspace.centered_layout = workspace_position.centered_layout;
|
||||
workspace
|
||||
})
|
||||
});
|
||||
cx.new(|cx| MultiWorkspace::new(workspace, cx))
|
||||
})?
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use std::{num::NonZeroU32, sync::Arc, time::Duration};
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{AppState, Workspace};
|
||||
use workspace::{AppState, MultiWorkspace, Workspace};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_go_to_line_view_row_highlights(cx: &mut TestAppContext) {
|
||||
|
|
@ -407,8 +407,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
@ -504,8 +505,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let cursor_position = cx.new(|_| CursorPosition::new(workspace));
|
||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||
|
|
@ -589,8 +591,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let cursor_position = cx.new(|_| CursorPosition::new(workspace));
|
||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||
|
|
@ -667,8 +670,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let cursor_position = cx.new(|_| CursorPosition::new(workspace));
|
||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||
|
|
@ -843,8 +847,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
@ -900,8 +905,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
@ -955,8 +961,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
|
|
|
|||
|
|
@ -265,6 +265,8 @@ pub enum IconName {
|
|||
UserRoundPen,
|
||||
Warning,
|
||||
WholeWord,
|
||||
WorkspaceNavClosed,
|
||||
WorkspaceNavOpen,
|
||||
XCircle,
|
||||
XCircleFilled,
|
||||
ZedAgent,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ editor.workspace = true
|
|||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
platform_title_bar.workspace = true
|
||||
project.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use anyhow::{Context as _, anyhow};
|
||||
use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window};
|
||||
use platform_title_bar::PlatformTitleBar;
|
||||
use std::{cell::OnceCell, path::Path, sync::Arc};
|
||||
use ui::{Label, Tooltip, prelude::*};
|
||||
use ui::{Label, Tooltip, prelude::*, utils::platform_title_bar_height};
|
||||
use util::{ResultExt as _, command::new_smol_command};
|
||||
use workspace::AppState;
|
||||
|
||||
|
|
@ -61,7 +60,7 @@ fn render_inspector(
|
|||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
let colors = cx.theme().colors();
|
||||
let inspector_id = inspector.active_element_id();
|
||||
let toolbar_height = PlatformTitleBar::height(window);
|
||||
let toolbar_height = platform_title_bar_height(window);
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
|
|
|
|||
|
|
@ -118,17 +118,20 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap
|
|||
})?
|
||||
.await?;
|
||||
new_workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.open_paths(
|
||||
vec![entry_path],
|
||||
workspace::OpenOptions {
|
||||
visible: Some(OpenVisible::All),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_paths(
|
||||
vec![entry_path],
|
||||
workspace::OpenOptions {
|
||||
visible: Some(OpenVisible::All),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?
|
||||
.await
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1319,7 +1319,7 @@ impl KeymapEditor {
|
|||
cx.spawn(async move |_, _| {
|
||||
remove_keybinding(to_remove, &fs, keyboard_mapper.as_ref()).await
|
||||
})
|
||||
.detach_and_notify_err(window, cx);
|
||||
.detach_and_notify_err(self.workspace.clone(), window, cx);
|
||||
}
|
||||
|
||||
fn copy_context_to_clipboard(
|
||||
|
|
|
|||
|
|
@ -674,7 +674,7 @@ mod tests {
|
|||
use itertools::Itertools as _;
|
||||
use project::Project;
|
||||
use settings::SettingsStore;
|
||||
use workspace::Workspace;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
pub struct KeystrokeInputTestHelper {
|
||||
input: Entity<KeystrokeInput>,
|
||||
|
|
@ -1120,9 +1120,9 @@ mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = VisualTestContext::from_window(*workspace, cx);
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let cx = VisualTestContext::from_window(window_handle.into(), cx);
|
||||
KeystrokeInputTestHelper::new(cx)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::{
|
|||
use gpui::{
|
||||
App, AppContext, ClipboardItem, Context, Div, Entity, Hsla, InteractiveElement,
|
||||
ParentElement as _, Render, SerializedTaskTiming, SharedString, StatefulInteractiveElement,
|
||||
Styled, Task, TaskTiming, TitlebarOptions, UniformListScrollHandle, WindowBounds, WindowHandle,
|
||||
Styled, Task, TaskTiming, TitlebarOptions, UniformListScrollHandle, WeakEntity, WindowBounds,
|
||||
WindowOptions, div, prelude::FluentBuilder, px, relative, size, uniform_list,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
|
@ -22,13 +22,10 @@ use workspace::{
|
|||
use zed_actions::OpenPerformanceProfiler;
|
||||
|
||||
pub fn init(startup_time: Instant, cx: &mut App) {
|
||||
cx.observe_new(move |workspace: &mut workspace::Workspace, _, _| {
|
||||
workspace.register_action(move |workspace, _: &OpenPerformanceProfiler, window, cx| {
|
||||
let window_handle = window
|
||||
.window_handle()
|
||||
.downcast::<Workspace>()
|
||||
.expect("Workspaces are root Windows");
|
||||
open_performance_profiler(startup_time, workspace, window_handle, cx);
|
||||
cx.observe_new(move |workspace: &mut workspace::Workspace, _, cx| {
|
||||
let workspace_handle = cx.entity().downgrade();
|
||||
workspace.register_action(move |_workspace, _: &OpenPerformanceProfiler, window, cx| {
|
||||
open_performance_profiler(startup_time, workspace_handle.clone(), window, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
|
@ -36,8 +33,8 @@ pub fn init(startup_time: Instant, cx: &mut App) {
|
|||
|
||||
fn open_performance_profiler(
|
||||
startup_time: Instant,
|
||||
_workspace: &mut workspace::Workspace,
|
||||
workspace_handle: WindowHandle<Workspace>,
|
||||
workspace_handle: WeakEntity<Workspace>,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let existing_window = cx
|
||||
|
|
@ -48,7 +45,7 @@ fn open_performance_profiler(
|
|||
if let Some(existing_window) = existing_window {
|
||||
existing_window
|
||||
.update(cx, |profiler_window, window, _cx| {
|
||||
profiler_window.workspace = Some(workspace_handle);
|
||||
profiler_window.workspace = Some(workspace_handle.clone());
|
||||
window.activate_window();
|
||||
})
|
||||
.log_err();
|
||||
|
|
@ -97,14 +94,14 @@ pub struct ProfilerWindow {
|
|||
include_self_timings: ToggleState,
|
||||
autoscroll: bool,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
workspace: Option<WindowHandle<Workspace>>,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
_refresh: Option<Task<()>>,
|
||||
}
|
||||
|
||||
impl ProfilerWindow {
|
||||
pub fn new(
|
||||
startup_time: Instant,
|
||||
workspace_handle: Option<WindowHandle<Workspace>>,
|
||||
workspace_handle: Option<WeakEntity<Workspace>>,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let entity = cx.new(|cx| ProfilerWindow {
|
||||
|
|
@ -280,7 +277,7 @@ impl Render for ProfilerWindow {
|
|||
Button::new("export-data", "Save")
|
||||
.style(ButtonStyle::Filled)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
let Some(workspace) = this.workspace else {
|
||||
let Some(workspace) = this.workspace.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -297,7 +294,7 @@ impl Render for ProfilerWindow {
|
|||
.log_err()
|
||||
.flatten()
|
||||
.and_then(|p| p.parent().map(|p| p.to_owned()))
|
||||
.unwrap_or_else(|| PathBuf::default());
|
||||
.unwrap_or_else(PathBuf::default);
|
||||
|
||||
let path = cx.prompt_for_new_path(
|
||||
&active_path,
|
||||
|
|
|
|||
|
|
@ -238,15 +238,16 @@ impl Onboarding {
|
|||
go_to_welcome_page(cx);
|
||||
}
|
||||
|
||||
fn handle_sign_in(_: &SignIn, window: &mut Window, cx: &mut App) {
|
||||
fn handle_sign_in(&mut self, _: &SignIn, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let client = Client::global(cx);
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
.spawn(cx, async move |mut cx| {
|
||||
client
|
||||
.sign_in_with_optional_connect(true, cx)
|
||||
.sign_in_with_optional_connect(true, &cx)
|
||||
.await
|
||||
.notify_async_err(cx);
|
||||
.notify_workspace_async_err(workspace, &mut cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
|
@ -274,7 +275,7 @@ impl Render for Onboarding {
|
|||
.size_full()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_action(Self::on_finish)
|
||||
.on_action(Self::handle_sign_in)
|
||||
.on_action(cx.listener(Self::handle_sign_in))
|
||||
.on_action(Self::handle_open_account)
|
||||
.on_action(cx.listener(|_, _: &menu::SelectNext, window, cx| {
|
||||
window.focus_next(cx);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use project::Project;
|
|||
use serde_json::json;
|
||||
use ui::rems;
|
||||
use util::path;
|
||||
use workspace::{AppState, Workspace};
|
||||
use workspace::{AppState, MultiWorkspace};
|
||||
|
||||
use crate::OpenPathDelegate;
|
||||
|
||||
|
|
@ -426,7 +426,9 @@ fn build_open_path_prompt(
|
|||
let (tx, _) = futures::channel::oneshot::channel();
|
||||
let lister = project::DirectoryLister::Project(project.clone());
|
||||
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
(
|
||||
workspace.update_in(cx, |_, window, cx| {
|
||||
let delegate = OpenPathDelegate::new(tx, lister.clone(), creating_path, cx);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use settings::Settings;
|
|||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::{DismissDecision, ModalView, Workspace};
|
||||
use workspace::{DismissDecision, ModalView};
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(OutlineView::register).detach();
|
||||
|
|
@ -41,7 +41,7 @@ pub fn toggle(
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(workspace) = window.root::<Workspace>().flatten() else {
|
||||
let Some(workspace) = editor.read(cx).workspace() else {
|
||||
return;
|
||||
};
|
||||
if workspace.read(cx).active_modal::<OutlineView>(cx).is_some() {
|
||||
|
|
@ -453,7 +453,7 @@ mod tests {
|
|||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt as _;
|
||||
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) {
|
||||
|
|
@ -481,7 +481,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()
|
||||
|
|
@ -736,8 +738,9 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = cx.read(|cx| multi_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()
|
||||
|
|
|
|||
|
|
@ -5387,7 +5387,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use smol::stream::StreamExt as _;
|
||||
use util::path;
|
||||
use workspace::{OpenOptions, OpenVisible, ToolbarItemView};
|
||||
use workspace::{MultiWorkspace, OpenOptions, OpenVisible, ToolbarItemView};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -5402,33 +5402,29 @@ mod tests {
|
|||
populate_with_test_ra_project(&fs, root).await;
|
||||
let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.set_active(true, window, cx)
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let search_view = workspace.update_in(cx, |workspace, _window, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
});
|
||||
|
||||
let query = "param_names_for_lifetime_elision_hints";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
|
|
@ -5635,33 +5631,29 @@ mod tests {
|
|||
populate_with_test_ra_project(&fs, root).await;
|
||||
let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.set_active(true, window, cx)
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let search_view = workspace.update_in(cx, |workspace, _window, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
});
|
||||
|
||||
let query = "param_names_for_lifetime_elision_hints";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
|
|
@ -5772,33 +5764,29 @@ mod tests {
|
|||
populate_with_test_ra_project(&fs, root).await;
|
||||
let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.set_active(true, window, cx)
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let search_view = workspace.update_in(cx, |workspace, _window, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
});
|
||||
|
||||
let query = "param_names_for_lifetime_elision_hints";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
|
|
@ -5998,15 +5986,15 @@ outline: fn hints_lifetimes_named <==== selected"
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/root/one"))], cx).await;
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.set_active(true, window, cx)
|
||||
});
|
||||
|
||||
let items = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_paths(
|
||||
vec![PathBuf::from(path!("/root/two"))],
|
||||
OpenOptions {
|
||||
|
|
@ -6018,7 +6006,6 @@ outline: fn hints_lifetimes_named <==== selected"
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await;
|
||||
assert_eq!(items.len(), 1, "Were opening another worktree directory");
|
||||
assert!(
|
||||
|
|
@ -6026,26 +6013,22 @@ outline: fn hints_lifetimes_named <==== selected"
|
|||
"Directory should be opened successfully"
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let search_view = workspace.update_in(cx, |workspace, _window, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
});
|
||||
|
||||
let query = "aaa";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
|
|
@ -6183,8 +6166,8 @@ struct OutlineEntryExcerpt {
|
|||
.await;
|
||||
let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
cx.update(|window, cx| {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
|
|
@ -6193,7 +6176,7 @@ struct OutlineEntryExcerpt {
|
|||
});
|
||||
|
||||
let _editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from(path!("/root/src/lib.rs")),
|
||||
OpenOptions {
|
||||
|
|
@ -6204,7 +6187,6 @@ struct OutlineEntryExcerpt {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.expect("Failed to open Rust source file")
|
||||
.downcast::<Editor>()
|
||||
|
|
@ -6545,33 +6527,29 @@ outline: struct OutlineEntryExcerpt
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.set_active(true, window, cx)
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let search_view = workspace.update_in(cx, |workspace, _window, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
});
|
||||
|
||||
let query = "static";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
|
|
@ -6806,13 +6784,18 @@ outline: struct OutlineEntryExcerpt
|
|||
async fn add_outline_panel(
|
||||
project: &Entity<Project>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> WindowHandle<Workspace> {
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
) -> (WindowHandle<MultiWorkspace>, Entity<Workspace>) {
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
let workspace_weak = workspace.downgrade();
|
||||
let outline_panel = window
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.spawn_in(window, async |this, cx| {
|
||||
OutlinePanel::load(this, cx.clone()).await
|
||||
cx.spawn_in(window, async move |_this, cx| {
|
||||
OutlinePanel::load(workspace_weak, cx.clone()).await
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
|
|
@ -6820,24 +6803,24 @@ outline: struct OutlineEntryExcerpt
|
|||
.expect("Failed to load outline panel");
|
||||
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.add_panel(outline_panel, window, cx);
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.workspace().update(cx, |workspace, cx| {
|
||||
workspace.add_panel(outline_panel, window, cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
window
|
||||
(window, workspace)
|
||||
}
|
||||
|
||||
fn outline_panel(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
cx: &mut TestAppContext,
|
||||
workspace: &Entity<Workspace>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Entity<OutlinePanel> {
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.panel::<OutlinePanel>(cx)
|
||||
.expect("no outline panel")
|
||||
})
|
||||
.unwrap()
|
||||
workspace.update_in(cx, |workspace, _window, cx| {
|
||||
workspace
|
||||
.panel::<OutlinePanel>(cx)
|
||||
.expect("no outline panel")
|
||||
})
|
||||
}
|
||||
|
||||
fn display_entries(
|
||||
|
|
@ -7196,8 +7179,8 @@ outline: struct OutlineEntryExcerpt
|
|||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
|
|
@ -7205,7 +7188,7 @@ outline: struct OutlineEntryExcerpt
|
|||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from("/test/src/lib.rs"),
|
||||
OpenOptions {
|
||||
|
|
@ -7216,7 +7199,6 @@ outline: struct OutlineEntryExcerpt
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -7452,8 +7434,8 @@ outline: fn main"
|
|||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
|
|
@ -7461,7 +7443,7 @@ outline: fn main"
|
|||
});
|
||||
|
||||
let _editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from("/test/src/main.rs"),
|
||||
OpenOptions {
|
||||
|
|
@ -7472,7 +7454,6 @@ outline: fn main"
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -7666,8 +7647,8 @@ outline: fn main"
|
|||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
project.read_with(cx, |project, _| project.languages().add(rust_lang()));
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
|
|
@ -7675,7 +7656,7 @@ outline: fn main"
|
|||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from("/test/src/lib.rs"),
|
||||
OpenOptions {
|
||||
|
|
@ -7686,7 +7667,6 @@ outline: fn main"
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -7841,11 +7821,11 @@ outline: fn main"
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
let editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from("/test/foo.txt"),
|
||||
OpenOptions {
|
||||
|
|
@ -7856,22 +7836,19 @@ outline: fn main"
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let search_bar = workspace
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut search_bar = BufferSearchBar::new(None, window, cx);
|
||||
search_bar.set_active_pane_item(Some(&editor), window, cx);
|
||||
search_bar.show(window, cx);
|
||||
search_bar
|
||||
})
|
||||
let search_bar = workspace.update_in(cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut search_bar = BufferSearchBar::new(None, window, cx);
|
||||
search_bar.set_active_pane_item(Some(&editor), window, cx);
|
||||
search_bar.show(window, cx);
|
||||
search_bar
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
|
||||
|
|
@ -8008,8 +7985,8 @@ search: | Field | Meaning « »|"
|
|||
},
|
||||
);
|
||||
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let (window, workspace) = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
cx.update(|window, cx| {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
|
|
@ -8018,7 +7995,7 @@ search: | Field | Meaning « »|"
|
|||
});
|
||||
|
||||
let _editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from(path!("/root/src/lib.rs")),
|
||||
OpenOptions {
|
||||
|
|
@ -8029,7 +8006,6 @@ search: | Field | Meaning « »|"
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.expect("Failed to open Rust source file")
|
||||
.downcast::<Editor>()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ path = "src/platform_title_bar.rs"
|
|||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
mod platforms;
|
||||
mod system_window_tabs;
|
||||
|
||||
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
|
||||
use gpui::{
|
||||
AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
|
||||
ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
|
||||
AnyElement, App, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement,
|
||||
MouseButton, ParentElement, StatefulInteractiveElement, Styled, Window, WindowControlArea, div,
|
||||
px,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::mem;
|
||||
use ui::prelude::*;
|
||||
use ui::{
|
||||
prelude::*,
|
||||
utils::{TRAFFIC_LIGHT_PADDING, platform_title_bar_height},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
platforms::{platform_linux, platform_mac, platform_windows},
|
||||
platforms::{platform_linux, platform_windows},
|
||||
system_window_tabs::SystemWindowTabs,
|
||||
};
|
||||
|
||||
|
|
@ -24,6 +29,8 @@ pub struct PlatformTitleBar {
|
|||
children: SmallVec<[AnyElement; 2]>,
|
||||
should_move: bool,
|
||||
system_window_tabs: Entity<SystemWindowTabs>,
|
||||
workspace_sidebar_open: bool,
|
||||
sidebar_has_notifications: bool,
|
||||
}
|
||||
|
||||
impl PlatformTitleBar {
|
||||
|
|
@ -37,20 +44,11 @@ impl PlatformTitleBar {
|
|||
children: SmallVec::new(),
|
||||
should_move: false,
|
||||
system_window_tabs,
|
||||
workspace_sidebar_open: false,
|
||||
sidebar_has_notifications: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn height(window: &mut Window) -> Pixels {
|
||||
(1.75 * window.rem_size()).max(px(34.))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn height(_window: &mut Window) -> Pixels {
|
||||
// todo(windows) instead of hard coded size report the actual size to the Windows platform API
|
||||
px(32.)
|
||||
}
|
||||
|
||||
pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
|
||||
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
if window.is_window_active() && !self.should_move {
|
||||
|
|
@ -73,17 +71,46 @@ impl PlatformTitleBar {
|
|||
pub fn init(cx: &mut App) {
|
||||
SystemWindowTabs::init(cx);
|
||||
}
|
||||
|
||||
pub fn is_workspace_sidebar_open(&self) -> bool {
|
||||
self.workspace_sidebar_open
|
||||
}
|
||||
|
||||
pub fn set_workspace_sidebar_open(&mut self, open: bool, cx: &mut Context<Self>) {
|
||||
self.workspace_sidebar_open = open;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn sidebar_has_notifications(&self) -> bool {
|
||||
self.sidebar_has_notifications
|
||||
}
|
||||
|
||||
pub fn set_sidebar_has_notifications(
|
||||
&mut self,
|
||||
has_notifications: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.sidebar_has_notifications = has_notifications;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_multi_workspace_enabled(cx: &App) -> bool {
|
||||
cx.has_flag::<AgentV2FeatureFlag>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for PlatformTitleBar {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let supported_controls = window.window_controls();
|
||||
let decorations = window.window_decorations();
|
||||
let height = Self::height(window);
|
||||
let height = platform_title_bar_height(window);
|
||||
let titlebar_color = self.title_bar_color(window, cx);
|
||||
let close_action = Box::new(workspace::CloseWindow);
|
||||
let children = mem::take(&mut self.children);
|
||||
|
||||
let is_multiworkspace_sidebar_open =
|
||||
PlatformTitleBar::is_multi_workspace_enabled(cx) && self.is_workspace_sidebar_open();
|
||||
|
||||
let title_bar = h_flex()
|
||||
.window_control_area(WindowControlArea::Drag)
|
||||
.w_full()
|
||||
|
|
@ -132,8 +159,10 @@ impl Render for PlatformTitleBar {
|
|||
.map(|this| {
|
||||
if window.is_fullscreen() {
|
||||
this.pl_2()
|
||||
} else if self.platform_style == PlatformStyle::Mac {
|
||||
this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
|
||||
} else if self.platform_style == PlatformStyle::Mac
|
||||
&& !is_multiworkspace_sidebar_open
|
||||
{
|
||||
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
||||
} else {
|
||||
this.pl_2()
|
||||
}
|
||||
|
|
@ -144,9 +173,10 @@ impl Render for PlatformTitleBar {
|
|||
.when(!(tiling.top || tiling.right), |el| {
|
||||
el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
|
||||
})
|
||||
.when(!(tiling.top || tiling.left), |el| {
|
||||
el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
|
||||
})
|
||||
.when(
|
||||
!(tiling.top || tiling.left) && !is_multiworkspace_sidebar_open,
|
||||
|el| el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING),
|
||||
)
|
||||
// this border is to avoid a transparent gap in the rounded corners
|
||||
.mt(px(-1.))
|
||||
.mb(px(-1.))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
pub mod platform_linux;
|
||||
pub mod platform_mac;
|
||||
pub mod platform_windows;
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// Use pixels here instead of a rem-based size because the macOS traffic
|
||||
// lights are a static size, and don't scale with the rest of the UI.
|
||||
//
|
||||
// Magic number: There is one extra pixel of padding on the left side due to
|
||||
// the 1px border around the window on macOS apps.
|
||||
#[cfg(macos_sdk_26)]
|
||||
pub const TRAFFIC_LIGHT_PADDING: f32 = 78.;
|
||||
|
||||
#[cfg(not(macos_sdk_26))]
|
||||
pub const TRAFFIC_LIGHT_PADDING: f32 = 71.;
|
||||
|
|
@ -772,7 +772,11 @@ impl ProjectPanel {
|
|||
{
|
||||
match project_panel.confirm_edit(false, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
task.detach_and_notify_err(
|
||||
project_panel.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
project_panel.discard_edit_state(window, cx);
|
||||
|
|
@ -1648,7 +1652,7 @@ impl ProjectPanel {
|
|||
|
||||
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(task) = self.confirm_edit(true, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
task.detach_and_notify_err(self.workspace.clone(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3033,20 +3037,25 @@ impl ProjectPanel {
|
|||
}
|
||||
|
||||
let item_count = paste_tasks.len();
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
cx.spawn_in(window, async move |project_panel, cx| {
|
||||
cx.spawn_in(window, async move |project_panel, mut cx| {
|
||||
let mut last_succeed = None;
|
||||
for task in paste_tasks {
|
||||
match task {
|
||||
PasteTask::Rename(task) => {
|
||||
if let Some(CreatedEntry::Included(entry)) =
|
||||
task.await.notify_async_err(cx)
|
||||
if let Some(CreatedEntry::Included(entry)) = task
|
||||
.await
|
||||
.notify_workspace_async_err(workspace.clone(), &mut cx)
|
||||
{
|
||||
last_succeed = Some(entry);
|
||||
}
|
||||
}
|
||||
PasteTask::Copy(task) => {
|
||||
if let Some(Some(entry)) = task.await.notify_async_err(cx) {
|
||||
if let Some(Some(entry)) = task
|
||||
.await
|
||||
.notify_workspace_async_err(workspace.clone(), &mut cx)
|
||||
{
|
||||
last_succeed = Some(entry);
|
||||
}
|
||||
}
|
||||
|
|
@ -3388,7 +3397,7 @@ impl ProjectPanel {
|
|||
if let Some((file_path1, file_path2)) = selected_files {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
FileDiffView::open(file_path1, file_path2, workspace, window, cx)
|
||||
FileDiffView::open(file_path1, file_path2, workspace.weak_handle(), window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.ok();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -318,6 +318,7 @@ mod tests {
|
|||
use settings::SettingsStore;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use util::path;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_project_symbols(cx: &mut TestAppContext) {
|
||||
|
|
@ -409,8 +410,9 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
// Create the project symbols view.
|
||||
let symbols = cx.new_window_entity(|window, cx| {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ db.workspace = true
|
|||
dev_container.workspace = true
|
||||
editor.workspace = true
|
||||
extension_host.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
|
|
@ -66,6 +67,7 @@ language = { workspace = true, features = ["test-support"] }
|
|||
project = { workspace = true, features = ["test-support"] }
|
||||
release_channel.workspace = true
|
||||
remote = { workspace = true, features = ["test-support"] }
|
||||
remote_connection = { workspace = true, features = ["test-support"] }
|
||||
remote_server.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ use ui::{
|
|||
HeadlineSize, IconName, IconPosition, InteractiveElement, IntoElement, Label, Modal,
|
||||
ModalFooter, ModalHeader, ParentElement, Section, Styled, StyledExt, Window, div, h_flex, rems,
|
||||
};
|
||||
use workspace::{ModalView, OpenOptions, Workspace, notifications::DetachAndPromptErr};
|
||||
use workspace::{
|
||||
ModalView, MultiWorkspace, OpenOptions, Workspace, notifications::DetachAndPromptErr,
|
||||
};
|
||||
|
||||
use crate::open_remote_project;
|
||||
|
||||
|
|
@ -109,7 +111,7 @@ impl DisconnectedOverlay {
|
|||
return;
|
||||
};
|
||||
|
||||
let Some(window_handle) = window.window_handle().downcast::<Workspace>() else {
|
||||
let Some(window_handle) = window.window_handle().downcast::<MultiWorkspace>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ mod remote_connections;
|
|||
mod remote_servers;
|
||||
mod ssh_config;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use fs::Fs;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod wsl_picker;
|
||||
|
|
@ -27,11 +29,11 @@ use picker::{
|
|||
pub use remote_connections::RemoteSettings;
|
||||
pub use remote_servers::RemoteServerProjects;
|
||||
use settings::Settings;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use std::path::Path;
|
||||
use ui::{KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*, tooltip_container};
|
||||
use util::{ResultExt, paths::PathExt};
|
||||
use workspace::{
|
||||
CloseIntent, HistoryManager, ModalView, OpenOptions, PathList, SerializedWorkspaceLocation,
|
||||
HistoryManager, ModalView, MultiWorkspace, OpenOptions, PathList, SerializedWorkspaceLocation,
|
||||
WORKSPACE_DB, Workspace, WorkspaceId, notifications::DetachAndPromptErr,
|
||||
with_active_or_new_workspace,
|
||||
};
|
||||
|
|
@ -48,9 +50,10 @@ pub struct RecentProjectEntry {
|
|||
pub async fn get_recent_projects(
|
||||
current_workspace_id: Option<WorkspaceId>,
|
||||
limit: Option<usize>,
|
||||
fs: Arc<dyn fs::Fs>,
|
||||
) -> Vec<RecentProjectEntry> {
|
||||
let workspaces = WORKSPACE_DB
|
||||
.recent_workspaces_on_disk()
|
||||
.recent_workspaces_on_disk(fs.as_ref())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
|
|
@ -176,7 +179,7 @@ pub fn init(cx: &mut App) {
|
|||
let fs = workspace.project().read(cx).fs().clone();
|
||||
add_wsl_distro(fs, &open_wsl.distro, cx);
|
||||
let open_options = OpenOptions {
|
||||
replace_window: window.window_handle().downcast::<Workspace>(),
|
||||
replace_window: window.window_handle().downcast::<MultiWorkspace>(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
@ -232,10 +235,8 @@ pub fn init(cx: &mut App) {
|
|||
|
||||
cx.on_action(|_: &OpenDevContainer, cx| {
|
||||
with_active_or_new_workspace(cx, move |workspace, window, cx| {
|
||||
let is_local = workspace.project().read(cx).is_local();
|
||||
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
if !is_local {
|
||||
if !workspace.project().read(cx).is_local() {
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
cx.prompt(
|
||||
gpui::PromptLevel::Critical,
|
||||
"Cannot open Dev Container from remote project",
|
||||
|
|
@ -244,21 +245,16 @@ pub fn init(cx: &mut App) {
|
|||
)
|
||||
.await
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update(|_, cx| {
|
||||
with_active_or_new_workspace(cx, move |workspace, window, cx| {
|
||||
let fs = workspace.project().read(cx).fs().clone();
|
||||
let handle = cx.entity().downgrade();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
RemoteServerProjects::new_dev_container(fs, window, handle, cx)
|
||||
});
|
||||
});
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
.detach();
|
||||
return;
|
||||
}
|
||||
|
||||
let fs = workspace.project().read(cx).fs().clone();
|
||||
let handle = cx.entity().downgrade();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
RemoteServerProjects::new_dev_container(fs, window, handle, cx)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -334,6 +330,7 @@ impl ModalView for RecentProjects {}
|
|||
impl RecentProjects {
|
||||
fn new(
|
||||
delegate: RecentProjectsDelegate,
|
||||
fs: Option<Arc<dyn Fs>>,
|
||||
rem_width: f32,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
|
|
@ -350,8 +347,9 @@ impl RecentProjects {
|
|||
// We do not want to block the UI on a potentially lengthy call to DB, so we're gonna swap
|
||||
// out workspace locations once the future runs to completion.
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let Some(fs) = fs else { return };
|
||||
let workspaces = WORKSPACE_DB
|
||||
.recent_workspaces_on_disk()
|
||||
.recent_workspaces_on_disk(fs.as_ref())
|
||||
.await
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
|
|
@ -361,7 +359,7 @@ impl RecentProjects {
|
|||
picker.update_matches(picker.query(cx), window, cx)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
Self {
|
||||
|
|
@ -379,10 +377,11 @@ impl RecentProjects {
|
|||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let weak = cx.entity().downgrade();
|
||||
let fs = Some(workspace.app_state().fs.clone());
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
let delegate = RecentProjectsDelegate::new(weak, create_new_window, true, focus_handle);
|
||||
|
||||
Self::new(delegate, 34., window, cx)
|
||||
Self::new(delegate, fs, 34., window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -393,10 +392,13 @@ impl RecentProjects {
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let fs = workspace
|
||||
.upgrade()
|
||||
.map(|ws| ws.read(cx).app_state().fs.clone());
|
||||
cx.new(|cx| {
|
||||
let delegate =
|
||||
RecentProjectsDelegate::new(workspace, create_new_window, true, focus_handle);
|
||||
let list = Self::new(delegate, 34., window, cx);
|
||||
let list = Self::new(delegate, fs, 34., window, cx);
|
||||
list.picker.focus_handle(cx).focus(window, cx);
|
||||
list
|
||||
})
|
||||
|
|
@ -580,27 +582,21 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
SerializedWorkspaceLocation::Local => {
|
||||
let paths = candidate_workspace_paths.paths().to_vec();
|
||||
if replace_current_window {
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let continue_replacing = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.prepare_to_close(
|
||||
CloseIntent::ReplaceWindow,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
if continue_replacing {
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace
|
||||
.open_workspace_for_paths(true, paths, window, cx)
|
||||
})?
|
||||
.await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
if let Some(handle) =
|
||||
window.window_handle().downcast::<MultiWorkspace>()
|
||||
{
|
||||
cx.defer(move |cx| {
|
||||
if let Some(task) = handle
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.open_project(paths, window, cx)
|
||||
})
|
||||
.log_err()
|
||||
{
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
workspace.open_workspace_for_paths(false, paths, window, cx)
|
||||
}
|
||||
|
|
@ -609,7 +605,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
let app_state = workspace.app_state().clone();
|
||||
|
||||
let replace_window = if replace_current_window {
|
||||
window.window_handle().downcast::<Workspace>()
|
||||
window.window_handle().downcast::<MultiWorkspace>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -884,10 +880,18 @@ impl RecentProjectsDelegate {
|
|||
) {
|
||||
if let Some(selected_match) = self.matches.get(ix) {
|
||||
let (workspace_id, _, _) = self.workspaces[selected_match.candidate_id];
|
||||
let fs = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
.map(|ws| ws.read(cx).app_state().fs.clone());
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let _ = WORKSPACE_DB.delete_workspace_by_id(workspace_id).await;
|
||||
WORKSPACE_DB
|
||||
.delete_workspace_by_id(workspace_id)
|
||||
.await
|
||||
.log_err();
|
||||
let Some(fs) = fs else { return };
|
||||
let workspaces = WORKSPACE_DB
|
||||
.recent_workspaces_on_disk()
|
||||
.recent_workspaces_on_disk(fs.as_ref())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
this.update_in(cx, move |picker, window, cx| {
|
||||
|
|
@ -904,6 +908,7 @@ impl RecentProjectsDelegate {
|
|||
.update(cx, |this, cx| this.delete_history(workspace_id, cx));
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
|
@ -951,7 +956,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prompts_on_dirty_before_submit(cx: &mut TestAppContext) {
|
||||
async fn test_dirty_workspace_survives_when_opening_recent_project(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
cx.update(|cx| {
|
||||
|
|
@ -975,6 +980,11 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.await;
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(path!("/test/path"), json!({}))
|
||||
.await;
|
||||
cx.update(|cx| {
|
||||
open_paths(
|
||||
&[PathBuf::from(path!("/dir/main.ts"))],
|
||||
|
|
@ -987,31 +997,40 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||
|
||||
let workspace = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
||||
workspace
|
||||
.update(cx, |workspace, _, _| assert!(!workspace.is_edited()))
|
||||
let multi_workspace = cx.update(|cx| cx.windows()[0].downcast::<MultiWorkspace>().unwrap());
|
||||
multi_workspace
|
||||
.update(cx, |multi_workspace, _, cx| {
|
||||
assert!(!multi_workspace.workspace().read(cx).is_edited())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let editor = workspace
|
||||
.read_with(cx, |workspace, cx| {
|
||||
workspace
|
||||
let editor = multi_workspace
|
||||
.read_with(cx, |multi_workspace, cx| {
|
||||
multi_workspace
|
||||
.workspace()
|
||||
.read(cx)
|
||||
.active_item(cx)
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
workspace
|
||||
multi_workspace
|
||||
.update(cx, |_, window, cx| {
|
||||
editor.update(cx, |editor, cx| editor.insert("EDIT", window, cx));
|
||||
})
|
||||
.unwrap();
|
||||
workspace
|
||||
.update(cx, |workspace, _, _| assert!(workspace.is_edited(), "After inserting more text into the editor without saving, we should have a dirty project"))
|
||||
multi_workspace
|
||||
.update(cx, |multi_workspace, _, cx| {
|
||||
assert!(
|
||||
multi_workspace.workspace().read(cx).is_edited(),
|
||||
"After inserting more text into the editor without saving, we should have a dirty project"
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let recent_projects_picker = open_recent_projects(&workspace, cx);
|
||||
workspace
|
||||
let recent_projects_picker = open_recent_projects(&multi_workspace, cx);
|
||||
multi_workspace
|
||||
.update(cx, |_, _, cx| {
|
||||
recent_projects_picker.update(cx, |picker, cx| {
|
||||
assert_eq!(picker.query(cx), "");
|
||||
|
|
@ -1035,47 +1054,64 @@ mod tests {
|
|||
!cx.has_pending_prompt(),
|
||||
"Should have no pending prompt on dirty project before opening the new recent project"
|
||||
);
|
||||
cx.dispatch_action(*workspace, menu::Confirm);
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
assert!(
|
||||
workspace.active_modal::<RecentProjects>(cx).is_none(),
|
||||
"Should remove the modal after selecting new recent project"
|
||||
)
|
||||
let dirty_workspace = multi_workspace
|
||||
.read_with(cx, |multi_workspace, _cx| {
|
||||
multi_workspace.workspace().clone()
|
||||
})
|
||||
.unwrap();
|
||||
assert!(
|
||||
cx.has_pending_prompt(),
|
||||
"Dirty workspace should prompt before opening the new recent project"
|
||||
);
|
||||
cx.simulate_prompt_answer("Cancel");
|
||||
|
||||
cx.dispatch_action(*multi_workspace, menu::Confirm);
|
||||
cx.run_until_parked();
|
||||
|
||||
multi_workspace
|
||||
.update(cx, |multi_workspace, _, cx| {
|
||||
assert!(
|
||||
multi_workspace
|
||||
.workspace()
|
||||
.read(cx)
|
||||
.active_modal::<RecentProjects>(cx)
|
||||
.is_none(),
|
||||
"Should remove the modal after selecting new recent project"
|
||||
);
|
||||
|
||||
assert!(
|
||||
multi_workspace.workspaces().len() >= 2,
|
||||
"Should have at least 2 workspaces: the dirty one and the newly opened one"
|
||||
);
|
||||
|
||||
assert!(
|
||||
multi_workspace.workspaces().contains(&dirty_workspace),
|
||||
"The original dirty workspace should still be present"
|
||||
);
|
||||
|
||||
assert!(
|
||||
dirty_workspace.read(cx).is_edited(),
|
||||
"The original workspace should still be dirty"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
!cx.has_pending_prompt(),
|
||||
"Should have no pending prompt after cancelling"
|
||||
"No save prompt in multi-workspace mode — dirty workspace survives in background"
|
||||
);
|
||||
workspace
|
||||
.update(cx, |workspace, _, _| {
|
||||
assert!(
|
||||
workspace.is_edited(),
|
||||
"Should be in the same dirty project after cancelling"
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn open_recent_projects(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
multi_workspace: &WindowHandle<MultiWorkspace>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Entity<Picker<RecentProjectsDelegate>> {
|
||||
cx.dispatch_action(
|
||||
(*workspace).into(),
|
||||
(*multi_workspace).into(),
|
||||
OpenRecent {
|
||||
create_new_window: false,
|
||||
},
|
||||
);
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
multi_workspace
|
||||
.update(cx, |multi_workspace, _, cx| {
|
||||
multi_workspace
|
||||
.workspace()
|
||||
.read(cx)
|
||||
.active_modal::<RecentProjects>(cx)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use remote::{
|
|||
pub use settings::SshConnection;
|
||||
use settings::{DevContainerConnection, ExtendingVec, RegisterSetting, Settings, WslConnection};
|
||||
use util::paths::PathWithPosition;
|
||||
use workspace::{AppState, Workspace};
|
||||
use workspace::{AppState, MultiWorkspace, Workspace};
|
||||
|
||||
pub use remote_connection::{
|
||||
RemoteClientDelegate, RemoteConnectionModal, RemoteConnectionPrompt, SshConnectionHeader,
|
||||
|
|
@ -131,8 +131,11 @@ pub async fn open_remote_project(
|
|||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
let created_new_window = open_options.replace_window.is_none();
|
||||
let window = if let Some(window) = open_options.replace_window {
|
||||
window
|
||||
let (window, initial_workspace) = if let Some(window) = open_options.replace_window {
|
||||
let workspace = window.update(cx, |multi_workspace, _, _| {
|
||||
multi_workspace.workspace().clone()
|
||||
})?;
|
||||
(window, workspace)
|
||||
} else {
|
||||
let workspace_position = cx
|
||||
.update(|cx| {
|
||||
|
|
@ -145,7 +148,7 @@ pub async fn open_remote_project(
|
|||
cx.update(|cx| (app_state.build_window_options)(workspace_position.display, cx));
|
||||
options.window_bounds = workspace_position.window_bounds;
|
||||
|
||||
cx.open_window(options, |window, cx| {
|
||||
let window = cx.open_window(options, |window, cx| {
|
||||
let project = project::Project::local(
|
||||
app_state.client.clone(),
|
||||
app_state.node_runtime.clone(),
|
||||
|
|
@ -159,12 +162,17 @@ pub async fn open_remote_project(
|
|||
},
|
||||
cx,
|
||||
);
|
||||
cx.new(|cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
let mut workspace = Workspace::new(None, project, app_state.clone(), window, cx);
|
||||
workspace.centered_layout = workspace_position.centered_layout;
|
||||
workspace
|
||||
})
|
||||
})?
|
||||
});
|
||||
cx.new(|cx| MultiWorkspace::new(workspace, cx))
|
||||
})?;
|
||||
let workspace = window.update(cx, |multi_workspace, _, _cx| {
|
||||
multi_workspace.workspace().clone()
|
||||
})?;
|
||||
(window, workspace)
|
||||
};
|
||||
|
||||
loop {
|
||||
|
|
@ -172,35 +180,38 @@ pub async fn open_remote_project(
|
|||
let delegate = window.update(cx, {
|
||||
let paths = paths.clone();
|
||||
let connection_options = connection_options.clone();
|
||||
move |workspace, window, cx| {
|
||||
let initial_workspace = initial_workspace.clone();
|
||||
move |_multi_workspace: &mut MultiWorkspace, window, cx| {
|
||||
window.activate_window();
|
||||
workspace.hide_modal(window, cx);
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
RemoteConnectionModal::new(&connection_options, paths, window, cx)
|
||||
});
|
||||
initial_workspace.update(cx, |workspace, cx| {
|
||||
workspace.hide_modal(window, cx);
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
RemoteConnectionModal::new(&connection_options, paths, window, cx)
|
||||
});
|
||||
|
||||
let ui = workspace
|
||||
.active_modal::<RemoteConnectionModal>(cx)?
|
||||
.read(cx)
|
||||
.prompt
|
||||
.clone();
|
||||
let ui = workspace
|
||||
.active_modal::<RemoteConnectionModal>(cx)?
|
||||
.read(cx)
|
||||
.prompt
|
||||
.clone();
|
||||
|
||||
ui.update(cx, |ui, _cx| {
|
||||
ui.set_cancellation_tx(cancel_tx);
|
||||
});
|
||||
ui.update(cx, |ui, _cx| {
|
||||
ui.set_cancellation_tx(cancel_tx);
|
||||
});
|
||||
|
||||
Some(Arc::new(RemoteClientDelegate::new(
|
||||
window.window_handle(),
|
||||
ui.downgrade(),
|
||||
if let RemoteConnectionOptions::Ssh(options) = &connection_options {
|
||||
options
|
||||
.password
|
||||
.as_deref()
|
||||
.and_then(|pw| EncryptedPassword::try_from(pw).ok())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)))
|
||||
Some(Arc::new(RemoteClientDelegate::new(
|
||||
window.window_handle(),
|
||||
ui.downgrade(),
|
||||
if let RemoteConnectionOptions::Ssh(options) = &connection_options {
|
||||
options
|
||||
.password
|
||||
.as_deref()
|
||||
.and_then(|pw| EncryptedPassword::try_from(pw).ok())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)))
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
|
|
@ -209,13 +220,11 @@ pub async fn open_remote_project(
|
|||
let connection = remote::connect(connection_options.clone(), delegate.clone(), cx);
|
||||
let connection = select! {
|
||||
_ = cancel_rx => {
|
||||
window
|
||||
.update(cx, |workspace, _, cx| {
|
||||
if let Some(ui) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
ui.update(cx, |modal, cx| modal.finished(cx))
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
initial_workspace.update(cx, |workspace, cx| {
|
||||
if let Some(ui) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
ui.update(cx, |modal, cx| modal.finished(cx))
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
},
|
||||
|
|
@ -224,13 +233,11 @@ pub async fn open_remote_project(
|
|||
let remote_connection = match connection {
|
||||
Ok(connection) => connection,
|
||||
Err(e) => {
|
||||
window
|
||||
.update(cx, |workspace, _, cx| {
|
||||
if let Some(ui) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
ui.update(cx, |modal, cx| modal.finished(cx))
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
initial_workspace.update(cx, |workspace, cx| {
|
||||
if let Some(ui) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
ui.update(cx, |modal, cx| modal.finished(cx))
|
||||
}
|
||||
});
|
||||
log::error!("Failed to open project: {e:#}");
|
||||
let response = window
|
||||
.update(cx, |_, window, cx| {
|
||||
|
|
@ -284,13 +291,11 @@ pub async fn open_remote_project(
|
|||
})
|
||||
.await;
|
||||
|
||||
window
|
||||
.update(cx, |workspace, _, cx| {
|
||||
if let Some(ui) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
ui.update(cx, |modal, cx| modal.finished(cx))
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
initial_workspace.update(cx, |workspace, cx| {
|
||||
if let Some(ui) = workspace.active_modal::<RemoteConnectionModal>(cx) {
|
||||
ui.update(cx, |modal, cx| modal.finished(cx))
|
||||
}
|
||||
});
|
||||
|
||||
match opened_items {
|
||||
Err(e) => {
|
||||
|
|
@ -320,20 +325,20 @@ pub async fn open_remote_project(
|
|||
continue;
|
||||
}
|
||||
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
if created_new_window {
|
||||
window.remove_window();
|
||||
}
|
||||
trusted_worktrees::track_worktree_trust(
|
||||
workspace.project().read(cx).worktree_store(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
if created_new_window {
|
||||
window
|
||||
.update(cx, |_, window, _| window.remove_window())
|
||||
.ok();
|
||||
}
|
||||
initial_workspace.update(cx, |workspace, cx| {
|
||||
trusted_worktrees::track_worktree_trust(
|
||||
workspace.project().read(cx).worktree_store(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Ok(items) => {
|
||||
|
|
@ -366,14 +371,20 @@ pub async fn open_remote_project(
|
|||
break;
|
||||
}
|
||||
|
||||
// Register the remote client with extensions. We use `multi_workspace.workspace()` here
|
||||
// (not `initial_workspace`) because `open_remote_project_inner` activated the new remote
|
||||
// workspace, so the active workspace is now the one with the remote project.
|
||||
window
|
||||
.update(cx, |workspace, _, cx| {
|
||||
if let Some(client) = workspace.project().read(cx).remote_client() {
|
||||
if let Some(extension_store) = ExtensionStore::try_global(cx) {
|
||||
extension_store
|
||||
.update(cx, |store, cx| store.register_remote_client(client, cx));
|
||||
.update(cx, |multi_workspace: &mut MultiWorkspace, _, cx| {
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(client) = workspace.project().read(cx).remote_client() {
|
||||
if let Some(extension_store) = ExtensionStore::try_global(cx) {
|
||||
extension_store
|
||||
.update(cx, |store, cx| store.register_remote_client(client, cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
Ok(())
|
||||
|
|
@ -500,12 +511,16 @@ mod tests {
|
|||
let windows = cx.update(|cx| cx.windows().len());
|
||||
assert_eq!(windows, 1, "Should have opened a window");
|
||||
|
||||
let workspace_handle = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
||||
let multi_workspace_handle =
|
||||
cx.update(|cx| cx.windows()[0].downcast::<MultiWorkspace>().unwrap());
|
||||
|
||||
workspace_handle
|
||||
.update(cx, |workspace, _, cx| {
|
||||
let project = workspace.project().read(cx);
|
||||
assert!(project.is_remote(), "Project should be a remote project");
|
||||
multi_workspace_handle
|
||||
.update(cx, |multi_workspace, _, cx| {
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().read(cx);
|
||||
assert!(project.is_remote(), "Project should be a remote project");
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use crate::{
|
|||
ssh_config::parse_ssh_config_hosts,
|
||||
};
|
||||
use dev_container::{
|
||||
DevContainerConfig, find_devcontainer_configs, start_dev_container_with_config,
|
||||
DevContainerConfig, DevContainerContext, find_devcontainer_configs,
|
||||
start_dev_container_with_config,
|
||||
};
|
||||
use editor::Editor;
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ use util::{
|
|||
rel_path::RelPath,
|
||||
};
|
||||
use workspace::{
|
||||
ModalView, OpenLog, OpenOptions, Toast, Workspace,
|
||||
ModalView, MultiWorkspace, OpenLog, OpenOptions, Toast, Workspace,
|
||||
notifications::{DetachAndPromptErr, NotificationId},
|
||||
open_remote_project_with_existing_connection,
|
||||
};
|
||||
|
|
@ -478,10 +479,11 @@ impl ProjectPicker {
|
|||
.log_err()?;
|
||||
let window = cx
|
||||
.open_window(options, |window, cx| {
|
||||
cx.new(|cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
telemetry::event!("SSH Project Created");
|
||||
Workspace::new(None, project.clone(), app_state.clone(), window, cx)
|
||||
})
|
||||
});
|
||||
cx.new(|cx| MultiWorkspace::new(workspace, cx))
|
||||
})
|
||||
.log_err()?;
|
||||
|
||||
|
|
@ -808,11 +810,18 @@ impl RemoteServerProjects {
|
|||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let this = Self::new_inner(
|
||||
Mode::CreateRemoteDevContainer(CreateRemoteDevContainer::new(
|
||||
DevContainerCreationProgress::Creating,
|
||||
cx,
|
||||
)),
|
||||
let configs = workspace
|
||||
.read_with(cx, |workspace, cx| find_devcontainer_configs(workspace, cx))
|
||||
.unwrap_or_default();
|
||||
|
||||
let initial_mode = if configs.len() > 1 {
|
||||
DevContainerCreationProgress::SelectingConfig
|
||||
} else {
|
||||
DevContainerCreationProgress::Creating
|
||||
};
|
||||
|
||||
let mut this = Self::new_inner(
|
||||
Mode::CreateRemoteDevContainer(CreateRemoteDevContainer::new(initial_mode, cx)),
|
||||
false,
|
||||
fs,
|
||||
window,
|
||||
|
|
@ -820,35 +829,15 @@ impl RemoteServerProjects {
|
|||
cx,
|
||||
);
|
||||
|
||||
// Spawn a task to scan for configs and then start the container
|
||||
cx.spawn_in(window, async move |entity, cx| {
|
||||
let configs = find_devcontainer_configs(cx);
|
||||
|
||||
entity
|
||||
.update_in(cx, |this, window, cx| {
|
||||
if configs.len() > 1 {
|
||||
// Multiple configs found - show selection UI
|
||||
let delegate = DevContainerPickerDelegate::new(configs, cx.weak_entity());
|
||||
this.dev_container_picker = Some(
|
||||
cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)),
|
||||
);
|
||||
|
||||
let state = CreateRemoteDevContainer::new(
|
||||
DevContainerCreationProgress::SelectingConfig,
|
||||
cx,
|
||||
);
|
||||
this.mode = Mode::CreateRemoteDevContainer(state);
|
||||
cx.notify();
|
||||
} else {
|
||||
// Single or no config - proceed with opening
|
||||
let config = configs.into_iter().next();
|
||||
this.open_dev_container(config, window, cx);
|
||||
this.view_in_progress_dev_container(window, cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
if configs.len() > 1 {
|
||||
let delegate = DevContainerPickerDelegate::new(configs, cx.weak_entity());
|
||||
this.dev_container_picker =
|
||||
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)));
|
||||
} else {
|
||||
let config = configs.into_iter().next();
|
||||
this.open_dev_container(config, window, cx);
|
||||
this.view_in_progress_dev_container(window, cx);
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
|
@ -1551,7 +1540,9 @@ impl RemoteServerProjects {
|
|||
|
||||
let replace_window = match (create_new_window, secondary_confirm) {
|
||||
(true, false) | (false, true) => None,
|
||||
(true, true) | (false, false) => window.window_handle().downcast::<Workspace>(),
|
||||
(true, true) | (false, false) => {
|
||||
window.window_handle().downcast::<MultiWorkspace>()
|
||||
}
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
|
|
@ -1803,25 +1794,25 @@ impl RemoteServerProjects {
|
|||
}
|
||||
|
||||
fn init_dev_container_mode(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.spawn_in(window, async move |entity, cx| {
|
||||
let configs = find_devcontainer_configs(cx);
|
||||
let configs = self
|
||||
.workspace
|
||||
.read_with(cx, |workspace, cx| find_devcontainer_configs(workspace, cx))
|
||||
.unwrap_or_default();
|
||||
|
||||
entity
|
||||
.update_in(cx, |this, window, cx| {
|
||||
let delegate = DevContainerPickerDelegate::new(configs, cx.weak_entity());
|
||||
this.dev_container_picker =
|
||||
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)));
|
||||
if configs.len() > 1 {
|
||||
let delegate = DevContainerPickerDelegate::new(configs, cx.weak_entity());
|
||||
self.dev_container_picker =
|
||||
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)));
|
||||
|
||||
let state = CreateRemoteDevContainer::new(
|
||||
DevContainerCreationProgress::SelectingConfig,
|
||||
cx,
|
||||
);
|
||||
this.mode = Mode::CreateRemoteDevContainer(state);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
let state =
|
||||
CreateRemoteDevContainer::new(DevContainerCreationProgress::SelectingConfig, cx);
|
||||
self.mode = Mode::CreateRemoteDevContainer(state);
|
||||
cx.notify();
|
||||
} else {
|
||||
let config = configs.into_iter().next();
|
||||
self.open_dev_container(config, window, cx);
|
||||
self.view_in_progress_dev_container(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn open_dev_container(
|
||||
|
|
@ -1830,21 +1821,25 @@ impl RemoteServerProjects {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(app_state) = self
|
||||
let Some((app_state, context)) = self
|
||||
.workspace
|
||||
.read_with(cx, |workspace, _| workspace.app_state().clone())
|
||||
.read_with(cx, |workspace, cx| {
|
||||
let app_state = workspace.app_state().clone();
|
||||
let context = DevContainerContext::from_workspace(workspace, cx)?;
|
||||
Some((app_state, context))
|
||||
})
|
||||
.log_err()
|
||||
.flatten()
|
||||
else {
|
||||
log::error!("No active project directory for Dev Container");
|
||||
return;
|
||||
};
|
||||
|
||||
let replace_window = window.window_handle().downcast::<Workspace>();
|
||||
let replace_window = window.window_handle().downcast::<MultiWorkspace>();
|
||||
|
||||
cx.spawn_in(window, async move |entity, cx| {
|
||||
let (connection, starting_dir) =
|
||||
match start_dev_container_with_config(cx, app_state.node_runtime.clone(), config)
|
||||
.await
|
||||
{
|
||||
match start_dev_container_with_config(context, config).await {
|
||||
Ok((c, s)) => (Connection::DevContainer(c), s),
|
||||
Err(e) => {
|
||||
log::error!("Failed to start dev container: {:?}", e);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use ui::{
|
|||
Render, Styled, StyledExt, Toggleable, Window, div, h_flex, rems, v_flex,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{ModalView, Workspace};
|
||||
use workspace::{ModalView, MultiWorkspace};
|
||||
|
||||
use crate::open_remote_project;
|
||||
|
||||
|
|
@ -249,7 +249,7 @@ impl WslOpenModal {
|
|||
false => !secondary,
|
||||
};
|
||||
let replace_window = match replace_current_window {
|
||||
true => window.window_handle().downcast::<Workspace>(),
|
||||
true => window.window_handle().downcast::<MultiWorkspace>(),
|
||||
false => None,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -779,8 +779,10 @@ mod tests {
|
|||
let fs = project::FakeFs::new(cx.background_executor.clone());
|
||||
let project = project::Project::test(fs, [] as [&Path; 0], cx).await;
|
||||
let window =
|
||||
cx.add_window(|window, cx| workspace::Workspace::test_new(project, window, cx));
|
||||
let workspace = window.root(cx).expect("workspace should exist");
|
||||
cx.add_window(|window, cx| workspace::MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let visual_cx = gpui::VisualTestContext::from_window(window.into(), cx);
|
||||
(visual_cx, weak_workspace)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use editor::{CompletionProvider, SelectionEffects};
|
|||
use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab};
|
||||
use gpui::{
|
||||
App, Bounds, DEFAULT_ADDITIONAL_WINDOW_SIZE, Entity, EventEmitter, Focusable, PromptLevel,
|
||||
Subscription, Task, TextStyle, TitlebarOptions, WindowBounds, WindowHandle, WindowOptions,
|
||||
actions, point, size, transparent_black,
|
||||
Subscription, Task, TextStyle, Tiling, TitlebarOptions, WindowBounds, WindowHandle,
|
||||
WindowOptions, actions, point, size, transparent_black,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry, language_settings::SoftWrap};
|
||||
use language_model::{
|
||||
|
|
@ -24,7 +24,7 @@ use theme::ThemeSettings;
|
|||
use ui::{Divider, ListItem, ListItemSpacing, ListSubHeader, Tooltip, prelude::*};
|
||||
use ui_input::ErasedEditor;
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::{Workspace, WorkspaceSettings, client_side_decorations};
|
||||
use workspace::{MultiWorkspace, Workspace, WorkspaceSettings, client_side_decorations};
|
||||
use zed_actions::assistant::InlineAssist;
|
||||
|
||||
use prompt_store::*;
|
||||
|
|
@ -968,12 +968,14 @@ impl RulesLibrary {
|
|||
.assist(rule_editor, initial_prompt, window, cx);
|
||||
} else {
|
||||
for window in cx.windows() {
|
||||
if let Some(workspace) = window.downcast::<Workspace>() {
|
||||
let panel = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
if let Some(multi_workspace) = window.downcast::<MultiWorkspace>() {
|
||||
let panel = multi_workspace
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
window.activate_window();
|
||||
self.inline_assist_delegate
|
||||
.focus_agent_panel(workspace, window, cx)
|
||||
multi_workspace.workspace().update(cx, |workspace, cx| {
|
||||
self.inline_assist_delegate
|
||||
.focus_agent_panel(workspace, window, cx)
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
if panel == Some(true) {
|
||||
|
|
@ -1427,6 +1429,7 @@ impl Render for RulesLibrary {
|
|||
),
|
||||
window,
|
||||
cx,
|
||||
Tiling::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2495,7 +2495,6 @@ pub fn perform_project_search(
|
|||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::{
|
||||
ops::Deref as _,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
Arc,
|
||||
|
|
@ -2516,7 +2515,7 @@ pub mod tests {
|
|||
};
|
||||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||
use util_macros::perf;
|
||||
use workspace::DeploySearch;
|
||||
use workspace::{DeploySearch, MultiWorkspace};
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
|
|
@ -2632,8 +2631,11 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
|
||||
let search_view = cx.add_window(|window, cx| {
|
||||
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
|
||||
|
|
@ -2791,14 +2793,16 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
|
||||
let active_item = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.unwrap()
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
|
|
@ -2809,27 +2813,24 @@ pub mod tests {
|
|||
"Expected no search panel to be active"
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::find(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::find(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let Some(search_view) = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.unwrap()
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
|
|
@ -2969,16 +2970,14 @@ pub mod tests {
|
|||
});
|
||||
}).unwrap();
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::find(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::find(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
window.update(cx, |_, window, cx| {
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
|
||||
|
|
@ -3032,30 +3031,30 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
|
||||
window
|
||||
.update(cx, move |workspace, window, cx| {
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, move |workspace, window, cx| {
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::find(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::find(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let Some(search_view) = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.unwrap()
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
|
|
@ -3153,14 +3152,16 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
|
||||
let active_item = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.unwrap()
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
|
|
@ -3171,22 +3172,19 @@ pub mod tests {
|
|||
"Expected no search panel to be active"
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
});
|
||||
|
||||
let Some(search_view) = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.unwrap()
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
|
|
@ -3326,16 +3324,13 @@ pub mod tests {
|
|||
});
|
||||
}).unwrap();
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
});
|
||||
cx.background_executor.run_until_parked();
|
||||
let Some(search_view_2) = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.unwrap()
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
|
|
@ -3456,8 +3451,11 @@ pub mod tests {
|
|||
let worktree_id = project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
|
||||
let active_item = cx.read(|cx| {
|
||||
|
|
@ -3473,17 +3471,15 @@ pub mod tests {
|
|||
"Expected no search panel to be active"
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, move |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, move |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
});
|
||||
|
||||
let a_dir_entry = cx.update(|cx| {
|
||||
let a_dir_entry = cx.update(|_, cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
|
|
@ -3493,11 +3489,9 @@ pub mod tests {
|
|||
.clone()
|
||||
});
|
||||
assert!(a_dir_entry.is_dir());
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::new_search_in_directory(workspace, &a_dir_entry.path, window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::new_search_in_directory(workspace, &a_dir_entry.path, window, cx)
|
||||
});
|
||||
|
||||
let Some(search_view) = cx.read(|cx| {
|
||||
workspace
|
||||
|
|
@ -3576,24 +3570,25 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
|
||||
window
|
||||
.update(cx, {
|
||||
let search_bar = search_bar.clone();
|
||||
|workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, {
|
||||
let search_bar = search_bar.clone();
|
||||
|workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 1);
|
||||
workspace.panes()[0].update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
});
|
||||
|
||||
let search_view = cx.read(|cx| {
|
||||
workspace
|
||||
|
|
@ -3908,21 +3903,22 @@ pub mod tests {
|
|||
this.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let panes: Vec<_> = window
|
||||
.update(cx, |this, _, _| this.panes().to_owned())
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
let panes: Vec<_> = workspace.update_in(cx, |this, _, _| this.panes().to_owned());
|
||||
|
||||
let search_bar_1 = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
let search_bar_2 = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
|
||||
assert_eq!(panes.len(), 1);
|
||||
let first_pane = panes.first().cloned().unwrap();
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 0);
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 0);
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, rel_path("one.rs")),
|
||||
Some(first_pane.downgrade()),
|
||||
|
|
@ -3931,25 +3927,22 @@ pub mod tests {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1);
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 1);
|
||||
|
||||
// Add a project search item to the first pane
|
||||
window
|
||||
.update(cx, {
|
||||
let search_bar = search_bar_1.clone();
|
||||
|workspace, window, cx| {
|
||||
first_pane.update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, {
|
||||
let search_bar = search_bar_1.clone();
|
||||
|workspace, window, cx| {
|
||||
first_pane.update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
});
|
||||
let search_view_1 = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
|
|
@ -3958,8 +3951,8 @@ pub mod tests {
|
|||
.expect("Search view expected to appear after new search event trigger")
|
||||
});
|
||||
|
||||
let second_pane = window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let second_pane = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.split_and_clone(
|
||||
first_pane.clone(),
|
||||
workspace::SplitDirection::Right,
|
||||
|
|
@ -3967,30 +3960,27 @@ pub mod tests {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1);
|
||||
assert_eq!(cx.update(|_, cx| second_pane.read(cx).items_len()), 1);
|
||||
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1);
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 2);
|
||||
assert_eq!(cx.update(|_, cx| second_pane.read(cx).items_len()), 1);
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 2);
|
||||
|
||||
// Add a project search item to the second pane
|
||||
window
|
||||
.update(cx, {
|
||||
let search_bar = search_bar_2.clone();
|
||||
let pane = second_pane.clone();
|
||||
move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 2);
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, {
|
||||
let search_bar = search_bar_2.clone();
|
||||
let pane = second_pane.clone();
|
||||
move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 2);
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
});
|
||||
|
||||
let search_view_2 = cx.read(|cx| {
|
||||
workspace
|
||||
|
|
@ -4001,8 +3991,8 @@ pub mod tests {
|
|||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 2);
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 2);
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 2);
|
||||
assert_eq!(cx.update(|_, cx| second_pane.read(cx).items_len()), 2);
|
||||
|
||||
let update_search_view =
|
||||
|search_view: &Entity<ProjectSearchView>, query: &str, cx: &mut TestAppContext| {
|
||||
|
|
@ -4133,15 +4123,17 @@ pub mod tests {
|
|||
let worktree_id = project.update(cx, |this, cx| {
|
||||
this.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let panes: Vec<_> = window
|
||||
.update(cx, |this, _, _| this.panes().to_owned())
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let panes: Vec<_> = workspace.update_in(cx, |this, _, _| this.panes().to_owned());
|
||||
assert_eq!(panes.len(), 1);
|
||||
let first_pane = panes.first().cloned().unwrap();
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 0);
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 0);
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, rel_path("one.rs")),
|
||||
Some(first_pane.downgrade()),
|
||||
|
|
@ -4150,12 +4142,11 @@ pub mod tests {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1);
|
||||
let second_pane = window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 1);
|
||||
let second_pane = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.split_and_clone(
|
||||
first_pane.clone(),
|
||||
workspace::SplitDirection::Right,
|
||||
|
|
@ -4163,10 +4154,9 @@ pub mod tests {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1);
|
||||
assert_eq!(cx.update(|_, cx| second_pane.read(cx).items_len()), 1);
|
||||
assert!(
|
||||
window
|
||||
.update(cx, |_, window, cx| second_pane
|
||||
|
|
@ -4175,76 +4165,66 @@ pub mod tests {
|
|||
.unwrap()
|
||||
);
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
window
|
||||
.update(cx, {
|
||||
let search_bar = search_bar.clone();
|
||||
let pane = first_pane.clone();
|
||||
move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 2);
|
||||
pane.update(cx, move |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, {
|
||||
let search_bar = search_bar.clone();
|
||||
let pane = first_pane.clone();
|
||||
move |workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 2);
|
||||
pane.update(cx, move |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add a project search item to the second pane
|
||||
window
|
||||
.update(cx, {
|
||||
|workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 2);
|
||||
second_pane.update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
workspace.update_in(cx, {
|
||||
|workspace, window, cx| {
|
||||
assert_eq!(workspace.panes().len(), 2);
|
||||
second_pane.update(cx, |pane, cx| {
|
||||
pane.toolbar()
|
||||
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx))
|
||||
});
|
||||
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx)
|
||||
}
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 2);
|
||||
assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1);
|
||||
assert_eq!(cx.update(|_, cx| second_pane.read(cx).items_len()), 2);
|
||||
assert_eq!(cx.update(|_, cx| first_pane.read(cx).items_len()), 1);
|
||||
|
||||
// Focus the first pane
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(workspace.active_pane(), &second_pane);
|
||||
second_pane.update(cx, |this, cx| {
|
||||
assert_eq!(this.active_item_index(), 1);
|
||||
this.activate_previous_item(&Default::default(), window, cx);
|
||||
assert_eq!(this.active_item_index(), 0);
|
||||
});
|
||||
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
window
|
||||
.update(cx, |workspace, _, cx| {
|
||||
assert_eq!(workspace.active_pane(), &first_pane);
|
||||
assert_eq!(first_pane.read(cx).items_len(), 1);
|
||||
assert_eq!(second_pane.read(cx).items_len(), 2);
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
assert_eq!(workspace.active_pane(), &second_pane);
|
||||
second_pane.update(cx, |this, cx| {
|
||||
assert_eq!(this.active_item_index(), 1);
|
||||
this.activate_previous_item(&Default::default(), window, cx);
|
||||
assert_eq!(this.active_item_index(), 0);
|
||||
});
|
||||
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx);
|
||||
});
|
||||
workspace.update_in(cx, |workspace, _, cx| {
|
||||
assert_eq!(workspace.active_pane(), &first_pane);
|
||||
assert_eq!(first_pane.read(cx).items_len(), 1);
|
||||
assert_eq!(second_pane.read(cx).items_len(), 2);
|
||||
});
|
||||
|
||||
// Deploy a new search
|
||||
cx.dispatch_action(window.into(), DeploySearch::find());
|
||||
cx.dispatch_action(DeploySearch::find());
|
||||
|
||||
// Both panes should now have a project search in them
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(workspace.active_pane(), &first_pane);
|
||||
first_pane.read_with(cx, |this, _| {
|
||||
assert_eq!(this.active_item_index(), 1);
|
||||
assert_eq!(this.items_len(), 2);
|
||||
});
|
||||
second_pane.update(cx, |this, cx| {
|
||||
assert!(!cx.focus_handle().contains_focused(window, cx));
|
||||
assert_eq!(this.items_len(), 2);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
assert_eq!(workspace.active_pane(), &first_pane);
|
||||
first_pane.read_with(cx, |this, _| {
|
||||
assert_eq!(this.active_item_index(), 1);
|
||||
assert_eq!(this.items_len(), 2);
|
||||
});
|
||||
second_pane.update(cx, |this, cx| {
|
||||
assert!(!cx.focus_handle().contains_focused(window, cx));
|
||||
assert_eq!(this.items_len(), 2);
|
||||
});
|
||||
});
|
||||
|
||||
// Focus the second pane's non-search item
|
||||
window
|
||||
|
|
@ -4256,7 +4236,7 @@ pub mod tests {
|
|||
.unwrap();
|
||||
|
||||
// Deploy a new search
|
||||
cx.dispatch_action(window.into(), DeploySearch::find());
|
||||
cx.dispatch_action(DeploySearch::find());
|
||||
|
||||
// The project search view should now be focused in the second pane
|
||||
// And the number of items should be unchanged.
|
||||
|
|
@ -4310,8 +4290,11 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let search = cx.new(|cx| ProjectSearch::new(project, cx));
|
||||
let search_view = cx.add_window(|window, cx| {
|
||||
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
|
||||
|
|
@ -4374,9 +4357,12 @@ pub mod tests {
|
|||
let worktree_id = project.update(cx, |this, cx| {
|
||||
this.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let mut cx = VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
let editor = workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
|
|
@ -4398,9 +4384,7 @@ pub mod tests {
|
|||
search_bar
|
||||
});
|
||||
|
||||
let panes: Vec<_> = window
|
||||
.update(&mut cx, |this, _, _| this.panes().to_owned())
|
||||
.unwrap();
|
||||
let panes: Vec<_> = workspace.update_in(&mut cx, |this, _, _| this.panes().to_owned());
|
||||
assert_eq!(panes.len(), 1);
|
||||
let pane = panes.first().cloned().unwrap();
|
||||
pane.update_in(&mut cx, |pane, window, cx| {
|
||||
|
|
@ -4450,7 +4434,12 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).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
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
struct EmptyModalView {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
|
|
@ -4468,34 +4457,28 @@ pub mod tests {
|
|||
}
|
||||
impl workspace::ModalView for EmptyModalView {}
|
||||
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |_, cx| EmptyModalView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
});
|
||||
assert!(workspace.has_active_modal(window, cx));
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |_, cx| EmptyModalView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
});
|
||||
assert!(workspace.has_active_modal(window, cx));
|
||||
});
|
||||
|
||||
cx.dispatch_action(window.into(), Deploy::find());
|
||||
cx.dispatch_action(Deploy::find());
|
||||
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert!(!workspace.has_active_modal(window, cx));
|
||||
workspace.toggle_modal(window, cx, |_, cx| EmptyModalView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
});
|
||||
assert!(workspace.has_active_modal(window, cx));
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
assert!(!workspace.has_active_modal(window, cx));
|
||||
workspace.toggle_modal(window, cx, |_, cx| EmptyModalView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
});
|
||||
assert!(workspace.has_active_modal(window, cx));
|
||||
});
|
||||
|
||||
cx.dispatch_action(window.into(), DeploySearch::find());
|
||||
cx.dispatch_action(DeploySearch::find());
|
||||
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert!(!workspace.has_active_modal(window, cx));
|
||||
})
|
||||
.unwrap();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
assert!(!workspace.has_active_modal(window, cx));
|
||||
});
|
||||
}
|
||||
|
||||
#[perf]
|
||||
|
|
@ -4562,8 +4545,12 @@ pub mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
|
||||
let search_view = cx.add_window(|window, cx| {
|
||||
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
|
||||
|
|
@ -4609,8 +4596,8 @@ pub mod tests {
|
|||
"We did drop the previous buffer when cleared the old project search results, hence another query was made",
|
||||
);
|
||||
|
||||
let singleton_editor = window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let singleton_editor = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from(path!("/dir/main.rs")),
|
||||
workspace::OpenOptions::default(),
|
||||
|
|
@ -4618,7 +4605,6 @@ pub mod tests {
|
|||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) {
|
|||
struct NotifType();
|
||||
let notification_id = NotificationId::unique::<NotifType>();
|
||||
|
||||
let Some(workspace) = window.root::<Workspace>().flatten() else {
|
||||
let Some(workspace) = Workspace::for_window(window, cx) else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,15 @@ impl Session {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test_with_old_session(old_session_id: String) -> Self {
|
||||
Self {
|
||||
session_id: uuid::Uuid::new_v4().to_string(),
|
||||
old_session_id: Some(old_session_id),
|
||||
old_window_ids: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
&self.session_id
|
||||
}
|
||||
|
|
@ -109,6 +118,11 @@ impl AppSession {
|
|||
self.session.old_session_id.as_deref()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn replace_session_for_test(&mut self, session: Session) {
|
||||
self.session = session;
|
||||
}
|
||||
|
||||
pub fn last_session_window_stack(&self) -> Option<Vec<WindowId>> {
|
||||
self.session.old_window_ids.clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use settings::Settings;
|
||||
use theme::{self, ThemeSettings};
|
||||
use workspace::{self, AppState};
|
||||
use workspace::{self, AppState, MultiWorkspace};
|
||||
use zed_actions::settings_profile_selector;
|
||||
|
||||
async fn init_test(
|
||||
|
|
@ -320,8 +320,11 @@ mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, ["/test".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let cx = VisualTestContext::from_window(*window, cx).into_mut();
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
|
||||
cx.update(|_, cx| {
|
||||
assert!(!cx.has_global::<ActiveSettingsProfileName>());
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ use fuzzy::StringMatchCandidate;
|
|||
use gpui::{
|
||||
Action, App, AsyncApp, ClipboardItem, DEFAULT_ADDITIONAL_WINDOW_SIZE, Div, Entity, FocusHandle,
|
||||
Focusable, Global, KeyContext, ListState, ReadGlobal as _, ScrollHandle, Stateful,
|
||||
Subscription, Task, TitlebarOptions, UniformListScrollHandle, WeakEntity, Window, WindowBounds,
|
||||
WindowHandle, WindowOptions, actions, div, list, point, prelude::*, px, uniform_list,
|
||||
Subscription, Task, Tiling, TitlebarOptions, UniformListScrollHandle, WeakEntity, Window,
|
||||
WindowBounds, WindowHandle, WindowOptions, actions, div, list, point, prelude::*, px,
|
||||
uniform_list,
|
||||
};
|
||||
|
||||
use language::Buffer;
|
||||
|
|
@ -40,7 +41,9 @@ use ui::{
|
|||
};
|
||||
|
||||
use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
|
||||
use workspace::{AppState, OpenOptions, OpenVisible, Workspace, client_side_decorations};
|
||||
use workspace::{
|
||||
AppState, MultiWorkspace, OpenOptions, OpenVisible, Workspace, client_side_decorations,
|
||||
};
|
||||
use zed_actions::{OpenProjectSettings, OpenSettings, OpenSettingsAt};
|
||||
|
||||
use crate::components::{
|
||||
|
|
@ -394,7 +397,7 @@ pub fn init(cx: &mut App) {
|
|||
|workspace, OpenSettingsAt { path }: &OpenSettingsAt, window, cx| {
|
||||
let window_handle = window
|
||||
.window_handle()
|
||||
.downcast::<Workspace>()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.expect("Workspaces are root Windows");
|
||||
open_settings_editor(workspace, Some(&path), false, window_handle, cx);
|
||||
},
|
||||
|
|
@ -402,14 +405,14 @@ pub fn init(cx: &mut App) {
|
|||
.register_action(|workspace, _: &OpenSettings, window, cx| {
|
||||
let window_handle = window
|
||||
.window_handle()
|
||||
.downcast::<Workspace>()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.expect("Workspaces are root Windows");
|
||||
open_settings_editor(workspace, None, false, window_handle, cx);
|
||||
})
|
||||
.register_action(|workspace, _: &OpenProjectSettings, window, cx| {
|
||||
let window_handle = window
|
||||
.window_handle()
|
||||
.downcast::<Workspace>()
|
||||
.downcast::<MultiWorkspace>()
|
||||
.expect("Workspaces are root Windows");
|
||||
open_settings_editor(workspace, None, true, window_handle, cx);
|
||||
});
|
||||
|
|
@ -549,7 +552,7 @@ pub fn open_settings_editor(
|
|||
_workspace: &mut Workspace,
|
||||
path: Option<&str>,
|
||||
open_project_settings: bool,
|
||||
workspace_handle: WindowHandle<Workspace>,
|
||||
workspace_handle: WindowHandle<MultiWorkspace>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
telemetry::event!("Settings Viewed");
|
||||
|
|
@ -717,7 +720,7 @@ fn active_language_mut() -> Option<std::sync::RwLockWriteGuard<'static, Option<S
|
|||
|
||||
pub struct SettingsWindow {
|
||||
title_bar: Option<Entity<PlatformTitleBar>>,
|
||||
original_window: Option<WindowHandle<Workspace>>,
|
||||
original_window: Option<WindowHandle<MultiWorkspace>>,
|
||||
files: Vec<(SettingsUiFile, FocusHandle)>,
|
||||
worktree_root_dirs: HashMap<WorktreeId, String>,
|
||||
current_file: SettingsUiFile,
|
||||
|
|
@ -1450,7 +1453,7 @@ impl SettingsUiFile {
|
|||
|
||||
impl SettingsWindow {
|
||||
fn new(
|
||||
original_window: Option<WindowHandle<Workspace>>,
|
||||
original_window: Option<WindowHandle<MultiWorkspace>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
|
@ -1521,34 +1524,21 @@ impl SettingsWindow {
|
|||
.detach();
|
||||
|
||||
if let Some(app_state) = AppState::global(cx).upgrade() {
|
||||
for project in app_state
|
||||
let workspaces: Vec<Entity<Workspace>> = app_state
|
||||
.workspace_store
|
||||
.read(cx)
|
||||
.workspaces()
|
||||
.iter()
|
||||
.filter_map(|space| {
|
||||
space
|
||||
.read(cx)
|
||||
.ok()
|
||||
.map(|workspace| workspace.project().clone())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
.filter_map(|weak| weak.upgrade())
|
||||
.collect();
|
||||
|
||||
for workspace in workspaces {
|
||||
let project = workspace.read(cx).project().clone();
|
||||
cx.observe_release_in(&project, window, |this, _, window, cx| {
|
||||
this.fetch_files(window, cx)
|
||||
})
|
||||
.detach();
|
||||
cx.subscribe_in(&project, window, Self::handle_project_event)
|
||||
.detach();
|
||||
}
|
||||
|
||||
for workspace in app_state
|
||||
.workspace_store
|
||||
.read(cx)
|
||||
.workspaces()
|
||||
.iter()
|
||||
.filter_map(|space| space.entity(cx).ok())
|
||||
{
|
||||
cx.observe_release_in(&workspace, window, |this, _, window, cx| {
|
||||
this.fetch_files(window, cx)
|
||||
})
|
||||
|
|
@ -3324,56 +3314,19 @@ impl SettingsWindow {
|
|||
return;
|
||||
};
|
||||
original_window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace
|
||||
.with_local_or_wsl_workspace(window, cx, |workspace, window, cx| {
|
||||
let project = workspace.project().clone();
|
||||
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let (config_dir, settings_file) =
|
||||
project.update(cx, |project, cx| {
|
||||
(
|
||||
project.try_windows_path_to_wsl(
|
||||
paths::config_dir().as_path(),
|
||||
cx,
|
||||
),
|
||||
project.try_windows_path_to_wsl(
|
||||
paths::settings_file().as_path(),
|
||||
cx,
|
||||
),
|
||||
)
|
||||
});
|
||||
let config_dir = config_dir.await?;
|
||||
let settings_file = settings_file.await?;
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(&config_dir, false, cx)
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_paths(
|
||||
vec![settings_file],
|
||||
OpenOptions {
|
||||
visible: Some(OpenVisible::None),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
workspace.update_in(cx, |_, window, cx| {
|
||||
window.activate_window();
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace
|
||||
.workspace()
|
||||
.clone()
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.with_local_or_wsl_workspace(
|
||||
window,
|
||||
cx,
|
||||
open_user_settings_in_workspace,
|
||||
)
|
||||
.detach();
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
|
||||
|
|
@ -3385,22 +3338,22 @@ impl SettingsWindow {
|
|||
return;
|
||||
};
|
||||
|
||||
let Some((worktree, corresponding_workspace)) = app_state
|
||||
let Some((workspace_window, worktree, corresponding_workspace)) = app_state
|
||||
.workspace_store
|
||||
.read(cx)
|
||||
.workspaces()
|
||||
.iter()
|
||||
.find_map(|workspace| {
|
||||
.workspaces_with_windows()
|
||||
.filter_map(|(window_handle, weak)| {
|
||||
let workspace = weak.upgrade()?;
|
||||
let window = window_handle.downcast::<MultiWorkspace>()?;
|
||||
Some((window, workspace))
|
||||
})
|
||||
.find_map(|(window, workspace): (_, Entity<Workspace>)| {
|
||||
workspace
|
||||
.read_with(cx, |workspace, cx| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.zip(Some(*workspace))
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)
|
||||
.map(|worktree| (window, worktree, workspace))
|
||||
})
|
||||
else {
|
||||
log::error!(
|
||||
|
|
@ -3428,14 +3381,15 @@ impl SettingsWindow {
|
|||
|
||||
// TODO: move zed::open_local_file() APIs to this crate, and
|
||||
// re-implement the "initial_contents" behavior
|
||||
corresponding_workspace
|
||||
let workspace_weak = corresponding_workspace.downgrade();
|
||||
workspace_window
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
if let Some(create_task) = create_task {
|
||||
create_task.await.ok()?;
|
||||
};
|
||||
|
||||
workspace
|
||||
workspace_weak
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, settings_path.clone()),
|
||||
|
|
@ -3449,7 +3403,7 @@ impl SettingsWindow {
|
|||
.await
|
||||
.log_err()?;
|
||||
|
||||
workspace
|
||||
workspace_weak
|
||||
.update_in(cx, |_, window, cx| {
|
||||
window.activate_window();
|
||||
cx.notify();
|
||||
|
|
@ -3753,12 +3707,13 @@ impl Render for SettingsWindow {
|
|||
),
|
||||
window,
|
||||
cx,
|
||||
Tiling::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn all_projects(
|
||||
window: Option<&WindowHandle<Workspace>>,
|
||||
window: Option<&WindowHandle<MultiWorkspace>>,
|
||||
cx: &App,
|
||||
) -> impl Iterator<Item = Entity<Project>> {
|
||||
let mut seen_project_ids = std::collections::HashSet::new();
|
||||
|
|
@ -3769,10 +3724,19 @@ fn all_projects(
|
|||
.workspace_store
|
||||
.read(cx)
|
||||
.workspaces()
|
||||
.iter()
|
||||
.filter_map(|workspace| Some(workspace.read(cx).ok()?.project().clone()))
|
||||
.filter_map(|weak| weak.upgrade())
|
||||
.map(|workspace: Entity<Workspace>| workspace.read(cx).project().clone())
|
||||
.chain(
|
||||
window.and_then(|workspace| Some(workspace.read(cx).ok()?.project().clone())),
|
||||
window
|
||||
.and_then(|handle| handle.read(cx).ok())
|
||||
.into_iter()
|
||||
.flat_map(|multi_workspace| {
|
||||
multi_workspace
|
||||
.workspaces()
|
||||
.iter()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
)
|
||||
.filter(move |project| seen_project_ids.insert(project.entity_id()))
|
||||
})
|
||||
|
|
@ -3780,6 +3744,51 @@ fn all_projects(
|
|||
.flatten()
|
||||
}
|
||||
|
||||
fn open_user_settings_in_workspace(
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let (config_dir, settings_file) = project.update(cx, |project, cx| {
|
||||
(
|
||||
project.try_windows_path_to_wsl(paths::config_dir().as_path(), cx),
|
||||
project.try_windows_path_to_wsl(paths::settings_file().as_path(), cx),
|
||||
)
|
||||
});
|
||||
let config_dir = config_dir.await?;
|
||||
let settings_file = settings_file.await?;
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(&config_dir, false, cx)
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_paths(
|
||||
vec![settings_file],
|
||||
OpenOptions {
|
||||
visible: Some(OpenVisible::None),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
workspace.update_in(cx, |_, window, cx| {
|
||||
window.activate_window();
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_settings_file(
|
||||
file: SettingsUiFile,
|
||||
file_name: Option<&'static str>,
|
||||
|
|
@ -4762,29 +4771,33 @@ pub mod test {
|
|||
.await
|
||||
.expect("Failed to create worktree_c");
|
||||
|
||||
let (_workspace1, cx) = cx.add_window_view(|window, cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project1.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let (_multi_workspace1, cx) = cx.add_window_view(|window, cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project1.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
|
||||
let _workspace1_handle = cx.window_handle().downcast::<Workspace>().unwrap();
|
||||
|
||||
let (_workspace2, cx) = cx.add_window_view(|window, cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project2.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let (_multi_workspace2, cx) = cx.add_window_view(|window, cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project2.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
|
||||
let workspace2_handle = cx.window_handle().downcast::<Workspace>().unwrap();
|
||||
let workspace2_handle = cx.window_handle().downcast::<MultiWorkspace>().unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
|
|
@ -4903,17 +4916,20 @@ pub mod test {
|
|||
.await
|
||||
.expect("Failed to create worktree_a");
|
||||
|
||||
let (_workspace1, cx) = cx.add_window_view(|window, cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project1.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let (_multi_workspace1, cx) = cx.add_window_view(|window, cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project1.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
|
||||
let workspace1_handle = cx.window_handle().downcast::<Workspace>().unwrap();
|
||||
let workspace1_handle = cx.window_handle().downcast::<MultiWorkspace>().unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
|
|
@ -4950,14 +4966,17 @@ pub mod test {
|
|||
.await
|
||||
.expect("Failed to create worktree_b");
|
||||
|
||||
let (_workspace2, cx) = cx.add_window_view(|window, cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project2.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let (_multi_workspace2, cx) = cx.add_window_view(|window, cx| {
|
||||
let workspace = cx.new(|cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
project2.clone(),
|
||||
app_state.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
MultiWorkspace::new(workspace, cx)
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
|
|
|||
43
crates/sidebar/Cargo.toml
Normal file
43
crates/sidebar/Cargo.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[package]
|
||||
name = "sidebar"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/sidebar.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
acp_thread.workspace = true
|
||||
agent_ui.workspace = true
|
||||
db.workspace = true
|
||||
fs.workspace = true
|
||||
fuzzy.workspace = true
|
||||
serde_json.workspace = true
|
||||
gpui.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
recent_projects.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
ui_input.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
recent_projects = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
1
crates/sidebar/LICENSE-GPL
Symbolic link
1
crates/sidebar/LICENSE-GPL
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../LICENSE-GPL
|
||||
1283
crates/sidebar/src/sidebar.rs
Normal file
1283
crates/sidebar/src/sidebar.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,7 @@ use menu::SelectPrevious;
|
|||
use project::{Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{ActivatePreviousItem, AppState, Workspace, item::test::TestItem};
|
||||
use workspace::{ActivatePreviousItem, AppState, MultiWorkspace, Workspace, item::test::TestItem};
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
|
|
@ -33,8 +33,9 @@ async fn test_open_with_prev_tab_selected_and_cycle_on_toggle_action(
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tab_1 = open_buffer("1.txt", &workspace, cx).await;
|
||||
let tab_2 = open_buffer("2.txt", &workspace, cx).await;
|
||||
|
|
@ -89,8 +90,9 @@ async fn test_open_with_last_tab_selected(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tab_1 = open_buffer("1.txt", &workspace, cx).await;
|
||||
let tab_2 = open_buffer("2.txt", &workspace, cx).await;
|
||||
|
|
@ -123,8 +125,9 @@ async fn test_open_item_on_modifiers_release(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tab_1 = open_buffer("1.txt", &workspace, cx).await;
|
||||
let tab_2 = open_buffer("2.txt", &workspace, cx).await;
|
||||
|
|
@ -151,8 +154,9 @@ async fn test_open_on_empty_pane(cx: &mut gpui::TestAppContext) {
|
|||
app_state.fs.as_fake().insert_tree("/root", json!({})).await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
cx.simulate_modifiers_change(Modifiers::control());
|
||||
let tab_switcher = open_tab_switcher(false, &workspace, cx);
|
||||
|
|
@ -174,8 +178,9 @@ async fn test_open_with_single_item(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tab = open_buffer("1.txt", &workspace, cx).await;
|
||||
|
||||
|
|
@ -204,8 +209,9 @@ async fn test_close_selected_item(cx: &mut gpui::TestAppContext) {
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tab_1 = open_buffer("1.txt", &workspace, cx).await;
|
||||
let tab_3 = open_buffer("3.txt", &workspace, cx).await;
|
||||
|
|
@ -369,8 +375,9 @@ async fn test_open_in_active_pane_deduplicates_files_by_path(cx: &mut gpui::Test
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_buffer("1.txt", &workspace, cx).await;
|
||||
open_buffer("2.txt", &workspace, cx).await;
|
||||
|
|
@ -406,8 +413,9 @@ async fn test_open_in_active_pane_clones_files_to_current_pane(cx: &mut gpui::Te
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_buffer("1.txt", &workspace, cx).await;
|
||||
|
||||
|
|
@ -453,8 +461,9 @@ async fn test_open_in_active_pane_clones_files_to_current_pane(cx: &mut gpui::Te
|
|||
async fn test_open_in_active_pane_moves_terminals_to_current_pane(cx: &mut gpui::TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let test_item = cx.new(|cx| TestItem::new(cx).with_label("terminal"));
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
|
|
@ -506,8 +515,9 @@ async fn test_open_in_active_pane_closes_file_in_all_panes(cx: &mut gpui::TestAp
|
|||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
open_buffer("1.txt", &workspace, cx).await;
|
||||
|
||||
|
|
|
|||
|
|
@ -754,7 +754,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use task::TaskTemplates;
|
||||
use util::path;
|
||||
use workspace::{CloseInactiveTabsAndPanes, OpenOptions, OpenVisible};
|
||||
use workspace::{CloseInactiveTabsAndPanes, MultiWorkspace, OpenOptions, OpenVisible};
|
||||
|
||||
use crate::{modal::Spawn, tests::init_test};
|
||||
|
||||
|
|
@ -787,8 +787,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tasks_picker = open_spawn_tasks(&workspace, cx);
|
||||
assert_eq!(
|
||||
|
|
@ -960,8 +961,9 @@ mod tests {
|
|||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let tasks_picker = open_spawn_tasks(&workspace, cx);
|
||||
assert_eq!(
|
||||
|
|
@ -1115,8 +1117,9 @@ mod tests {
|
|||
))),
|
||||
));
|
||||
});
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let _ts_file_1 = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ mod tests {
|
|||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
use ui::VisualContext;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{AppState, Workspace};
|
||||
use workspace::{AppState, MultiWorkspace};
|
||||
|
||||
use crate::task_contexts;
|
||||
|
||||
|
|
@ -474,8 +474,9 @@ mod tests {
|
|||
let worktree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let buffer1 = workspace
|
||||
.update(cx, |this, cx| {
|
||||
|
|
|
|||
|
|
@ -1847,6 +1847,7 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
use project::FakeFs;
|
||||
use settings::SettingsStore;
|
||||
use workspace::MultiWorkspace;
|
||||
|
||||
#[test]
|
||||
fn test_prepare_empty_task() {
|
||||
|
|
@ -1878,13 +1879,14 @@ mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
|
||||
let (window_handle, terminal_panel) = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let window_handle = window.window_handle();
|
||||
let terminal_panel = cx.new(|cx| TerminalPanel::new(workspace, window, cx));
|
||||
(window_handle, terminal_panel)
|
||||
let terminal_panel = window_handle
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.workspace().update(cx, |workspace, cx| {
|
||||
cx.new(|cx| TerminalPanel::new(workspace, window, cx))
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1963,13 +1965,14 @@ mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
|
||||
let (window_handle, terminal_panel) = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let window_handle = window.window_handle();
|
||||
let terminal_panel = cx.new(|cx| TerminalPanel::new(workspace, window, cx));
|
||||
(window_handle, terminal_panel)
|
||||
let terminal_panel = window_handle
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.workspace().update(cx, |workspace, cx| {
|
||||
cx.new(|cx| TerminalPanel::new(workspace, window, cx))
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -2006,13 +2009,14 @@ mod tests {
|
|||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let window_handle =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
|
||||
let (window_handle, terminal_panel) = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let window_handle = window.window_handle();
|
||||
let terminal_panel = cx.new(|cx| TerminalPanel::new(workspace, window, cx));
|
||||
(window_handle, terminal_panel)
|
||||
let terminal_panel = window_handle
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.workspace().update(cx, |workspace, cx| {
|
||||
cx.new(|cx| TerminalPanel::new(workspace, window, cx))
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -523,7 +523,7 @@ mod tests {
|
|||
terminal_settings::{AlternateScroll, CursorShape},
|
||||
};
|
||||
use util::path;
|
||||
use workspace::AppState;
|
||||
use workspace::{AppState, MultiWorkspace};
|
||||
|
||||
async fn init_test(
|
||||
app_cx: &mut TestAppContext,
|
||||
|
|
@ -552,8 +552,9 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let (workspace, _cx) =
|
||||
app_cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let (multi_workspace, cx) = app_cx
|
||||
.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
|
||||
|
||||
let terminal = app_cx.new(|cx| {
|
||||
TerminalBuilder::new_display_only(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue