@@ -1,7 +1,8 @@
use crate::{
remote_connections::{
- RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent, SshConnection,
- SshConnectionHeader, SshProject, SshSettings, connect_over_ssh, open_remote_project,
+ Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent,
+ SshConnection, SshConnectionHeader, SshProject, SshSettings, connect, connect_over_ssh,
+ open_remote_project,
},
ssh_config::parse_ssh_config_hosts,
};
@@ -13,11 +14,12 @@ use gpui::{
FocusHandle, Focusable, PromptLevel, ScrollHandle, Subscription, Task, WeakEntity, Window,
canvas,
};
+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,
+ RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions,
remote_client::ConnectionIdentifier,
};
use settings::{Settings, SettingsStore, update_settings_file, watch_config_file};
@@ -79,27 +81,253 @@ impl CreateRemoteServer {
}
}
+#[cfg(target_os = "windows")]
+struct AddWslDistro {
+ picker: Entity<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 {
+ 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,
+ }
+ }
+}
+
+#[cfg(target_os = "windows")]
+#[derive(Clone, Debug)]
+pub struct WslDistroSelected(pub String);
+
+#[cfg(target_os = "windows")]
+#[derive(Clone, Debug)]
+pub struct WslPickerDismissed;
+
+#[cfg(target_os = "windows")]
+struct WslPickerDelegate {
+ selected_index: usize,
+ distro_list: Option<Vec<String>>,
+ matches: Vec<fuzzy::StringMatch>,
+}
+
+#[cfg(target_os = "windows")]
+impl WslPickerDelegate {
+ fn new() -> Self {
+ WslPickerDelegate {
+ selected_index: 0,
+ distro_list: None,
+ matches: Vec::new(),
+ }
+ }
+
+ pub fn selected_distro(&self) -> Option<String> {
+ self.matches
+ .get(self.selected_index)
+ .map(|m| m.string.clone())
+ }
+}
+
+#[cfg(target_os = "windows")]
+impl WslPickerDelegate {
+ fn fetch_distros() -> anyhow::Result<Vec<String>> {
+ use anyhow::Context;
+ use windows_registry::CURRENT_USER;
+
+ let lxss_key = CURRENT_USER
+ .open("Software\\Microsoft\\Windows\\CurrentVersion\\Lxss")
+ .context("failed to get lxss wsl key")?;
+
+ let distros = lxss_key
+ .keys()
+ .context("failed to get wsl distros")?
+ .filter_map(|key| {
+ lxss_key
+ .open(&key)
+ .context("failed to open subkey for distro")
+ .log_err()
+ })
+ .filter_map(|distro| distro.get_string("DistributionName").ok())
+ .collect::<Vec<_>>();
+
+ Ok(distros)
+ }
+}
+
+#[cfg(target_os = "windows")]
+impl EventEmitter<WslDistroSelected> for Picker<WslPickerDelegate> {}
+
+#[cfg(target_os = "windows")]
+impl EventEmitter<WslPickerDismissed> for Picker<WslPickerDelegate> {}
+
+#[cfg(target_os = "windows")]
+impl picker::PickerDelegate for WslPickerDelegate {
+ type ListItem = ListItem;
+
+ fn match_count(&self) -> usize {
+ self.matches.len()
+ }
+
+ fn selected_index(&self) -> usize {
+ self.selected_index
+ }
+
+ fn set_selected_index(
+ &mut self,
+ ix: usize,
+ _window: &mut Window,
+ cx: &mut Context<Picker<Self>>,
+ ) {
+ self.selected_index = ix;
+ cx.notify();
+ }
+
+ fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
+ Arc::from("Enter WSL distro name")
+ }
+
+ fn update_matches(
+ &mut self,
+ query: String,
+ _window: &mut Window,
+ cx: &mut Context<Picker<Self>>,
+ ) -> Task<()> {
+ use fuzzy::StringMatchCandidate;
+
+ let needs_fetch = self.distro_list.is_none();
+ if needs_fetch {
+ let distros = Self::fetch_distros().log_err();
+ self.distro_list = distros;
+ }
+
+ if let Some(distro_list) = &self.distro_list {
+ use ordered_float::OrderedFloat;
+
+ let candidates = distro_list
+ .iter()
+ .enumerate()
+ .map(|(id, distro)| StringMatchCandidate::new(id, distro))
+ .collect::<Vec<_>>();
+
+ let query = query.trim_start();
+ let smart_case = query.chars().any(|c| c.is_uppercase());
+ self.matches = smol::block_on(fuzzy::match_strings(
+ candidates.as_slice(),
+ query,
+ smart_case,
+ true,
+ 100,
+ &Default::default(),
+ cx.background_executor().clone(),
+ ));
+ self.matches.sort_unstable_by_key(|m| m.candidate_id);
+
+ self.selected_index = self
+ .matches
+ .iter()
+ .enumerate()
+ .rev()
+ .max_by_key(|(_, m)| OrderedFloat(m.score))
+ .map(|(index, _)| index)
+ .unwrap_or(0);
+ }
+
+ Task::ready(())
+ }
+
+ fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ if let Some(distro) = self.matches.get(self.selected_index) {
+ cx.emit(WslDistroSelected(distro.string.clone()));
+ }
+ }
+
+ fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ cx.emit(WslPickerDismissed);
+ }
+
+ fn render_match(
+ &self,
+ ix: usize,
+ selected: bool,
+ _: &mut Window,
+ _: &mut Context<Picker<Self>>,
+ ) -> Option<Self::ListItem> {
+ use ui::HighlightedLabel;
+
+ let matched = self.matches.get(ix)?;
+ Some(
+ ListItem::new(ix)
+ .toggle_state(selected)
+ .inset(true)
+ .spacing(ui::ListItemSpacing::Sparse)
+ .child(
+ h_flex()
+ .flex_grow()
+ .gap_3()
+ .child(Icon::new(IconName::Server))
+ .child(v_flex().child(HighlightedLabel::new(
+ matched.string.clone(),
+ matched.positions.clone(),
+ ))),
+ ),
+ )
+ }
+}
+
+enum ProjectPickerData {
+ Ssh {
+ connection_string: SharedString,
+ nickname: Option<SharedString>,
+ },
+ Wsl {
+ distro_name: SharedString,
+ },
+}
+
struct ProjectPicker {
- connection_string: SharedString,
- nickname: Option<SharedString>,
+ data: ProjectPickerData,
picker: Entity<Picker<OpenPathDelegate>>,
_path_task: Shared<Task<Option<()>>>,
}
struct EditNicknameState {
- index: usize,
+ index: SshServerIndex,
editor: Entity<Editor>,
}
impl EditNicknameState {
- fn new(index: usize, window: &mut Window, cx: &mut App) -> Self {
+ 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 = SshSettings::get_global(cx)
.ssh_connections()
- .nth(index)
+ .nth(index.0)
.and_then(|state| state.nickname)
.filter(|text| !text.is_empty());
this.editor.update(cx, |this, cx| {
@@ -122,8 +350,8 @@ impl Focusable for ProjectPicker {
impl ProjectPicker {
fn new(
create_new_window: bool,
- ix: usize,
- connection: SshConnectionOptions,
+ index: ServerIndex,
+ connection: RemoteConnectionOptions,
project: Entity<Project>,
home_dir: RemotePathBuf,
path_style: PathStyle,
@@ -142,8 +370,16 @@ impl ProjectPicker {
picker.set_query(home_dir.to_string(), window, cx);
picker
});
- let connection_string = connection.connection_string().into();
- let nickname = connection.nickname.clone().map(|nick| nick.into());
+
+ 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(),
+ },
+ };
let _path_task = cx
.spawn_in(window, {
let workspace = workspace;
@@ -178,13 +414,24 @@ impl ProjectPicker {
.iter()
.map(|path| path.to_string_lossy().to_string())
.collect();
- move |setting, _| {
- if let Some(server) = setting
- .ssh_connections
- .as_mut()
- .and_then(|connections| connections.get_mut(ix))
- {
- server.projects.insert(SshProject { paths });
+ move |setting, _| match index {
+ ServerIndex::Ssh(index) => {
+ if let Some(server) = setting
+ .ssh_connections
+ .as_mut()
+ .and_then(|connections| connections.get_mut(index.0))
+ {
+ server.projects.insert(SshProject { paths });
+ };
+ }
+ ServerIndex::Wsl(index) => {
+ if let Some(server) = setting
+ .wsl_connections
+ .as_mut()
+ .and_then(|connections| connections.get_mut(index.0))
+ {
+ server.projects.insert(SshProject { paths });
+ };
}
}
});
@@ -204,12 +451,7 @@ impl ProjectPicker {
.log_err()?;
open_remote_project_with_existing_connection(
- RemoteConnectionOptions::Ssh(connection),
- project,
- paths,
- app_state,
- window,
- cx,
+ connection, project, paths, app_state, window, cx,
)
.await
.log_err();
@@ -225,8 +467,7 @@ impl ProjectPicker {
cx.new(|_| Self {
_path_task,
picker,
- connection_string,
- nickname,
+ data,
})
}
}
@@ -234,14 +475,23 @@ impl ProjectPicker {
impl gpui::Render for ProjectPicker {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
- .child(
- SshConnectionHeader {
- connection_string: self.connection_string.clone(),
+ .child(match &self.data {
+ ProjectPickerData::Ssh {
+ connection_string,
+ nickname,
+ } => SshConnectionHeader {
+ connection_string: connection_string.clone(),
paths: Default::default(),
- nickname: self.nickname.clone(),
+ nickname: nickname.clone(),
}
.render(window, cx),
- )
+ ProjectPickerData::Wsl { distro_name } => SshConnectionHeader {
+ connection_string: distro_name.clone(),
+ paths: Default::default(),
+ nickname: None,
+ }
+ .render(window, cx),
+ })
.child(
div()
.border_t_1()
@@ -251,13 +501,48 @@ impl gpui::Render for ProjectPicker {
}
}
+#[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, SshProject)>,
configure: NavigableEntry,
- connection: SshConnection,
+ connection: Connection,
+ index: ServerIndex,
},
SshConfig {
open_folder: NavigableEntry,
@@ -270,13 +555,16 @@ impl RemoteEntry {
matches!(self, Self::Project { .. })
}
- fn connection(&self) -> Cow<'_, SshConnection> {
+ fn connection(&self) -> Cow<'_, Connection> {
match self {
Self::Project { connection, .. } => Cow::Borrowed(connection),
- Self::SshConfig { host, .. } => Cow::Owned(SshConnection {
- host: host.clone(),
- ..SshConnection::default()
- }),
+ Self::SshConfig { host, .. } => Cow::Owned(
+ SshConnection {
+ host: host.clone(),
+ ..SshConnection::default()
+ }
+ .into(),
+ ),
}
}
}
@@ -285,6 +573,7 @@ impl RemoteEntry {
struct DefaultState {
scroll_handle: ScrollHandle,
add_new_server: NavigableEntry,
+ add_new_wsl: NavigableEntry,
servers: Vec<RemoteEntry>,
}
@@ -292,13 +581,15 @@ 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_wsl = NavigableEntry::new(&handle, cx);
let ssh_settings = SshSettings::get_global(cx);
let read_ssh_config = ssh_settings.read_ssh_config;
- let mut servers: Vec<RemoteEntry> = ssh_settings
+ let ssh_servers = ssh_settings
.ssh_connections()
- .map(|connection| {
+ .enumerate()
+ .map(|(index, connection)| {
let open_folder = NavigableEntry::new(&handle, cx);
let configure = NavigableEntry::new(&handle, cx);
let projects = connection
@@ -310,16 +601,42 @@ impl DefaultState {
open_folder,
configure,
projects,
- connection,
+ index: ServerIndex::Ssh(SshServerIndex(index)),
+ connection: connection.into(),
}
- })
- .collect();
+ });
+
+ let wsl_servers = ssh_settings
+ .wsl_connections()
+ .enumerate()
+ .map(|(index, connection)| {
+ let open_folder = NavigableEntry::new(&handle, cx);
+ let configure = NavigableEntry::new(&handle, cx);
+ let projects = connection
+ .projects
+ .iter()
+ .map(|project| (NavigableEntry::new(&handle, cx), project.clone()))
+ .collect();
+ RemoteEntry::Project {
+ open_folder,
+ configure,
+ projects,
+ index: ServerIndex::Wsl(WslServerIndex(index)),
+ connection: connection.into(),
+ }
+ });
+
+ let mut servers = ssh_servers.chain(wsl_servers).collect::<Vec<RemoteEntry>>();
if read_ssh_config {
let mut extra_servers_from_config = ssh_config_servers.clone();
for server in &servers {
- if let RemoteEntry::Project { connection, .. } = server {
- extra_servers_from_config.remove(&connection.host);
+ if let RemoteEntry::Project {
+ connection: Connection::Ssh(ssh_options),
+ ..
+ } = server
+ {
+ extra_servers_from_config.remove(&SharedString::new(ssh_options.host.clone()));
}
}
servers.extend(extra_servers_from_config.into_iter().map(|host| {
@@ -333,23 +650,43 @@ impl DefaultState {
Self {
scroll_handle: handle,
add_new_server,
+ add_new_wsl,
servers,
}
}
}
#[derive(Clone)]
-struct ViewServerOptionsState {
- server_index: usize,
- connection: SshConnection,
- entries: [NavigableEntry; 4],
+enum ViewServerOptionsState {
+ Ssh {
+ connection: SshConnectionOptions,
+ server_index: SshServerIndex,
+ entries: [NavigableEntry; 4],
+ },
+ Wsl {
+ connection: WslConnectionOptions,
+ server_index: WslServerIndex,
+ entries: [NavigableEntry; 2],
+ },
}
+
+impl ViewServerOptionsState {
+ fn entries(&self) -> &[NavigableEntry] {
+ match self {
+ Self::Ssh { entries, .. } => entries,
+ Self::Wsl { entries, .. } => entries,
+ }
+ }
+}
+
enum Mode {
Default(DefaultState),
ViewServerOptions(ViewServerOptionsState),
EditNickname(EditNicknameState),
ProjectPicker(Entity<ProjectPicker>),
CreateRemoteServer(CreateRemoteServer),
+ #[cfg(target_os = "windows")]
+ AddWslDistro(AddWslDistro),
}
impl Mode {
@@ -357,6 +694,7 @@ impl Mode {
Self::Default(DefaultState::new(ssh_config_servers, cx))
}
}
+
impl RemoteServerProjects {
pub fn new(
create_new_window: bool,
@@ -405,10 +743,10 @@ impl RemoteServerProjects {
}
}
- pub fn project_picker(
+ fn project_picker(
create_new_window: bool,
- ix: usize,
- connection_options: remote::SshConnectionOptions,
+ index: ServerIndex,
+ connection_options: remote::RemoteConnectionOptions,
project: Entity<Project>,
home_dir: RemotePathBuf,
path_style: PathStyle,
@@ -420,7 +758,7 @@ impl RemoteServerProjects {
let mut this = Self::new(create_new_window, fs, window, workspace.clone(), cx);
this.mode = Mode::ProjectPicker(ProjectPicker::new(
create_new_window,
- ix,
+ index,
connection_options,
project,
home_dir,
@@ -480,6 +818,7 @@ impl RemoteServerProjects {
match connection.await {
Some(Some(client)) => this
.update_in(cx, |this, window, cx| {
+ info!("ssh server created");
telemetry::event!("SSH Server Created");
this.retained_connections.push(client);
this.add_ssh_server(connection_options, cx);
@@ -517,25 +856,100 @@ impl RemoteServerProjects {
});
}
+ #[cfg(target_os = "windows")]
+ fn connect_wsl_distro(
+ &mut self,
+ picker: Entity<Picker<WslPickerDelegate>>,
+ distro: String,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let connection_options = WslConnectionOptions {
+ distro_name: distro,
+ user: None,
+ };
+
+ let prompt = cx.new(|cx| {
+ RemoteConnectionPrompt::new(connection_options.distro_name.clone(), None, window, cx)
+ });
+ let connection = connect(
+ ConnectionIdentifier::setup(),
+ connection_options.clone().into(),
+ prompt.clone(),
+ window,
+ cx,
+ )
+ .prompt_err("Failed to connect", window, cx, |_, _, _| None);
+
+ let wsl_picker = picker.clone();
+ let creating = cx.spawn_in(window, async move |this, cx| {
+ match connection.await {
+ Some(Some(client)) => this
+ .update_in(cx, |this, window, cx| {
+ telemetry::event!("WSL Distro Added");
+ this.retained_connections.push(client);
+ this.add_wsl_distro(connection_options, cx);
+ this.mode = Mode::default_mode(&BTreeSet::new(), cx);
+ this.focus_handle(cx).focus(window);
+ cx.notify()
+ })
+ .log_err(),
+ _ => this
+ .update(cx, |this, cx| {
+ this.mode = Mode::AddWslDistro(AddWslDistro {
+ picker: wsl_picker,
+ connection_prompt: None,
+ _creating: None,
+ });
+ cx.notify()
+ })
+ .log_err(),
+ };
+ ()
+ });
+
+ self.mode = Mode::AddWslDistro(AddWslDistro {
+ picker,
+ connection_prompt: Some(prompt),
+ _creating: Some(creating),
+ });
+ }
+
fn view_server_options(
&mut self,
- (server_index, connection): (usize, SshConnection),
+ (server_index, connection): (ServerIndex, RemoteConnectionOptions),
window: &mut Window,
cx: &mut Context<Self>,
) {
- self.mode = Mode::ViewServerOptions(ViewServerOptionsState {
- server_index,
- connection,
- entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)),
+ self.mode = Mode::ViewServerOptions(match (server_index, connection) {
+ (ServerIndex::Ssh(server_index), RemoteConnectionOptions::Ssh(connection)) => {
+ ViewServerOptionsState::Ssh {
+ connection,
+ server_index,
+ entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)),
+ }
+ }
+ (ServerIndex::Wsl(server_index), RemoteConnectionOptions::Wsl(connection)) => {
+ ViewServerOptionsState::Wsl {
+ connection,
+ server_index,
+ entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)),
+ }
+ }
+ _ => {
+ log::error!("server index and connection options mismatch");
+ self.mode = Mode::default_mode(&BTreeSet::default(), cx);
+ return;
+ }
});
self.focus_handle(cx).focus(window);
cx.notify();
}
- fn create_ssh_project(
+ fn create_remote_project(
&mut self,
- ix: usize,
- ssh_connection: SshConnection,
+ index: ServerIndex,
+ connection_options: RemoteConnectionOptions,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -544,17 +958,11 @@ impl RemoteServerProjects {
};
let create_new_window = self.create_new_window;
- let connection_options: SshConnectionOptions = ssh_connection.into();
workspace.update(cx, |_, cx| {
cx.defer_in(window, move |workspace, window, cx| {
let app_state = workspace.app_state().clone();
workspace.toggle_modal(window, cx, |window, cx| {
- RemoteConnectionModal::new(
- &RemoteConnectionOptions::Ssh(connection_options.clone()),
- Vec::new(),
- window,
- cx,
- )
+ RemoteConnectionModal::new(&connection_options, Vec::new(), window, cx)
});
let prompt = workspace
.active_modal::<RemoteConnectionModal>(cx)
@@ -563,7 +971,7 @@ impl RemoteServerProjects {
.prompt
.clone();
- let connect = connect_over_ssh(
+ let connect = connect(
ConnectionIdentifier::setup(),
connection_options.clone(),
prompt,
@@ -624,7 +1032,7 @@ impl RemoteServerProjects {
workspace.toggle_modal(window, cx, |window, cx| {
RemoteServerProjects::project_picker(
create_new_window,
- ix,
+ index,
connection_options,
project,
home_dir,
@@ -662,7 +1070,7 @@ impl RemoteServerProjects {
let index = state.index;
self.update_settings_file(cx, move |setting, _| {
if let Some(connections) = setting.ssh_connections.as_mut()
- && let Some(connection) = connections.get_mut(index)
+ && let Some(connection) = connections.get_mut(index.0)
{
connection.nickname = text;
}
@@ -670,6 +1078,12 @@ impl RemoteServerProjects {
self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
self.focus_handle.focus(window);
}
+ #[cfg(target_os = "windows")]
+ Mode::AddWslDistro(state) => {
+ let delegate = &state.picker.read(cx).delegate;
+ let distro = delegate.selected_distro().unwrap();
+ self.connect_wsl_distro(state.picker.clone(), distro, window, cx);
+ }
}
}
@@ -702,11 +1116,19 @@ impl RemoteServerProjects {
cx: &mut Context<Self>,
) -> impl IntoElement {
let connection = ssh_server.connection().into_owned();
- let (main_label, aux_label) = if let Some(nickname) = connection.nickname.clone() {
- let aux_label = SharedString::from(format!("({})", connection.host));
- (nickname.into(), Some(aux_label))
- } else {
- (connection.host.clone(), None)
+
+ let (main_label, aux_label, is_wsl) = match &connection {
+ Connection::Ssh(connection) => {
+ if let Some(nickname) = connection.nickname.clone() {
+ let aux_label = SharedString::from(format!("({})", connection.host));
+ (nickname.into(), Some(aux_label), false)
+ } else {
+ (connection.host.clone(), None, false)
+ }
+ }
+ Connection::Wsl(wsl_connection_options) => {
+ (wsl_connection_options.distro_name.clone(), None, true)
+ }
};
v_flex()
.w_full()
@@ -720,11 +1142,23 @@ impl RemoteServerProjects {
.gap_1()
.overflow_hidden()
.child(
- div().max_w_96().overflow_hidden().text_ellipsis().child(
- Label::new(main_label)
- .size(LabelSize::Small)
- .color(Color::Muted),
- ),
+ h_flex()
+ .gap_1()
+ .max_w_96()
+ .overflow_hidden()
+ .text_ellipsis()
+ .when(is_wsl, |this| {
+ this.child(
+ Label::new("WSL:")
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ })
+ .child(
+ Label::new(main_label)
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ ),
)
.children(
aux_label.map(|label| {
@@ -738,98 +1172,114 @@ impl RemoteServerProjects {
projects,
configure,
connection,
- } => List::new()
- .empty_message("No projects.")
- .children(projects.iter().enumerate().map(|(pix, p)| {
- v_flex().gap_0p5().child(self.render_ssh_project(
- ix,
- ssh_server.clone(),
- pix,
- p,
- window,
- cx,
- ))
- }))
- .child(
- h_flex()
- .id(("new-remote-project-container", ix))
- .track_focus(&open_folder.focus_handle)
- .anchor_scroll(open_folder.scroll_anchor.clone())
- .on_action(cx.listener({
- let ssh_connection = connection.clone();
- move |this, _: &menu::Confirm, window, cx| {
- this.create_ssh_project(ix, ssh_connection.clone(), window, cx);
- }
- }))
- .child(
- ListItem::new(("new-remote-project", ix))
- .toggle_state(
- open_folder.focus_handle.contains_focused(window, cx),
- )
- .inset(true)
- .spacing(ui::ListItemSpacing::Sparse)
- .start_slot(Icon::new(IconName::Plus).color(Color::Muted))
- .child(Label::new("Open Folder"))
- .on_click(cx.listener({
- let ssh_connection = connection.clone();
- move |this, _, window, cx| {
- this.create_ssh_project(
- ix,
- ssh_connection.clone(),
- window,
- cx,
- );
- }
- })),
- ),
- )
- .child(
- h_flex()
- .id(("server-options-container", ix))
- .track_focus(&configure.focus_handle)
- .anchor_scroll(configure.scroll_anchor.clone())
- .on_action(cx.listener({
- let ssh_connection = connection.clone();
- move |this, _: &menu::Confirm, window, cx| {
- this.view_server_options(
- (ix, ssh_connection.clone()),
- window,
- cx,
- );
- }
- }))
- .child(
- ListItem::new(("server-options", ix))
- .toggle_state(
- configure.focus_handle.contains_focused(window, cx),
- )
- .inset(true)
- .spacing(ui::ListItemSpacing::Sparse)
- .start_slot(Icon::new(IconName::Settings).color(Color::Muted))
- .child(Label::new("View Server Options"))
- .on_click(cx.listener({
- let ssh_connection = connection.clone();
- move |this, _, window, cx| {
- this.view_server_options(
- (ix, ssh_connection.clone()),
- window,
- cx,
- );
- }
- })),
- ),
- ),
+ index,
+ } => {
+ let index = *index;
+ List::new()
+ .empty_message("No projects.")
+ .children(projects.iter().enumerate().map(|(pix, p)| {
+ v_flex().gap_0p5().child(self.render_ssh_project(
+ index,
+ ssh_server.clone(),
+ pix,
+ p,
+ window,
+ cx,
+ ))
+ }))
+ .child(
+ h_flex()
+ .id(("new-remote-project-container", ix))
+ .track_focus(&open_folder.focus_handle)
+ .anchor_scroll(open_folder.scroll_anchor.clone())
+ .on_action(cx.listener({
+ let connection = connection.clone();
+ move |this, _: &menu::Confirm, window, cx| {
+ this.create_remote_project(
+ index,
+ connection.clone().into(),
+ window,
+ cx,
+ );
+ }
+ }))
+ .child(
+ ListItem::new(("new-remote-project", ix))
+ .toggle_state(
+ open_folder.focus_handle.contains_focused(window, cx),
+ )
+ .inset(true)
+ .spacing(ui::ListItemSpacing::Sparse)
+ .start_slot(Icon::new(IconName::Plus).color(Color::Muted))
+ .child(Label::new("Open Folder"))
+ .on_click(cx.listener({
+ let connection = connection.clone();
+ move |this, _, window, cx| {
+ this.create_remote_project(
+ index,
+ connection.clone().into(),
+ window,
+ cx,
+ );
+ }
+ })),
+ ),
+ )
+ .child(
+ h_flex()
+ .id(("server-options-container", ix))
+ .track_focus(&configure.focus_handle)
+ .anchor_scroll(configure.scroll_anchor.clone())
+ .on_action(cx.listener({
+ let connection = connection.clone();
+ move |this, _: &menu::Confirm, window, cx| {
+ this.view_server_options(
+ (index, connection.clone().into()),
+ window,
+ cx,
+ );
+ }
+ }))
+ .child(
+ ListItem::new(("server-options", ix))
+ .toggle_state(
+ configure.focus_handle.contains_focused(window, cx),
+ )
+ .inset(true)
+ .spacing(ui::ListItemSpacing::Sparse)
+ .start_slot(
+ Icon::new(IconName::Settings).color(Color::Muted),
+ )
+ .child(Label::new("View Server Options"))
+ .on_click(cx.listener({
+ let ssh_connection = connection.clone();
+ move |this, _, window, cx| {
+ this.view_server_options(
+ (index, ssh_connection.clone().into()),
+ window,
+ cx,
+ );
+ }
+ })),
+ ),
+ )
+ }
RemoteEntry::SshConfig { open_folder, host } => List::new().child(
h_flex()
.id(("new-remote-project-container", ix))
.track_focus(&open_folder.focus_handle)
.anchor_scroll(open_folder.scroll_anchor.clone())
.on_action(cx.listener({
- let ssh_connection = connection.clone();
+ let connection = connection.clone();
let host = host.clone();
move |this, _: &menu::Confirm, window, cx| {
let new_ix = this.create_host_from_ssh_config(&host, cx);
- this.create_ssh_project(new_ix, ssh_connection.clone(), window, cx);
+ this.create_remote_project(
+ new_ix.into(),
+ connection.clone().into(),
+ window,
+ cx,
+ );
}
}))
.child(