fix: Windows build warnings, key echo duplication, case-insensitive dedup, add WSL scanning
This commit is contained in:
@@ -39,6 +39,8 @@ pub struct ShellCandidate {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub conflict: Option<ShellScanConflict>,
|
pub conflict: Option<ShellScanConflict>,
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub wsl_distro: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
|||||||
+85
-11
@@ -1,7 +1,10 @@
|
|||||||
use super::{ConnectionProfile, ConnectionSource, ConnectionType, ShellCandidate, ShellScanConflict};
|
use super::{ConnectionProfile, ConnectionSource, ConnectionType, ShellCandidate, ShellScanConflict};
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
|
#[cfg(unix)]
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
#[cfg(not(unix))]
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::{PathBuf};
|
||||||
|
|
||||||
impl super::SshellConfig {
|
impl super::SshellConfig {
|
||||||
pub fn local_shell_candidates(&self) -> Vec<ShellCandidate> {
|
pub fn local_shell_candidates(&self) -> Vec<ShellCandidate> {
|
||||||
@@ -27,8 +30,38 @@ impl super::SshellConfig {
|
|||||||
name: base_name.to_string(),
|
name: base_name.to_string(),
|
||||||
path,
|
path,
|
||||||
conflict,
|
conflict,
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
wsl_distro: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let wsl_path = PathBuf::from("wsl.exe");
|
||||||
|
for distro in wsl_distributions() {
|
||||||
|
let name = format!("wsl-{distro}");
|
||||||
|
let command = format!("wsl.exe -d {distro}");
|
||||||
|
if self.connections.values().any(|profile| {
|
||||||
|
matches!(&profile.kind, ConnectionType::Shell { command: existing, .. } if existing == &command)
|
||||||
|
}) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let conflict = self
|
||||||
|
.connections
|
||||||
|
.contains_key(&format!("${name}"))
|
||||||
|
.then(|| ShellScanConflict {
|
||||||
|
name: name.clone(),
|
||||||
|
path: wsl_path.clone(),
|
||||||
|
});
|
||||||
|
out.push(ShellCandidate {
|
||||||
|
name,
|
||||||
|
path: wsl_path.clone(),
|
||||||
|
conflict,
|
||||||
|
wsl_distro: Some(distro),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +96,7 @@ impl super::SshellConfig {
|
|||||||
if candidate.conflict.is_some() || self.connections.contains_key(&key) {
|
if candidate.conflict.is_some() || self.connections.contains_key(&key) {
|
||||||
bail!("shell name conflict: {}", candidate.name);
|
bail!("shell name conflict: {}", candidate.name);
|
||||||
}
|
}
|
||||||
let command = candidate.path.to_string_lossy().to_string();
|
let (command, local_args) = make_shell_command_args(candidate);
|
||||||
if self.connections.values().any(|profile| {
|
if self.connections.values().any(|profile| {
|
||||||
matches!(&profile.kind, ConnectionType::Shell { command: existing, .. } if existing == &command)
|
matches!(&profile.kind, ConnectionType::Shell { command: existing, .. } if existing == &command)
|
||||||
}) {
|
}) {
|
||||||
@@ -82,7 +115,7 @@ impl super::SshellConfig {
|
|||||||
auth_ref: None,
|
auth_ref: None,
|
||||||
command,
|
command,
|
||||||
sync_args: Vec::new(),
|
sync_args: Vec::new(),
|
||||||
local_args: Vec::new(),
|
local_args,
|
||||||
sync: false,
|
sync: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -164,10 +197,19 @@ fn local_shell_paths() -> Vec<PathBuf> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn same_file_name(a: &Path, b: &Path) -> bool {
|
||||||
|
a.file_name().is_some_and(|a_name| {
|
||||||
|
b.file_name().is_some_and(|b_name| a_name.eq_ignore_ascii_case(b_name))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
fn same_file_name(a: &Path, b: &Path) -> bool {
|
fn same_file_name(a: &Path, b: &Path) -> bool {
|
||||||
a.file_name() == b.file_name()
|
a.file_name() == b.file_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
fn is_executable_file(path: &Path) -> bool {
|
fn is_executable_file(path: &Path) -> bool {
|
||||||
path.is_file() && is_executable(path)
|
path.is_file() && is_executable(path)
|
||||||
}
|
}
|
||||||
@@ -181,12 +223,44 @@ fn is_executable(path: &Path) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
fn is_executable(path: &Path) -> bool {
|
fn make_shell_command_args(candidate: &ShellCandidate) -> (String, Vec<String>) {
|
||||||
let exts = [
|
if let Some(distro) = &candidate.wsl_distro {
|
||||||
std::ffi::OsStr::new("exe"),
|
(
|
||||||
std::ffi::OsStr::new("cmd"),
|
"wsl.exe".to_string(),
|
||||||
std::ffi::OsStr::new("bat"),
|
vec!["-d".to_string(), distro.clone()],
|
||||||
std::ffi::OsStr::new("ps1"),
|
)
|
||||||
];
|
} else {
|
||||||
path.extension().is_some_and(|ext| exts.contains(&ext))
|
(candidate.path.to_string_lossy().to_string(), Vec::new())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn make_shell_command_args(candidate: &ShellCandidate) -> (String, Vec<String>) {
|
||||||
|
(candidate.path.to_string_lossy().to_string(), Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn wsl_distributions() -> Vec<String> {
|
||||||
|
use std::process::Command;
|
||||||
|
let output = match Command::new("wsl.exe").args(["-l", "-q"]).output() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return Vec::new(),
|
||||||
|
};
|
||||||
|
if !output.status.success() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let raw = output.stdout;
|
||||||
|
if raw.len() < 2 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let u16_iter = raw.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]]));
|
||||||
|
let decoded = String::from_utf16_lossy(&u16_iter.collect::<Vec<u16>>());
|
||||||
|
decoded
|
||||||
|
.lines()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.map(|line| line.replace('\0', ""))
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,7 @@ use crate::app::{App, Mode};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::{Hide, Show},
|
cursor::{Hide, Show},
|
||||||
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
|
event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
|
||||||
execute,
|
execute,
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||||
};
|
};
|
||||||
@@ -31,6 +31,7 @@ pub fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
if event::poll(Duration::from_millis(200))?
|
if event::poll(Duration::from_millis(200))?
|
||||||
&& let Event::Key(key) = event::read()?
|
&& let Event::Key(key) = event::read()?
|
||||||
|
&& key.kind == KeyEventKind::Press
|
||||||
{
|
{
|
||||||
handle_key(&mut app, key)?;
|
handle_key(&mut app, key)?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user