use std::{path::PathBuf, sync::Arc}; use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Subscription, Task};use picker::Picker;use remote::{RemoteConnectionOptions, WslConnectionOptions};use ui::{ App, Context, HighlightedLabel, Icon, IconName, InteractiveElement, ListItem, ParentElement, Render, Styled, StyledExt, Toggleable, Window, div, h_flex, rems, v_flex,};use util::ResultExt as _;use workspace::{ModalView, MultiWorkspace}; use crate::open_remote_project; #[derive(Clone, Debug)]pub struct WslDistroSelected { pub secondary: bool, pub distro: String,} #[derive(Clone, Debug)]pub struct WslPickerDismissed; pub(crate) struct WslPickerDelegate { selected_index: usize, distro_list: Option<Vec<String>>, matches: Vec<fuzzy_nucleo::StringMatch>,} impl WslPickerDelegate { pub 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.to_string()) }} 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) }} impl EventEmitter<WslDistroSelected> for Picker<WslPickerDelegate> {} impl EventEmitter<WslPickerDismissed> for Picker<WslPickerDelegate> {} impl picker::PickerDelegate for WslPickerDelegate { type ListItem = ListItem; fn name() -> &'static str { "WSL-distor-picker" } 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_nucleo::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 case = fuzzy_nucleo::Case::smart_if_uppercase_in(query); self.matches = fuzzy_nucleo::match_strings( &candidates, query, case, fuzzy_nucleo::LengthPenalty::On, 100, ); 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 { secondary, distro: distro.string.to_string(), }); } } 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> { let matched = self.matches.get(ix)?; Some( ListItem::new(ix) .toggle_state(selected) .inset(true) .spacing(ui::ListItemSpacing::Sparse) .child( h_flex() .flex_grow_1() .gap_3() .child(Icon::new(IconName::Linux)) .child(v_flex().child(HighlightedLabel::new( matched.string.clone(), matched.positions.clone(), ))), ), ) }} pub(crate) struct WslOpenModal { paths: Vec<PathBuf>, create_new_window: bool, picker: Entity<Picker<WslPickerDelegate>>, _subscriptions: [Subscription; 2],} impl WslOpenModal { pub fn new( paths: Vec<PathBuf>, create_new_window: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let delegate = WslPickerDelegate::new(); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded()); let selected = cx.subscribe_in( &picker, window, |this, _, event: &WslDistroSelected, window, cx| { this.confirm(&event.distro, event.secondary, window, cx); }, ); let dismissed = cx.subscribe_in( &picker, window, |this, _, _: &WslPickerDismissed, window, cx| { this.cancel(&menu::Cancel, window, cx); }, ); WslOpenModal { paths, create_new_window, picker, _subscriptions: [selected, dismissed], } } fn confirm( &mut self, distro: &str, secondary: bool, window: &mut Window, cx: &mut Context<Self>, ) { let app_state = workspace::AppState::global(cx); let connection_options = RemoteConnectionOptions::Wsl(WslConnectionOptions { distro_name: distro.to_string(), user: None, }); let replace_current_window = match self.create_new_window { true => secondary, false => !secondary, }; let open_mode = if replace_current_window { workspace::OpenMode::Activate } else { workspace::OpenMode::NewWindow }; let paths = self.paths.clone(); let open_options = workspace::OpenOptions { requesting_window: window.window_handle().downcast::<MultiWorkspace>(), open_mode, ..Default::default() }; cx.emit(DismissEvent); cx.spawn_in(window, async move |_, cx| { open_remote_project(connection_options, paths, app_state, open_options, cx).await }) .detach(); } fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) { cx.emit(DismissEvent); }} impl ModalView for WslOpenModal {} impl Focusable for WslOpenModal { fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { self.picker.focus_handle(cx) }} impl EventEmitter<DismissEvent> for WslOpenModal {} impl Render for WslOpenModal { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement { div() .on_mouse_down_out(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))) .on_action(cx.listener(Self::cancel)) .elevation_3(cx) .w(rems(34.)) .flex_1() .overflow_hidden() .child(self.picker.clone()) }}
use std::{path::PathBuf, sync::Arc};
use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Subscription, Task};
use picker::Picker;
use remote::{RemoteConnectionOptions, WslConnectionOptions};
use ui::{
App, Context, HighlightedLabel, Icon, IconName, InteractiveElement, ListItem, ParentElement,
Render, Styled, StyledExt, Toggleable, Window, div, h_flex, rems, v_flex,
};
use util::ResultExt as _;
use workspace::{ModalView, MultiWorkspace};
use crate::open_remote_project;
#[derive(Clone, Debug)]
pub struct WslDistroSelected {
pub secondary: bool,
pub distro: String,
}
#[derive(Clone, Debug)]
pub struct WslPickerDismissed;
pub(crate) struct WslPickerDelegate {
selected_index: usize,
distro_list: Option<Vec<String>>,
matches: Vec<fuzzy_nucleo::StringMatch>,
}
impl WslPickerDelegate {
pub 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.to_string())
}
}
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)
}
}
impl EventEmitter<WslDistroSelected> for Picker<WslPickerDelegate> {}
impl EventEmitter<WslPickerDismissed> for Picker<WslPickerDelegate> {}
impl picker::PickerDelegate for WslPickerDelegate {
type ListItem = ListItem;
fn name() -> &'static str {
"WSL-distor-picker"
}
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_nucleo::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 case = fuzzy_nucleo::Case::smart_if_uppercase_in(query);
self.matches = fuzzy_nucleo::match_strings(
&candidates,
query,
case,
fuzzy_nucleo::LengthPenalty::On,
100,
);
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 {
secondary,
distro: distro.string.to_string(),
});
}
}
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> {
let matched = self.matches.get(ix)?;
Some(
ListItem::new(ix)
.toggle_state(selected)
.inset(true)
.spacing(ui::ListItemSpacing::Sparse)
.child(
h_flex()
.flex_grow_1()
.gap_3()
.child(Icon::new(IconName::Linux))
.child(v_flex().child(HighlightedLabel::new(
matched.string.clone(),
matched.positions.clone(),
))),
),
)
}
}
pub(crate) struct WslOpenModal {
paths: Vec<PathBuf>,
create_new_window: bool,
picker: Entity<Picker<WslPickerDelegate>>,
_subscriptions: [Subscription; 2],
}
impl WslOpenModal {
pub fn new(
paths: Vec<PathBuf>,
create_new_window: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let delegate = WslPickerDelegate::new();
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
let selected = cx.subscribe_in(
&picker,
window,
|this, _, event: &WslDistroSelected, window, cx| {
this.confirm(&event.distro, event.secondary, window, cx);
},
);
let dismissed = cx.subscribe_in(
&picker,
window,
|this, _, _: &WslPickerDismissed, window, cx| {
this.cancel(&menu::Cancel, window, cx);
},
);
WslOpenModal {
paths,
create_new_window,
picker,
_subscriptions: [selected, dismissed],
}
}
fn confirm(
&mut self,
distro: &str,
secondary: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
let app_state = workspace::AppState::global(cx);
let connection_options = RemoteConnectionOptions::Wsl(WslConnectionOptions {
distro_name: distro.to_string(),
user: None,
});
let replace_current_window = match self.create_new_window {
true => secondary,
false => !secondary,
};
let open_mode = if replace_current_window {
workspace::OpenMode::Activate
} else {
workspace::OpenMode::NewWindow
};
let paths = self.paths.clone();
let open_options = workspace::OpenOptions {
requesting_window: window.window_handle().downcast::<MultiWorkspace>(),
open_mode,
..Default::default()
};
cx.emit(DismissEvent);
cx.spawn_in(window, async move |_, cx| {
open_remote_project(connection_options, paths, app_state, open_options, cx).await
})
.detach();
}
fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
}
impl ModalView for WslOpenModal {}
impl Focusable for WslOpenModal {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for WslOpenModal {}
impl Render for WslOpenModal {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
div()
.on_mouse_down_out(cx.listener(|_, _, _, cx| cx.emit(DismissEvent)))
.on_action(cx.listener(Self::cancel))
.elevation_3(cx)
.w(rems(34.))
.flex_1()
.overflow_hidden()
.child(self.picker.clone())
}
}