mod dev_container_suggest;pub mod disconnected_overlay;mod remote_connections;mod remote_servers;pub mod sidebar_recent_projects;mod ssh_config; use std::{ path::{Path, PathBuf}, sync::Arc,}; use chrono::{DateTime, Utc}; 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_nucleo::{StringMatch, StringMatchCandidate, match_strings};use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, TaskExt, WeakEntity, Window, actions, px,}; use picker::{ Picker, PickerDelegate, ScrollBehavior, 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 workspace::ProjectGroupKey; use dev_container::{DevContainerContext, find_devcontainer_configs};use ui::{ ButtonLike, ContextMenu, Divider, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, ListSubHeader, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, prelude::*,};use util::{ResultExt, paths::PathExt};use workspace::{ HistoryManager, ModalView, MultiWorkspace, OpenMode, OpenOptions, OpenVisible, PathList, RecentWorkspace, SerializedWorkspaceLocation, Workspace, WorkspaceDb, WorkspaceId, notifications::DetachAndPromptErr, with_active_or_new_workspace,};use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote}; actions!( recent_projects, [ToggleActionsMenu, RemoveSelected, AddToWorkspace,]); #[derive(Clone, Debug)]pub struct RecentProjectEntry { pub name: SharedString, pub full_path: SharedString, pub paths: Vec<PathBuf>, pub workspace_id: WorkspaceId, pub timestamp: DateTime<Utc>,} #[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> }, ProjectGroup(StringMatch), RecentProject(StringMatch),} fn is_selectable_entry(entry: &ProjectPickerEntry) -> bool { matches!( entry, ProjectPickerEntry::OpenFolder { .. } | ProjectPickerEntry::ProjectGroup(_) | ProjectPickerEntry::RecentProject(_) )} #[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>, db: &WorkspaceDb,) -> Vec<RecentProjectEntry> { let workspaces = db .recent_project_workspaces(fs.as_ref()) .await .unwrap_or_default(); let filtered: Vec<_> = workspaces .into_iter() .filter(|workspace| Some(workspace.workspace_id) != current_workspace_id) .filter(|workspace| matches!(workspace.location, SerializedWorkspaceLocation::Local)) .collect(); let mut all_paths: Vec<PathBuf> = filtered .iter() .flat_map(|workspace| workspace.identity_paths.paths().iter().cloned()) .collect(); all_paths.sort(); all_paths.dedup(); let path_details = util::disambiguate::compute_disambiguation_details(&all_paths, |path, detail| { project::path_suffix(path, detail) }); let path_detail_map: std::collections::HashMap<PathBuf, usize> = all_paths.into_iter().zip(path_details).collect(); let entries: Vec<RecentProjectEntry> = filtered .into_iter() .map(|workspace| { let paths: Vec<PathBuf> = workspace.paths.paths().to_vec(); let ordered_paths: Vec<&PathBuf> = workspace.identity_paths.ordered_paths().collect(); let name = ordered_paths .iter() .map(|p| { let detail = path_detail_map.get(*p).copied().unwrap_or(0); project::path_suffix(p, detail) }) .filter(|s| !s.is_empty()) .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: workspace.workspace_id, timestamp: workspace.timestamp, } }) .collect(); match limit { Some(n) => entries.into_iter().take(n).collect(), None => entries, }} pub async fn delete_recent_project(workspace_id: WorkspaceId, db: &WorkspaceDb) { let _ = 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 = if let Some(repo) = project.active_repository(cx) { let repo = repo.read(cx); let repo_path = &repo.work_directory_abs_path; project.visible_worktrees(cx).find_map(|worktree| { let worktree_path = worktree.read(cx).abs_path(); (worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref())) .then(|| worktree.read(cx).id()) }) } else { project .visible_worktrees(cx) .next() .map(|wt| wt.read(cx).id()) }; let mut all_paths: Vec<PathBuf> = visible_worktrees .iter() .map(|wt| wt.read(cx).abs_path().to_path_buf()) .collect(); all_paths.sort(); all_paths.dedup(); let path_details = util::disambiguate::compute_disambiguation_details(&all_paths, |path, detail| { project::path_suffix(path, detail) }); let path_detail_map: std::collections::HashMap<PathBuf, usize> = all_paths.into_iter().zip(path_details).collect(); 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 path = worktree_ref.abs_path().to_path_buf(); let detail = path_detail_map.get(&path).copied().unwrap_or(0); let name = SharedString::from(project::path_suffix(&path, detail)); 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_key(|entry| entry.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(); repositories .iter() .filter(|repo| { let repo_path = &repo.read(cx).work_directory_abs_path; *repo_path == worktree_abs_path || worktree_abs_path.starts_with(repo_path.as_ref()) }) .max_by_key(|repo| repo.read(cx).work_directory_abs_path.as_os_str().len()) .and_then(|repo| { repo.read(cx) .branch .as_ref() .map(|branch| SharedString::from(branch.name().to_string())) })} 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 requesting_window = match create_new_window { false => window_handle, true => None, }; let open_options = workspace::OpenOptions { requesting_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 { requesting_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; match cx .active_window() .and_then(|w| w.downcast::<MultiWorkspace>()) { Some(multi_workspace) => { cx.defer(move |cx| { multi_workspace .update(cx, |multi_workspace, window, cx| { let window_project_groups: Vec<ProjectGroupKey> = multi_workspace.project_group_keys(); let workspace = multi_workspace.workspace().clone(); workspace.update(cx, |workspace, 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_project_groups, window, focus_handle, cx, ); return; }; recent_projects.update(cx, |recent_projects, cx| { recent_projects .picker .update(cx, |picker, cx| picker.cycle_selection(window, cx)) }); }); }) .log_err(); }); } None => { 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, Vec::new(), 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 containerLines 1–500 of 2814. Load more
mod dev_container_suggest; pub mod disconnected_overlay; mod remote_connections; mod remote_servers; pub mod sidebar_recent_projects; mod ssh_config; use std::{ path::{Path, PathBuf}, sync::Arc, }; use chrono::{DateTime, Utc}; 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_nucleo::{StringMatch, StringMatchCandidate, match_strings}; use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, TaskExt, WeakEntity, Window, actions, px, }; use picker::{ Picker, PickerDelegate, ScrollBehavior, 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 workspace::ProjectGroupKey; use dev_container::{DevContainerContext, find_devcontainer_configs}; use ui::{ ButtonLike, ContextMenu, Divider, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, ListSubHeader, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, prelude::*, }; use util::{ResultExt, paths::PathExt}; use workspace::{ HistoryManager, ModalView, MultiWorkspace, OpenMode, OpenOptions, OpenVisible, PathList, RecentWorkspace, SerializedWorkspaceLocation, Workspace, WorkspaceDb, WorkspaceId, notifications::DetachAndPromptErr, with_active_or_new_workspace, }; use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote}; actions!( recent_projects, [ToggleActionsMenu, RemoveSelected, AddToWorkspace,] ); #[derive(Clone, Debug)] pub struct RecentProjectEntry { pub name: SharedString, pub full_path: SharedString, pub paths: Vec<PathBuf>, pub workspace_id: WorkspaceId, pub timestamp: DateTime<Utc>, } #[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> }, ProjectGroup(StringMatch), RecentProject(StringMatch), } fn is_selectable_entry(entry: &ProjectPickerEntry) -> bool { matches!( entry, ProjectPickerEntry::OpenFolder { .. } | ProjectPickerEntry::ProjectGroup(_) | ProjectPickerEntry::RecentProject(_) ) } #[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>, db: &WorkspaceDb, ) -> Vec<RecentProjectEntry> { let workspaces = db .recent_project_workspaces(fs.as_ref()) .await .unwrap_or_default(); let filtered: Vec<_> = workspaces .into_iter() .filter(|workspace| Some(workspace.workspace_id) != current_workspace_id) .filter(|workspace| matches!(workspace.location, SerializedWorkspaceLocation::Local)) .collect(); let mut all_paths: Vec<PathBuf> = filtered .iter() .flat_map(|workspace| workspace.identity_paths.paths().iter().cloned()) .collect(); all_paths.sort(); all_paths.dedup(); let path_details = util::disambiguate::compute_disambiguation_details(&all_paths, |path, detail| { project::path_suffix(path, detail) }); let path_detail_map: std::collections::HashMap<PathBuf, usize> = all_paths.into_iter().zip(path_details).collect(); let entries: Vec<RecentProjectEntry> = filtered .into_iter() .map(|workspace| { let paths: Vec<PathBuf> = workspace.paths.paths().to_vec(); let ordered_paths: Vec<&PathBuf> = workspace.identity_paths.ordered_paths().collect(); let name = ordered_paths .iter() .map(|p| { let detail = path_detail_map.get(*p).copied().unwrap_or(0); project::path_suffix(p, detail) }) .filter(|s| !s.is_empty()) .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: workspace.workspace_id, timestamp: workspace.timestamp, } }) .collect(); match limit { Some(n) => entries.into_iter().take(n).collect(), None => entries, } } pub async fn delete_recent_project(workspace_id: WorkspaceId, db: &WorkspaceDb) { let _ = 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 = if let Some(repo) = project.active_repository(cx) { let repo = repo.read(cx); let repo_path = &repo.work_directory_abs_path; project.visible_worktrees(cx).find_map(|worktree| { let worktree_path = worktree.read(cx).abs_path(); (worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref())) .then(|| worktree.read(cx).id()) }) } else { project .visible_worktrees(cx) .next() .map(|wt| wt.read(cx).id()) }; let mut all_paths: Vec<PathBuf> = visible_worktrees .iter() .map(|wt| wt.read(cx).abs_path().to_path_buf()) .collect(); all_paths.sort(); all_paths.dedup(); let path_details = util::disambiguate::compute_disambiguation_details(&all_paths, |path, detail| { project::path_suffix(path, detail) }); let path_detail_map: std::collections::HashMap<PathBuf, usize> = all_paths.into_iter().zip(path_details).collect(); 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 path = worktree_ref.abs_path().to_path_buf(); let detail = path_detail_map.get(&path).copied().unwrap_or(0); let name = SharedString::from(project::path_suffix(&path, detail)); 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_key(|entry| entry.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(); repositories .iter() .filter(|repo| { let repo_path = &repo.read(cx).work_directory_abs_path; *repo_path == worktree_abs_path || worktree_abs_path.starts_with(repo_path.as_ref()) }) .max_by_key(|repo| repo.read(cx).work_directory_abs_path.as_os_str().len()) .and_then(|repo| { repo.read(cx) .branch .as_ref() .map(|branch| SharedString::from(branch.name().to_string())) }) } 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 requesting_window = match create_new_window { false => window_handle, true => None, }; let open_options = workspace::OpenOptions { requesting_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 { requesting_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; match cx .active_window() .and_then(|w| w.downcast::<MultiWorkspace>()) { Some(multi_workspace) => { cx.defer(move |cx| { multi_workspace .update(cx, |multi_workspace, window, cx| { let window_project_groups: Vec<ProjectGroupKey> = multi_workspace.project_group_keys(); let workspace = multi_workspace.workspace().clone(); workspace.update(cx, |workspace, 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_project_groups, window, focus_handle, cx, ); return; }; recent_projects.update(cx, |recent_projects, cx| { recent_projects .picker .update(cx, |picker, cx| picker.cycle_selection(window, cx)) }); }); }) .log_err(); }); } None => { 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, Vec::new(), 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