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
This commit is contained in:
@@ -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![
|
||||
|
||||
Reference in New Issue
Block a user