mod dev_container_suggest;pub mod disconnected_overlay;mod remote_connections;mod remote_servers;mod ssh_config; use std::{ path::{Path, PathBuf}, sync::Arc,}; use fs::Fs; #[cfg(target_os = "windows")]mod wsl_picker; use remote::RemoteConnectionOptions;pub use remote_connection::{RemoteConnectionModal, connect};pub use remote_connections::{navigate_to_positions, open_remote_project}; use disconnected_overlay::DisconnectedOverlay;use fuzzy::{StringMatch, StringMatchCandidate};use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, Window, actions, px,}; use picker::{ Picker, PickerDelegate, highlighted_match_with_paths::{HighlightedMatch, HighlightedMatchWithPaths},};use project::{Worktree, git_store::Repository};pub use remote_connections::RemoteSettings;pub use remote_servers::RemoteServerProjects;use settings::{Settings, WorktreeId};use ui_input::ErasedEditor; use dev_container::{DevContainerContext, find_devcontainer_configs};use ui::{ ContextMenu, Divider, KeyBinding, ListItem, ListItemSpacing, ListSubHeader, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, prelude::*,};use util::{ResultExt, paths::PathExt};use workspace::{ HistoryManager, ModalView, MultiWorkspace, OpenOptions, OpenVisible, PathList, SerializedWorkspaceLocation, WORKSPACE_DB, Workspace, WorkspaceId, notifications::DetachAndPromptErr, with_active_or_new_workspace,};use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote}; actions!(recent_projects, [ToggleActionsMenu]); #[derive(Clone, Debug)]pub struct RecentProjectEntry { pub name: SharedString, pub full_path: SharedString, pub paths: Vec<PathBuf>, pub workspace_id: WorkspaceId,} #[derive(Clone, Debug)]struct OpenFolderEntry { worktree_id: WorktreeId, name: SharedString, path: PathBuf, branch: Option<SharedString>, is_active: bool,} #[derive(Clone, Debug)]enum ProjectPickerEntry { Header(SharedString), OpenFolder { index: usize, positions: Vec<usize> }, RecentProject(StringMatch),} #[derive(Debug, Clone, Copy, PartialEq, Eq)]enum ProjectPickerStyle { Modal, Popover,} 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(fs.as_ref()) .await .unwrap_or_default(); let entries: Vec<RecentProjectEntry> = workspaces .into_iter() .filter(|(id, _, _)| Some(*id) != current_workspace_id) .filter(|(_, location, _)| matches!(location, SerializedWorkspaceLocation::Local)) .map(|(workspace_id, _, path_list)| { let paths: Vec<PathBuf> = path_list.paths().to_vec(); let ordered_paths: Vec<&PathBuf> = path_list.ordered_paths().collect(); let name = if ordered_paths.len() == 1 { ordered_paths[0] .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| ordered_paths[0].to_string_lossy().to_string()) } else { ordered_paths .iter() .filter_map(|p| p.file_name()) .map(|n| n.to_string_lossy().to_string()) .collect::<Vec<_>>() .join(", ") }; let full_path = ordered_paths .iter() .map(|p| p.to_string_lossy().to_string()) .collect::<Vec<_>>() .join("\n"); RecentProjectEntry { name: SharedString::from(name), full_path: SharedString::from(full_path), paths, workspace_id, } }) .collect(); match limit { Some(n) => entries.into_iter().take(n).collect(), None => entries, }} pub async fn delete_recent_project(workspace_id: WorkspaceId) { let _ = WORKSPACE_DB.delete_workspace_by_id(workspace_id).await;} fn get_open_folders(workspace: &Workspace, cx: &App) -> Vec<OpenFolderEntry> { let project = workspace.project().read(cx); let visible_worktrees: Vec<_> = project.visible_worktrees(cx).collect(); if visible_worktrees.len() <= 1 { return Vec::new(); } let active_worktree_id = workspace.active_worktree_override().or_else(|| { if let Some(repo) = project.active_repository(cx) { let repo = repo.read(cx); let repo_path = &repo.work_directory_abs_path; for worktree in project.visible_worktrees(cx) { let worktree_path = worktree.read(cx).abs_path(); if worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref()) { return Some(worktree.read(cx).id()); } } } project .visible_worktrees(cx) .next() .map(|wt| wt.read(cx).id()) }); let git_store = project.git_store().read(cx); let repositories: Vec<_> = git_store.repositories().values().cloned().collect(); let mut entries: Vec<OpenFolderEntry> = visible_worktrees .into_iter() .map(|worktree| { let worktree_ref = worktree.read(cx); let worktree_id = worktree_ref.id(); let name = SharedString::from(worktree_ref.root_name().as_unix_str().to_string()); let path = worktree_ref.abs_path().to_path_buf(); let branch = get_branch_for_worktree(worktree_ref, &repositories, cx); let is_active = active_worktree_id == Some(worktree_id); OpenFolderEntry { worktree_id, name, path, branch, is_active, } }) .collect(); entries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); entries} fn get_branch_for_worktree( worktree: &Worktree, repositories: &[Entity<Repository>], cx: &App,) -> Option<SharedString> { let worktree_abs_path = worktree.abs_path(); for repo in repositories { let repo = repo.read(cx); if repo.work_directory_abs_path == worktree_abs_path || worktree_abs_path.starts_with(&*repo.work_directory_abs_path) { if let Some(branch) = &repo.branch { return Some(SharedString::from(branch.name().to_string())); } } } None} pub fn init(cx: &mut App) { #[cfg(target_os = "windows")] cx.on_action(|open_wsl: &zed_actions::wsl_actions::OpenFolderInWsl, cx| { let create_new_window = open_wsl.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { use gpui::PathPromptOptions; use project::DirectoryLister; let paths = workspace.prompt_for_open_path( PathPromptOptions { files: true, directories: true, multiple: false, prompt: None, }, DirectoryLister::Local( workspace.project().clone(), workspace.app_state().fs.clone(), ), window, cx, ); let app_state = workspace.app_state().clone(); let window_handle = window.window_handle().downcast::<MultiWorkspace>(); cx.spawn_in(window, async move |workspace, cx| { use util::paths::SanitizedPath; let Some(paths) = paths.await.log_err().flatten() else { return; }; let wsl_path = paths .iter() .find_map(util::paths::WslPath::from_path); if let Some(util::paths::WslPath { distro, path }) = wsl_path { use remote::WslConnectionOptions; let connection_options = RemoteConnectionOptions::Wsl(WslConnectionOptions { distro_name: distro.to_string(), user: None, }); let replace_window = match create_new_window { false => window_handle, true => None, }; let open_options = workspace::OpenOptions { replace_window, ..Default::default() }; open_remote_project(connection_options, vec![path.into()], app_state, open_options, cx).await.log_err(); return; } let paths = paths .into_iter() .filter_map(|path| SanitizedPath::new(&path).local_to_wsl()) .collect::<Vec<_>>(); if paths.is_empty() { let message = indoc::indoc! { r#" Invalid path specified when trying to open a folder inside WSL. Please note that Zed currently does not support opening network share folders inside wsl. "#}; let _ = cx.prompt(gpui::PromptLevel::Critical, "Invalid path", Some(&message), &["Ok"]).await; return; } workspace.update_in(cx, |workspace, window, cx| { workspace.toggle_modal(window, cx, |window, cx| { crate::wsl_picker::WslOpenModal::new(paths, create_new_window, window, cx) }); }).log_err(); }) .detach(); }); }); #[cfg(target_os = "windows")] cx.on_action(|open_wsl: &zed_actions::wsl_actions::OpenWsl, cx| { let create_new_window = open_wsl.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { let handle = cx.entity().downgrade(); let fs = workspace.project().read(cx).fs().clone(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::wsl(create_new_window, fs, window, handle, cx) }); }); }); #[cfg(target_os = "windows")] cx.on_action(|open_wsl: &remote::OpenWslPath, cx| { let open_wsl = open_wsl.clone(); with_active_or_new_workspace(cx, move |workspace, window, cx| { 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::<MultiWorkspace>(), ..Default::default() }; let app_state = workspace.app_state().clone(); cx.spawn_in(window, async move |_, cx| { open_remote_project( RemoteConnectionOptions::Wsl(open_wsl.distro.clone()), open_wsl.paths, app_state, open_options, cx, ) .await }) .detach(); }); }); cx.on_action(|open_recent: &OpenRecent, cx| { let create_new_window = open_recent.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { let Some(recent_projects) = workspace.active_modal::<RecentProjects>(cx) else { let focus_handle = workspace.focus_handle(cx); RecentProjects::open(workspace, create_new_window, window, focus_handle, cx); return; }; recent_projects.update(cx, |recent_projects, cx| { recent_projects .picker .update(cx, |picker, cx| picker.cycle_selection(window, cx)) }); }); }); cx.on_action(|open_remote: &OpenRemote, cx| { let from_existing_connection = open_remote.from_existing_connection; let create_new_window = open_remote.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { if from_existing_connection { cx.propagate(); return; } let handle = cx.entity().downgrade(); let fs = workspace.project().read(cx).fs().clone(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::new(create_new_window, fs, window, handle, cx) }) }); }); cx.observe_new(DisconnectedOverlay::register).detach(); cx.on_action(|_: &OpenDevContainer, cx| { with_active_or_new_workspace(cx, move |workspace, window, cx| { 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", None, &["Ok"], ) .await .ok(); }) .detach(); return; } let fs = workspace.project().read(cx).fs().clone(); let configs = find_devcontainer_configs(workspace, cx); let app_state = workspace.app_state().clone(); let dev_container_context = DevContainerContext::from_workspace(workspace, cx); let handle = cx.entity().downgrade(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::new_dev_container( fs, configs, app_state, dev_container_context, window, handle, cx, ) }); }); }); // Subscribe to worktree additions to suggest opening the project in a dev container cx.observe_new( |workspace: &mut Workspace, window: Option<&mut Window>, cx: &mut Context<Workspace>| { let Some(window) = window else { return; }; cx.subscribe_in( workspace.project(), window, move |_, project, event, window, cx| { if let project::Event::WorktreeUpdatedEntries(worktree_id, updated_entries) = event { dev_container_suggest::suggest_on_worktree_updated( *worktree_id, updated_entries, project, window, cx, ); } }, ) .detach(); }, ) .detach();} #[cfg(target_os = "windows")]pub fn add_wsl_distro( fs: Arc<dyn project::Fs>, connection_options: &remote::WslConnectionOptions, cx: &App,) { use gpui::ReadGlobal; use settings::SettingsStore; let distro_name = connection_options.distro_name.clone(); let user = connection_options.user.clone(); SettingsStore::global(cx).update_settings_file(fs, move |setting, _| { let connections = setting .remote .wsl_connections .get_or_insert(Default::default()); if !connections .iter() .any(|conn| conn.distro_name == distro_name && conn.user == user) { use std::collections::BTreeSet; connections.push(settings::WslConnection { distro_name, user, projects: BTreeSet::new(), }) } });} pub struct RecentProjects { pub picker: Entity<Picker<RecentProjectsDelegate>>, rem_width: f32, _subscription: Subscription,} impl ModalView for RecentProjects { fn on_before_dismiss( &mut self, window: &mut Window, cx: &mut Context<Self>, ) -> workspace::DismissDecision { let submenu_focused = self.picker.update(cx, |picker, cx| { picker.delegate.actions_menu_handle.is_focused(window, cx) }); workspace::DismissDecision::Dismiss(!submenu_focused) }} impl RecentProjects { fn new( delegate: RecentProjectsDelegate, fs: Option<Arc<dyn Fs>>, rem_width: f32, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let picker = cx.new(|cx| { Picker::list(delegate, window, cx) .list_measure_all() .show_scrollbar(true) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle;Lines 1–500 of 1863. Load more
mod dev_container_suggest; pub mod disconnected_overlay; mod remote_connections; mod remote_servers; mod ssh_config; use std::{ path::{Path, PathBuf}, sync::Arc, }; use fs::Fs; #[cfg(target_os = "windows")] mod wsl_picker; use remote::RemoteConnectionOptions; pub use remote_connection::{RemoteConnectionModal, connect}; pub use remote_connections::{navigate_to_positions, open_remote_project}; use disconnected_overlay::DisconnectedOverlay; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, Window, actions, px, }; use picker::{ Picker, PickerDelegate, highlighted_match_with_paths::{HighlightedMatch, HighlightedMatchWithPaths}, }; use project::{Worktree, git_store::Repository}; pub use remote_connections::RemoteSettings; pub use remote_servers::RemoteServerProjects; use settings::{Settings, WorktreeId}; use ui_input::ErasedEditor; use dev_container::{DevContainerContext, find_devcontainer_configs}; use ui::{ ContextMenu, Divider, KeyBinding, ListItem, ListItemSpacing, ListSubHeader, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, prelude::*, }; use util::{ResultExt, paths::PathExt}; use workspace::{ HistoryManager, ModalView, MultiWorkspace, OpenOptions, OpenVisible, PathList, SerializedWorkspaceLocation, WORKSPACE_DB, Workspace, WorkspaceId, notifications::DetachAndPromptErr, with_active_or_new_workspace, }; use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote}; actions!(recent_projects, [ToggleActionsMenu]); #[derive(Clone, Debug)] pub struct RecentProjectEntry { pub name: SharedString, pub full_path: SharedString, pub paths: Vec<PathBuf>, pub workspace_id: WorkspaceId, } #[derive(Clone, Debug)] struct OpenFolderEntry { worktree_id: WorktreeId, name: SharedString, path: PathBuf, branch: Option<SharedString>, is_active: bool, } #[derive(Clone, Debug)] enum ProjectPickerEntry { Header(SharedString), OpenFolder { index: usize, positions: Vec<usize> }, RecentProject(StringMatch), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ProjectPickerStyle { Modal, Popover, } 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(fs.as_ref()) .await .unwrap_or_default(); let entries: Vec<RecentProjectEntry> = workspaces .into_iter() .filter(|(id, _, _)| Some(*id) != current_workspace_id) .filter(|(_, location, _)| matches!(location, SerializedWorkspaceLocation::Local)) .map(|(workspace_id, _, path_list)| { let paths: Vec<PathBuf> = path_list.paths().to_vec(); let ordered_paths: Vec<&PathBuf> = path_list.ordered_paths().collect(); let name = if ordered_paths.len() == 1 { ordered_paths[0] .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| ordered_paths[0].to_string_lossy().to_string()) } else { ordered_paths .iter() .filter_map(|p| p.file_name()) .map(|n| n.to_string_lossy().to_string()) .collect::<Vec<_>>() .join(", ") }; let full_path = ordered_paths .iter() .map(|p| p.to_string_lossy().to_string()) .collect::<Vec<_>>() .join("\n"); RecentProjectEntry { name: SharedString::from(name), full_path: SharedString::from(full_path), paths, workspace_id, } }) .collect(); match limit { Some(n) => entries.into_iter().take(n).collect(), None => entries, } } pub async fn delete_recent_project(workspace_id: WorkspaceId) { let _ = WORKSPACE_DB.delete_workspace_by_id(workspace_id).await; } fn get_open_folders(workspace: &Workspace, cx: &App) -> Vec<OpenFolderEntry> { let project = workspace.project().read(cx); let visible_worktrees: Vec<_> = project.visible_worktrees(cx).collect(); if visible_worktrees.len() <= 1 { return Vec::new(); } let active_worktree_id = workspace.active_worktree_override().or_else(|| { if let Some(repo) = project.active_repository(cx) { let repo = repo.read(cx); let repo_path = &repo.work_directory_abs_path; for worktree in project.visible_worktrees(cx) { let worktree_path = worktree.read(cx).abs_path(); if worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref()) { return Some(worktree.read(cx).id()); } } } project .visible_worktrees(cx) .next() .map(|wt| wt.read(cx).id()) }); let git_store = project.git_store().read(cx); let repositories: Vec<_> = git_store.repositories().values().cloned().collect(); let mut entries: Vec<OpenFolderEntry> = visible_worktrees .into_iter() .map(|worktree| { let worktree_ref = worktree.read(cx); let worktree_id = worktree_ref.id(); let name = SharedString::from(worktree_ref.root_name().as_unix_str().to_string()); let path = worktree_ref.abs_path().to_path_buf(); let branch = get_branch_for_worktree(worktree_ref, &repositories, cx); let is_active = active_worktree_id == Some(worktree_id); OpenFolderEntry { worktree_id, name, path, branch, is_active, } }) .collect(); entries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); entries } fn get_branch_for_worktree( worktree: &Worktree, repositories: &[Entity<Repository>], cx: &App, ) -> Option<SharedString> { let worktree_abs_path = worktree.abs_path(); for repo in repositories { let repo = repo.read(cx); if repo.work_directory_abs_path == worktree_abs_path || worktree_abs_path.starts_with(&*repo.work_directory_abs_path) { if let Some(branch) = &repo.branch { return Some(SharedString::from(branch.name().to_string())); } } } None } pub fn init(cx: &mut App) { #[cfg(target_os = "windows")] cx.on_action(|open_wsl: &zed_actions::wsl_actions::OpenFolderInWsl, cx| { let create_new_window = open_wsl.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { use gpui::PathPromptOptions; use project::DirectoryLister; let paths = workspace.prompt_for_open_path( PathPromptOptions { files: true, directories: true, multiple: false, prompt: None, }, DirectoryLister::Local( workspace.project().clone(), workspace.app_state().fs.clone(), ), window, cx, ); let app_state = workspace.app_state().clone(); let window_handle = window.window_handle().downcast::<MultiWorkspace>(); cx.spawn_in(window, async move |workspace, cx| { use util::paths::SanitizedPath; let Some(paths) = paths.await.log_err().flatten() else { return; }; let wsl_path = paths .iter() .find_map(util::paths::WslPath::from_path); if let Some(util::paths::WslPath { distro, path }) = wsl_path { use remote::WslConnectionOptions; let connection_options = RemoteConnectionOptions::Wsl(WslConnectionOptions { distro_name: distro.to_string(), user: None, }); let replace_window = match create_new_window { false => window_handle, true => None, }; let open_options = workspace::OpenOptions { replace_window, ..Default::default() }; open_remote_project(connection_options, vec![path.into()], app_state, open_options, cx).await.log_err(); return; } let paths = paths .into_iter() .filter_map(|path| SanitizedPath::new(&path).local_to_wsl()) .collect::<Vec<_>>(); if paths.is_empty() { let message = indoc::indoc! { r#" Invalid path specified when trying to open a folder inside WSL. Please note that Zed currently does not support opening network share folders inside wsl. "#}; let _ = cx.prompt(gpui::PromptLevel::Critical, "Invalid path", Some(&message), &["Ok"]).await; return; } workspace.update_in(cx, |workspace, window, cx| { workspace.toggle_modal(window, cx, |window, cx| { crate::wsl_picker::WslOpenModal::new(paths, create_new_window, window, cx) }); }).log_err(); }) .detach(); }); }); #[cfg(target_os = "windows")] cx.on_action(|open_wsl: &zed_actions::wsl_actions::OpenWsl, cx| { let create_new_window = open_wsl.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { let handle = cx.entity().downgrade(); let fs = workspace.project().read(cx).fs().clone(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::wsl(create_new_window, fs, window, handle, cx) }); }); }); #[cfg(target_os = "windows")] cx.on_action(|open_wsl: &remote::OpenWslPath, cx| { let open_wsl = open_wsl.clone(); with_active_or_new_workspace(cx, move |workspace, window, cx| { 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::<MultiWorkspace>(), ..Default::default() }; let app_state = workspace.app_state().clone(); cx.spawn_in(window, async move |_, cx| { open_remote_project( RemoteConnectionOptions::Wsl(open_wsl.distro.clone()), open_wsl.paths, app_state, open_options, cx, ) .await }) .detach(); }); }); cx.on_action(|open_recent: &OpenRecent, cx| { let create_new_window = open_recent.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { let Some(recent_projects) = workspace.active_modal::<RecentProjects>(cx) else { let focus_handle = workspace.focus_handle(cx); RecentProjects::open(workspace, create_new_window, window, focus_handle, cx); return; }; recent_projects.update(cx, |recent_projects, cx| { recent_projects .picker .update(cx, |picker, cx| picker.cycle_selection(window, cx)) }); }); }); cx.on_action(|open_remote: &OpenRemote, cx| { let from_existing_connection = open_remote.from_existing_connection; let create_new_window = open_remote.create_new_window; with_active_or_new_workspace(cx, move |workspace, window, cx| { if from_existing_connection { cx.propagate(); return; } let handle = cx.entity().downgrade(); let fs = workspace.project().read(cx).fs().clone(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::new(create_new_window, fs, window, handle, cx) }) }); }); cx.observe_new(DisconnectedOverlay::register).detach(); cx.on_action(|_: &OpenDevContainer, cx| { with_active_or_new_workspace(cx, move |workspace, window, cx| { 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", None, &["Ok"], ) .await .ok(); }) .detach(); return; } let fs = workspace.project().read(cx).fs().clone(); let configs = find_devcontainer_configs(workspace, cx); let app_state = workspace.app_state().clone(); let dev_container_context = DevContainerContext::from_workspace(workspace, cx); let handle = cx.entity().downgrade(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::new_dev_container( fs, configs, app_state, dev_container_context, window, handle, cx, ) }); }); }); // Subscribe to worktree additions to suggest opening the project in a dev container cx.observe_new( |workspace: &mut Workspace, window: Option<&mut Window>, cx: &mut Context<Workspace>| { let Some(window) = window else { return; }; cx.subscribe_in( workspace.project(), window, move |_, project, event, window, cx| { if let project::Event::WorktreeUpdatedEntries(worktree_id, updated_entries) = event { dev_container_suggest::suggest_on_worktree_updated( *worktree_id, updated_entries, project, window, cx, ); } }, ) .detach(); }, ) .detach(); } #[cfg(target_os = "windows")] pub fn add_wsl_distro( fs: Arc<dyn project::Fs>, connection_options: &remote::WslConnectionOptions, cx: &App, ) { use gpui::ReadGlobal; use settings::SettingsStore; let distro_name = connection_options.distro_name.clone(); let user = connection_options.user.clone(); SettingsStore::global(cx).update_settings_file(fs, move |setting, _| { let connections = setting .remote .wsl_connections .get_or_insert(Default::default()); if !connections .iter() .any(|conn| conn.distro_name == distro_name && conn.user == user) { use std::collections::BTreeSet; connections.push(settings::WslConnection { distro_name, user, projects: BTreeSet::new(), }) } }); } pub struct RecentProjects { pub picker: Entity<Picker<RecentProjectsDelegate>>, rem_width: f32, _subscription: Subscription, } impl ModalView for RecentProjects { fn on_before_dismiss( &mut self, window: &mut Window, cx: &mut Context<Self>, ) -> workspace::DismissDecision { let submenu_focused = self.picker.update(cx, |picker, cx| { picker.delegate.actions_menu_handle.is_focused(window, cx) }); workspace::DismissDecision::Dismiss(!submenu_focused) } } impl RecentProjects { fn new( delegate: RecentProjectsDelegate, fs: Option<Arc<dyn Fs>>, rem_width: f32, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let picker = cx.new(|cx| { Picker::list(delegate, window, cx) .list_measure_all() .show_scrollbar(true) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle;