use crate::{ dev_container::start_dev_container, remote_connections::{ Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettings, SshConnection, SshConnectionHeader, connect, determine_paths_with_positions, open_remote_project, }, ssh_config::parse_ssh_config_hosts,};use editor::Editor;use file_finder::OpenPathDelegate;use futures::{FutureExt, channel::oneshot, future::Shared, select};use gpui::{ AnyElement, App, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, ScrollHandle, Subscription, Task, WeakEntity, Window, canvas,};use language::Point;use log::info;use paths::{global_ssh_config_file, user_ssh_config_file};use picker::Picker;use project::{Fs, Project};use remote::{ RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions, remote_client::ConnectionIdentifier,};use settings::{ RemoteProject, RemoteSettingsContent, Settings as _, SettingsStore, update_settings_file, watch_config_file,};use smol::stream::StreamExt as _;use std::{ borrow::Cow, collections::BTreeSet, path::PathBuf, rc::Rc, sync::{ Arc, atomic::{self, AtomicUsize}, },};use ui::{ CommonAnimationExt, IconButtonShape, KeyBinding, List, ListItem, ListSeparator, Modal, ModalHeader, Navigable, NavigableEntry, Section, Tooltip, WithScrollbar, prelude::*,};use util::{ ResultExt, paths::{PathStyle, RemotePathBuf}, rel_path::RelPath,};use workspace::{ ModalView, OpenOptions, Toast, Workspace, notifications::{DetachAndPromptErr, NotificationId}, open_remote_project_with_existing_connection,}; pub struct RemoteServerProjects { mode: Mode, focus_handle: FocusHandle, workspace: WeakEntity<Workspace>, retained_connections: Vec<Entity<RemoteClient>>, ssh_config_updates: Task<()>, ssh_config_servers: BTreeSet<SharedString>, create_new_window: bool, _subscription: Subscription,} struct CreateRemoteServer { address_editor: Entity<Editor>, address_error: Option<SharedString>, ssh_prompt: Option<Entity<RemoteConnectionPrompt>>, _creating: Option<Task<Option<()>>>,} impl CreateRemoteServer { fn new(window: &mut Window, cx: &mut App) -> Self { let address_editor = cx.new(|cx| Editor::single_line(window, cx)); address_editor.update(cx, |this, cx| { this.focus_handle(cx).focus(window, cx); }); Self { address_editor, address_error: None, ssh_prompt: None, _creating: None, } }} #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]enum DevContainerCreationProgress { Initial, Creating, Error(String),} #[derive(Clone)]struct CreateRemoteDevContainer { // 3 Navigable Options // - Create from devcontainer.json // - Edit devcontainer.json // - Go back entries: [NavigableEntry; 3], progress: DevContainerCreationProgress,} impl CreateRemoteDevContainer { fn new(window: &mut Window, cx: &mut Context<RemoteServerProjects>) -> Self { let entries = std::array::from_fn(|_| NavigableEntry::focusable(cx)); entries[0].focus_handle.focus(window, cx); Self { entries, progress: DevContainerCreationProgress::Initial, } } fn progress(&mut self, progress: DevContainerCreationProgress) -> Self { self.progress = progress; self.clone() }} #[cfg(target_os = "windows")]struct AddWslDistro { picker: Entity<Picker<crate::wsl_picker::WslPickerDelegate>>, connection_prompt: Option<Entity<RemoteConnectionPrompt>>, _creating: Option<Task<()>>,} #[cfg(target_os = "windows")]impl AddWslDistro { fn new(window: &mut Window, cx: &mut Context<RemoteServerProjects>) -> Self { use crate::wsl_picker::{WslDistroSelected, WslPickerDelegate, WslPickerDismissed}; let delegate = WslPickerDelegate::new(); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)); cx.subscribe_in( &picker, window, |this, _, _: &WslDistroSelected, window, cx| { this.confirm(&menu::Confirm, window, cx); }, ) .detach(); cx.subscribe_in( &picker, window, |this, _, _: &WslPickerDismissed, window, cx| { this.cancel(&menu::Cancel, window, cx); }, ) .detach(); AddWslDistro { picker, connection_prompt: None, _creating: None, } }} enum ProjectPickerData { Ssh { connection_string: SharedString, nickname: Option<SharedString>, }, Wsl { distro_name: SharedString, },} struct ProjectPicker { data: ProjectPickerData, picker: Entity<Picker<OpenPathDelegate>>, _path_task: Shared<Task<Option<()>>>,} struct EditNicknameState { index: SshServerIndex, editor: Entity<Editor>,} impl EditNicknameState { fn new(index: SshServerIndex, window: &mut Window, cx: &mut App) -> Self { let this = Self { index, editor: cx.new(|cx| Editor::single_line(window, cx)), }; let starting_text = RemoteSettings::get_global(cx) .ssh_connections() .nth(index.0) .and_then(|state| state.nickname) .filter(|text| !text.is_empty()); this.editor.update(cx, |this, cx| { this.set_placeholder_text("Add a nickname for this server", window, cx); if let Some(starting_text) = starting_text { this.set_text(starting_text, window, cx); } }); this.editor.focus_handle(cx).focus(window, cx); this }} impl Focusable for ProjectPicker { fn focus_handle(&self, cx: &App) -> FocusHandle { self.picker.focus_handle(cx) }} impl ProjectPicker { fn new( create_new_window: bool, index: ServerIndex, connection: RemoteConnectionOptions, project: Entity<Project>, home_dir: RemotePathBuf, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<RemoteServerProjects>, ) -> Entity<Self> { let (tx, rx) = oneshot::channel(); let lister = project::DirectoryLister::Project(project.clone()); let delegate = file_finder::OpenPathDelegate::new(tx, lister, false, cx); let picker = cx.new(|cx| { let picker = Picker::uniform_list(delegate, window, cx) .width(rems(34.)) .modal(false); picker.set_query(home_dir.to_string(), window, cx); picker }); let data = match &connection { RemoteConnectionOptions::Ssh(connection) => ProjectPickerData::Ssh { connection_string: connection.connection_string().into(), nickname: connection.nickname.clone().map(|nick| nick.into()), }, RemoteConnectionOptions::Wsl(connection) => ProjectPickerData::Wsl { distro_name: connection.distro_name.clone().into(), }, RemoteConnectionOptions::Docker(_) => ProjectPickerData::Ssh { // Not implemented as a project picker at this time connection_string: "".into(), nickname: None, }, }; let _path_task = cx .spawn_in(window, { let workspace = workspace; async move |this, cx| { let Ok(Some(paths)) = rx.await else { workspace .update_in(cx, |workspace, window, cx| { let fs = workspace.project().read(cx).fs().clone(); let weak = cx.entity().downgrade(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::new( create_new_window, fs, window, weak, cx, ) }); }) .log_err()?; return None; }; let app_state = workspace .read_with(cx, |workspace, _| workspace.app_state().clone()) .ok()?; let remote_connection = project .read_with(cx, |project, cx| { project.remote_client()?.read(cx).connection() }) .ok()??; let (paths, paths_with_positions) = determine_paths_with_positions(&remote_connection, paths).await; cx.update(|_, cx| { let fs = app_state.fs.clone(); update_settings_file(fs, cx, { let paths = paths .iter() .map(|path| path.to_string_lossy().into_owned()) .collect(); move |settings, _| match index { ServerIndex::Ssh(index) => { if let Some(server) = settings .remote .ssh_connections .as_mut() .and_then(|connections| connections.get_mut(index.0)) { server.projects.insert(RemoteProject { paths }); }; } ServerIndex::Wsl(index) => { if let Some(server) = settings .remote .wsl_connections .as_mut() .and_then(|connections| connections.get_mut(index.0)) { server.projects.insert(RemoteProject { paths }); }; } } }); }) .log_err(); let options = cx .update(|_, cx| (app_state.build_window_options)(None, cx)) .log_err()?; let window = cx .open_window(options, |window, cx| { cx.new(|cx| { telemetry::event!("SSH Project Created"); Workspace::new(None, project.clone(), app_state.clone(), window, cx) }) }) .log_err()?; let items = open_remote_project_with_existing_connection( connection, project, paths, app_state, window, cx, ) .await .log_err(); if let Some(items) = items { for (item, path) in items.into_iter().zip(paths_with_positions) { let Some(item) = item else { continue; }; let Some(row) = path.row else { continue; }; if let Some(active_editor) = item.downcast::<Editor>() { window .update(cx, |_, window, cx| { active_editor.update(cx, |editor, cx| { let row = row.saturating_sub(1); let col = path.column.unwrap_or(0).saturating_sub(1); editor.go_to_singleton_buffer_point( Point::new(row, col), window, cx, ); }); }) .ok(); } } } this.update(cx, |_, cx| { cx.emit(DismissEvent); }) .ok(); Some(()) } }) .shared(); cx.new(|_| Self { _path_task, picker, data, }) }} impl gpui::Render for ProjectPicker { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .child(match &self.data { ProjectPickerData::Ssh { connection_string, nickname, } => SshConnectionHeader { connection_string: connection_string.clone(), paths: Default::default(), nickname: nickname.clone(), is_wsl: false, is_devcontainer: false, } .render(window, cx), ProjectPickerData::Wsl { distro_name } => SshConnectionHeader { connection_string: distro_name.clone(), paths: Default::default(), nickname: None, is_wsl: true, is_devcontainer: false, } .render(window, cx), }) .child( div() .border_t_1() .border_color(cx.theme().colors().border_variant) .child(self.picker.clone()), ) }} #[repr(transparent)]#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]struct SshServerIndex(usize);impl std::fmt::Display for SshServerIndex { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) }} #[repr(transparent)]#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]struct WslServerIndex(usize);impl std::fmt::Display for WslServerIndex { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) }} #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]enum ServerIndex { Ssh(SshServerIndex), Wsl(WslServerIndex),}impl From<SshServerIndex> for ServerIndex { fn from(index: SshServerIndex) -> Self { Self::Ssh(index) }}impl From<WslServerIndex> for ServerIndex { fn from(index: WslServerIndex) -> Self { Self::Wsl(index) }} #[derive(Clone)]enum RemoteEntry { Project { open_folder: NavigableEntry, projects: Vec<(NavigableEntry, RemoteProject)>, configure: NavigableEntry, connection: Connection, index: ServerIndex, }, SshConfig { open_folder: NavigableEntry, host: SharedString, },} impl RemoteEntry { fn is_from_zed(&self) -> bool { matches!(self, Self::Project { .. }) } fn connection(&self) -> Cow<'_, Connection> { match self { Self::Project { connection, .. } => Cow::Borrowed(connection), Self::SshConfig { host, .. } => Cow::Owned( SshConnection { host: host.clone(), ..SshConnection::default() } .into(), ), } }} #[derive(Clone)]struct DefaultState { scroll_handle: ScrollHandle, add_new_server: NavigableEntry, add_new_devcontainer: NavigableEntry, add_new_wsl: NavigableEntry, servers: Vec<RemoteEntry>,} impl DefaultState { fn new(ssh_config_servers: &BTreeSet<SharedString>, cx: &mut App) -> Self { let handle = ScrollHandle::new(); let add_new_server = NavigableEntry::new(&handle, cx); let add_new_devcontainer = NavigableEntry::new(&handle, cx); let add_new_wsl = NavigableEntry::new(&handle, cx); let ssh_settings = RemoteSettings::get_global(cx); let read_ssh_config = ssh_settings.read_ssh_config; let ssh_servers = ssh_settings .ssh_connections() .enumerate()Lines 1–500 of 2799. Load more
use crate::{ dev_container::start_dev_container, remote_connections::{ Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettings, SshConnection, SshConnectionHeader, connect, determine_paths_with_positions, open_remote_project, }, ssh_config::parse_ssh_config_hosts, }; use editor::Editor; use file_finder::OpenPathDelegate; use futures::{FutureExt, channel::oneshot, future::Shared, select}; use gpui::{ AnyElement, App, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, ScrollHandle, Subscription, Task, WeakEntity, Window, canvas, }; use language::Point; use log::info; use paths::{global_ssh_config_file, user_ssh_config_file}; use picker::Picker; use project::{Fs, Project}; use remote::{ RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions, remote_client::ConnectionIdentifier, }; use settings::{ RemoteProject, RemoteSettingsContent, Settings as _, SettingsStore, update_settings_file, watch_config_file, }; use smol::stream::StreamExt as _; use std::{ borrow::Cow, collections::BTreeSet, path::PathBuf, rc::Rc, sync::{ Arc, atomic::{self, AtomicUsize}, }, }; use ui::{ CommonAnimationExt, IconButtonShape, KeyBinding, List, ListItem, ListSeparator, Modal, ModalHeader, Navigable, NavigableEntry, Section, Tooltip, WithScrollbar, prelude::*, }; use util::{ ResultExt, paths::{PathStyle, RemotePathBuf}, rel_path::RelPath, }; use workspace::{ ModalView, OpenOptions, Toast, Workspace, notifications::{DetachAndPromptErr, NotificationId}, open_remote_project_with_existing_connection, }; pub struct RemoteServerProjects { mode: Mode, focus_handle: FocusHandle, workspace: WeakEntity<Workspace>, retained_connections: Vec<Entity<RemoteClient>>, ssh_config_updates: Task<()>, ssh_config_servers: BTreeSet<SharedString>, create_new_window: bool, _subscription: Subscription, } struct CreateRemoteServer { address_editor: Entity<Editor>, address_error: Option<SharedString>, ssh_prompt: Option<Entity<RemoteConnectionPrompt>>, _creating: Option<Task<Option<()>>>, } impl CreateRemoteServer { fn new(window: &mut Window, cx: &mut App) -> Self { let address_editor = cx.new(|cx| Editor::single_line(window, cx)); address_editor.update(cx, |this, cx| { this.focus_handle(cx).focus(window, cx); }); Self { address_editor, address_error: None, ssh_prompt: None, _creating: None, } } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] enum DevContainerCreationProgress { Initial, Creating, Error(String), } #[derive(Clone)] struct CreateRemoteDevContainer { // 3 Navigable Options // - Create from devcontainer.json // - Edit devcontainer.json // - Go back entries: [NavigableEntry; 3], progress: DevContainerCreationProgress, } impl CreateRemoteDevContainer { fn new(window: &mut Window, cx: &mut Context<RemoteServerProjects>) -> Self { let entries = std::array::from_fn(|_| NavigableEntry::focusable(cx)); entries[0].focus_handle.focus(window, cx); Self { entries, progress: DevContainerCreationProgress::Initial, } } fn progress(&mut self, progress: DevContainerCreationProgress) -> Self { self.progress = progress; self.clone() } } #[cfg(target_os = "windows")] struct AddWslDistro { picker: Entity<Picker<crate::wsl_picker::WslPickerDelegate>>, connection_prompt: Option<Entity<RemoteConnectionPrompt>>, _creating: Option<Task<()>>, } #[cfg(target_os = "windows")] impl AddWslDistro { fn new(window: &mut Window, cx: &mut Context<RemoteServerProjects>) -> Self { use crate::wsl_picker::{WslDistroSelected, WslPickerDelegate, WslPickerDismissed}; let delegate = WslPickerDelegate::new(); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)); cx.subscribe_in( &picker, window, |this, _, _: &WslDistroSelected, window, cx| { this.confirm(&menu::Confirm, window, cx); }, ) .detach(); cx.subscribe_in( &picker, window, |this, _, _: &WslPickerDismissed, window, cx| { this.cancel(&menu::Cancel, window, cx); }, ) .detach(); AddWslDistro { picker, connection_prompt: None, _creating: None, } } } enum ProjectPickerData { Ssh { connection_string: SharedString, nickname: Option<SharedString>, }, Wsl { distro_name: SharedString, }, } struct ProjectPicker { data: ProjectPickerData, picker: Entity<Picker<OpenPathDelegate>>, _path_task: Shared<Task<Option<()>>>, } struct EditNicknameState { index: SshServerIndex, editor: Entity<Editor>, } impl EditNicknameState { fn new(index: SshServerIndex, window: &mut Window, cx: &mut App) -> Self { let this = Self { index, editor: cx.new(|cx| Editor::single_line(window, cx)), }; let starting_text = RemoteSettings::get_global(cx) .ssh_connections() .nth(index.0) .and_then(|state| state.nickname) .filter(|text| !text.is_empty()); this.editor.update(cx, |this, cx| { this.set_placeholder_text("Add a nickname for this server", window, cx); if let Some(starting_text) = starting_text { this.set_text(starting_text, window, cx); } }); this.editor.focus_handle(cx).focus(window, cx); this } } impl Focusable for ProjectPicker { fn focus_handle(&self, cx: &App) -> FocusHandle { self.picker.focus_handle(cx) } } impl ProjectPicker { fn new( create_new_window: bool, index: ServerIndex, connection: RemoteConnectionOptions, project: Entity<Project>, home_dir: RemotePathBuf, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<RemoteServerProjects>, ) -> Entity<Self> { let (tx, rx) = oneshot::channel(); let lister = project::DirectoryLister::Project(project.clone()); let delegate = file_finder::OpenPathDelegate::new(tx, lister, false, cx); let picker = cx.new(|cx| { let picker = Picker::uniform_list(delegate, window, cx) .width(rems(34.)) .modal(false); picker.set_query(home_dir.to_string(), window, cx); picker }); let data = match &connection { RemoteConnectionOptions::Ssh(connection) => ProjectPickerData::Ssh { connection_string: connection.connection_string().into(), nickname: connection.nickname.clone().map(|nick| nick.into()), }, RemoteConnectionOptions::Wsl(connection) => ProjectPickerData::Wsl { distro_name: connection.distro_name.clone().into(), }, RemoteConnectionOptions::Docker(_) => ProjectPickerData::Ssh { // Not implemented as a project picker at this time connection_string: "".into(), nickname: None, }, }; let _path_task = cx .spawn_in(window, { let workspace = workspace; async move |this, cx| { let Ok(Some(paths)) = rx.await else { workspace .update_in(cx, |workspace, window, cx| { let fs = workspace.project().read(cx).fs().clone(); let weak = cx.entity().downgrade(); workspace.toggle_modal(window, cx, |window, cx| { RemoteServerProjects::new( create_new_window, fs, window, weak, cx, ) }); }) .log_err()?; return None; }; let app_state = workspace .read_with(cx, |workspace, _| workspace.app_state().clone()) .ok()?; let remote_connection = project .read_with(cx, |project, cx| { project.remote_client()?.read(cx).connection() }) .ok()??; let (paths, paths_with_positions) = determine_paths_with_positions(&remote_connection, paths).await; cx.update(|_, cx| { let fs = app_state.fs.clone(); update_settings_file(fs, cx, { let paths = paths .iter() .map(|path| path.to_string_lossy().into_owned()) .collect(); move |settings, _| match index { ServerIndex::Ssh(index) => { if let Some(server) = settings .remote .ssh_connections .as_mut() .and_then(|connections| connections.get_mut(index.0)) { server.projects.insert(RemoteProject { paths }); }; } ServerIndex::Wsl(index) => { if let Some(server) = settings .remote .wsl_connections .as_mut() .and_then(|connections| connections.get_mut(index.0)) { server.projects.insert(RemoteProject { paths }); }; } } }); }) .log_err(); let options = cx .update(|_, cx| (app_state.build_window_options)(None, cx)) .log_err()?; let window = cx .open_window(options, |window, cx| { cx.new(|cx| { telemetry::event!("SSH Project Created"); Workspace::new(None, project.clone(), app_state.clone(), window, cx) }) }) .log_err()?; let items = open_remote_project_with_existing_connection( connection, project, paths, app_state, window, cx, ) .await .log_err(); if let Some(items) = items { for (item, path) in items.into_iter().zip(paths_with_positions) { let Some(item) = item else { continue; }; let Some(row) = path.row else { continue; }; if let Some(active_editor) = item.downcast::<Editor>() { window .update(cx, |_, window, cx| { active_editor.update(cx, |editor, cx| { let row = row.saturating_sub(1); let col = path.column.unwrap_or(0).saturating_sub(1); editor.go_to_singleton_buffer_point( Point::new(row, col), window, cx, ); }); }) .ok(); } } } this.update(cx, |_, cx| { cx.emit(DismissEvent); }) .ok(); Some(()) } }) .shared(); cx.new(|_| Self { _path_task, picker, data, }) } } impl gpui::Render for ProjectPicker { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .child(match &self.data { ProjectPickerData::Ssh { connection_string, nickname, } => SshConnectionHeader { connection_string: connection_string.clone(), paths: Default::default(), nickname: nickname.clone(), is_wsl: false, is_devcontainer: false, } .render(window, cx), ProjectPickerData::Wsl { distro_name } => SshConnectionHeader { connection_string: distro_name.clone(), paths: Default::default(), nickname: None, is_wsl: true, is_devcontainer: false, } .render(window, cx), }) .child( div() .border_t_1() .border_color(cx.theme().colors().border_variant) .child(self.picker.clone()), ) } } #[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct SshServerIndex(usize); impl std::fmt::Display for SshServerIndex { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } #[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct WslServerIndex(usize); impl std::fmt::Display for WslServerIndex { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] enum ServerIndex { Ssh(SshServerIndex), Wsl(WslServerIndex), } impl From<SshServerIndex> for ServerIndex { fn from(index: SshServerIndex) -> Self { Self::Ssh(index) } } impl From<WslServerIndex> for ServerIndex { fn from(index: WslServerIndex) -> Self { Self::Wsl(index) } } #[derive(Clone)] enum RemoteEntry { Project { open_folder: NavigableEntry, projects: Vec<(NavigableEntry, RemoteProject)>, configure: NavigableEntry, connection: Connection, index: ServerIndex, }, SshConfig { open_folder: NavigableEntry, host: SharedString, }, } impl RemoteEntry { fn is_from_zed(&self) -> bool { matches!(self, Self::Project { .. }) } fn connection(&self) -> Cow<'_, Connection> { match self { Self::Project { connection, .. } => Cow::Borrowed(connection), Self::SshConfig { host, .. } => Cow::Owned( SshConnection { host: host.clone(), ..SshConnection::default() } .into(), ), } } } #[derive(Clone)] struct DefaultState { scroll_handle: ScrollHandle, add_new_server: NavigableEntry, add_new_devcontainer: NavigableEntry, add_new_wsl: NavigableEntry, servers: Vec<RemoteEntry>, } impl DefaultState { fn new(ssh_config_servers: &BTreeSet<SharedString>, cx: &mut App) -> Self { let handle = ScrollHandle::new(); let add_new_server = NavigableEntry::new(&handle, cx); let add_new_devcontainer = NavigableEntry::new(&handle, cx); let add_new_wsl = NavigableEntry::new(&handle, cx); let ssh_settings = RemoteSettings::get_global(cx); let read_ssh_config = ssh_settings.read_ssh_config; let ssh_servers = ssh_settings .ssh_connections() .enumerate()