v0.4.2 : menu de completion Nushell (reedline) + correction des chemins Windows - remplace rustyline par reedline (menu Tab interactif : 1er choix surbrille, Tab navigue, Entree valide, Esc ferme, filtrage en direct, descriptions, couleurs du theme), tokenizer Windows preservant les backslashes pour cd/ls (shell_words mangeait les \), historique history.txt conserve, repli sans terminal, tests + man pages regeneres
release / linux (push) Failing after 24s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s

This commit is contained in:
2026-08-17 13:08:50 -04:00
parent e6e85e2a9e
commit 2c079b0ccd
11 changed files with 1198 additions and 371 deletions
-89
View File
@@ -235,61 +235,6 @@ pub fn kv_table(
out
}
/// Build the lines of the boxed Tab-completion menu from (candidate,
/// description) rows. Single column when no descriptions exist.
pub fn completion_box(rows: &[(String, String)]) -> Vec<String> {
let has_desc = rows.iter().any(|(_, d)| !d.is_empty());
let name_w = rows
.iter()
.map(|(n, _)| n.chars().count())
.max()
.unwrap_or(0)
.max("name".chars().count());
let desc_w = if has_desc {
rows.iter()
.map(|(_, d)| truncate(d, 40).chars().count())
.max()
.unwrap_or(0)
.max("description".chars().count())
} else {
0
};
let border = |left: char, mid: char, right: char| -> String {
let mut line = String::new();
line.push(left);
line.push_str(&"─".repeat(name_w + 2));
if has_desc {
line.push(mid);
line.push_str(&"─".repeat(desc_w + 2));
}
line.push(right);
line
};
let mut out = Vec::new();
out.push(border('╭', '┬', '╮'));
if has_desc {
out.push(format!(
"│ {} │ {} │",
pad("name", name_w),
pad("description", desc_w)
));
out.push(border('├', '┼', '┤'));
}
for (name, desc) in rows {
if has_desc {
out.push(format!(
"│ {} │ {} │",
pad(name, name_w),
pad(&truncate(desc, 40), desc_w)
));
} else {
out.push(format!("│ {} │", pad(name, name_w)));
}
}
out.push(border('╰', '┴', '╯'));
out
}
// ---------------------------------------------------------------------------
// Tables
// ---------------------------------------------------------------------------
@@ -457,40 +402,6 @@ fn pad(s: &str, width: usize) -> String {
mod tests {
use super::*;
#[test]
fn completion_box_with_descriptions_is_aligned() {
let rows = vec![
("list".to_string(), "list installed agents".to_string()),
("status".to_string(), "show agent state".to_string()),
];
let lines = completion_box(&rows);
assert_eq!(lines.len(), 6); // top, header, sep, 2 rows, bottom
assert!(lines[0].starts_with('╭') && lines[0].contains('┬'));
assert!(lines[0].ends_with('╮'));
assert!(lines[1].contains("name") && lines[1].contains("description"));
assert!(lines[2].starts_with('├'));
assert!(lines[5].starts_with('╰'));
let widths: Vec<usize> = lines.iter().map(|l| l.chars().count()).collect();
assert!(
widths.iter().all(|w| *w == widths[0]),
"unaligned: {lines:?}"
);
assert!(lines[3].contains("list") && lines[3].contains("list installed agents"));
}
#[test]
fn completion_box_without_descriptions_is_single_column() {
let rows = vec![
("src".to_string(), "".to_string()),
("am.exe".to_string(), "".to_string()),
];
let lines = completion_box(&rows);
assert_eq!(lines.len(), 4); // top, 2 rows, bottom
assert!(!lines.iter().any(|l| l.contains("description")));
let widths: Vec<usize> = lines.iter().map(|l| l.chars().count()).collect();
assert!(widths.iter().all(|w| *w == widths[0]));
}
#[test]
fn kv_table_is_aligned_with_and_without_colors() {
let rows = vec![