mirror of
https://github.com/wez/wezterm.git
synced 2024-12-26 14:54:16 +03:00
3e99390299
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
208 lines
6.2 KiB
Rust
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
|
|
},
|
|
]
|
|
);
|
|
}
|