1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-26 14:54:16 +03:00
wezterm/config/src/wsl.rs
Wez Furlong 3e99390299 LocalDomain: don't use global default_prog/default_cwd for WslDomains
They have their own versions of these options, and logically it doesn't
make sense to use the default_prog from the `local` domain with a wsl
domain.

refs: #1242
2022-01-08 16:07:22 -07:00

208 lines
6.2 KiB
Rust

use crate::*;
use std::collections::HashMap;
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct WslDomain {
pub name: String,
pub distribution: Option<String>,
pub username: Option<String>,
pub default_cwd: Option<PathBuf>,
pub default_prog: Option<Vec<String>>,
}
impl_lua_conversion!(WslDomain);
impl WslDomain {
pub fn default_domains() -> Vec<WslDomain> {
#[allow(unused_mut)]
let mut domains = vec![];
#[cfg(windows)]
if let Ok(distros) = WslDistro::load_distro_list() {
for distro in distros {
domains.push(WslDomain {
name: format!("WSL:{}", distro.name),
distribution: Some(distro.name.clone()),
username: None,
default_cwd: None,
default_prog: None,
});
}
}
domains
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WslDistro {
pub name: String,
pub state: String,
pub version: String,
pub is_default: bool,
}
impl WslDistro {
pub fn load_distro_list() -> anyhow::Result<Vec<Self>> {
#[cfg(windows)]
use std::os::windows::process::CommandExt;
let mut cmd = std::process::Command::new("wsl.exe");
cmd.arg("-l");
cmd.arg("-v");
#[cfg(windows)]
cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
let output = cmd.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::ensure!(
output.status.success(),
"wsl -l command invocation failed: {}",
stderr
);
/// Ungh: https://github.com/microsoft/WSL/issues/4456
fn utf16_to_utf8(bytes: &[u8]) -> anyhow::Result<String> {
if bytes.len() % 2 != 0 {
anyhow::bail!("input data has odd length, cannot be utf16");
}
// This is "safe" because we checked that the length seems reasonable,
// and our new slice is within those same bounds.
let wide: &[u16] = unsafe {
std::slice::from_raw_parts(bytes.as_ptr() as *const u16, bytes.len() / 2)
};
String::from_utf16(wide).map_err(|_| anyhow!("wsl -l -v output is not valid utf16"))
}
let wsl_list = utf16_to_utf8(&output.stdout)?.replace("\r\n", "\n");
Ok(parse_wsl_distro_list(&wsl_list))
}
}
/// This function parses the `wsl -l -v` output.
/// It tries to be robust in the face of future changes
/// by looking at the tabulated output headers, determining
/// where the columns are and then collecting the information
/// into a hashmap and then grokking from there.
#[allow(dead_code)]
fn parse_wsl_distro_list(output: &str) -> Vec<WslDistro> {
let lines = output.lines().collect::<Vec<_>>();
// Determine where the field columns start
let mut field_starts = vec![];
{
let mut last_char = ' ';
for (idx, c) in lines[0].char_indices() {
if last_char == ' ' && c != ' ' {
field_starts.push(idx);
}
last_char = c;
}
}
fn field_slice(s: &str, start: usize, end: Option<usize>) -> &str {
if let Some(end) = end {
&s[start..end]
} else {
&s[start..]
}
}
fn opt_field_slice(s: &str, start: usize, end: Option<usize>) -> Option<&str> {
if let Some(end) = end {
s.get(start..end)
} else {
s.get(start..)
}
}
// Now build up a name -> column position map
let mut field_map = HashMap::new();
{
let mut iter = field_starts.into_iter().peekable();
while let Some(start_idx) = iter.next() {
let end_idx = iter.peek().copied();
let label = field_slice(&lines[0], start_idx, end_idx).trim();
field_map.insert(label, (start_idx, end_idx));
}
}
let mut result = vec![];
// and now process the output rows
for line in lines.iter().skip(1) {
if line.is_empty() {
continue;
}
let is_default = line.starts_with("*");
let mut fields = HashMap::new();
for (label, (start_idx, end_idx)) in field_map.iter() {
if let Some(value) = opt_field_slice(line, *start_idx, *end_idx) {
fields.insert(*label, value.trim().to_string());
} else {
return result;
}
}
result.push(WslDistro {
name: fields.get("NAME").cloned().unwrap_or_default(),
state: fields.get("STATE").cloned().unwrap_or_default(),
version: fields.get("VERSION").cloned().unwrap_or_default(),
is_default,
});
}
result
}
#[cfg(test)]
#[test]
fn test_parse_wsl_distro_list() {
let data = " NAME STATE VERSION
* Arch Running 2
docker-desktop-data Stopped 2
docker-desktop Stopped 2
Ubuntu Stopped 2
nvim Stopped 2";
assert_eq!(
parse_wsl_distro_list(data),
vec![
WslDistro {
name: "Arch".to_string(),
state: "Running".to_string(),
version: "2".to_string(),
is_default: true
},
WslDistro {
name: "docker-desktop-data".to_string(),
state: "Stopped".to_string(),
version: "2".to_string(),
is_default: false
},
WslDistro {
name: "docker-desktop".to_string(),
state: "Stopped".to_string(),
version: "2".to_string(),
is_default: false
},
WslDistro {
name: "Ubuntu".to_string(),
state: "Stopped".to_string(),
version: "2".to_string(),
is_default: false
},
WslDistro {
name: "nvim".to_string(),
state: "Stopped".to_string(),
version: "2".to_string(),
is_default: false
},
]
);
}