v0.2.14 : barres verticales des cadres aux couleurs du theme + commande theme dans le shell (theme liste les themes, theme <name> change a chaud, completion et menu synchronises)
This commit is contained in:
+11
-4
@@ -8,6 +8,7 @@ use crate::output::Logger;
|
||||
use crate::state::StateStore;
|
||||
use crate::theme::{self, Theme};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::cell::Cell;
|
||||
use std::io::{IsTerminal, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -20,8 +21,9 @@ pub struct App {
|
||||
pub catalog: Catalog,
|
||||
pub state: StateStore,
|
||||
pub log: Logger,
|
||||
/// Active output color theme.
|
||||
pub theme: &'static Theme,
|
||||
/// Active output color theme (switchable at runtime with the REPL
|
||||
/// 'theme <name>' command).
|
||||
theme: Cell<&'static Theme>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -90,7 +92,7 @@ impl App {
|
||||
catalog,
|
||||
state,
|
||||
log,
|
||||
theme,
|
||||
theme: Cell::new(theme),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -111,7 +113,12 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> &'static Theme {
|
||||
self.theme
|
||||
self.theme.get()
|
||||
}
|
||||
|
||||
/// Switch the active theme (used by the REPL 'theme <name>' command).
|
||||
pub fn set_theme(&self, theme: &'static Theme) {
|
||||
self.theme.set(theme);
|
||||
}
|
||||
|
||||
/// Ask the user for confirmation on stderr. Accepts y/yes/o/oui.
|
||||
|
||||
+26
-2
@@ -508,6 +508,23 @@ pub static HELP_SPECS: &[HelpSpec] = &[
|
||||
HelpExample { desc: "Show version and build information.", code: "version" },
|
||||
],
|
||||
},
|
||||
HelpSpec {
|
||||
name: "theme",
|
||||
category: "Shell",
|
||||
usage: "theme {flags} [name]",
|
||||
about: "Show the color themes, or switch the active one inside the shell.",
|
||||
search_terms: &["color", "colors", "palette"],
|
||||
flags: &[],
|
||||
subcommands: &[],
|
||||
parameters: &[
|
||||
HelpParam { name: "name", typ: "string", desc: "Theme to switch to (default, ocean, sunset, forest, dracula, mono); omit to list them" },
|
||||
],
|
||||
io: None,
|
||||
examples: &[
|
||||
HelpExample { desc: "List the available themes.", code: "theme" },
|
||||
HelpExample { desc: "Switch to the ocean theme.", code: "theme ocean" },
|
||||
],
|
||||
},
|
||||
HelpSpec {
|
||||
name: "ls",
|
||||
category: "Shell",
|
||||
@@ -751,8 +768,15 @@ fn io_table(input: &str, output: &str, theme: &Theme, color: bool) -> String {
|
||||
line
|
||||
}
|
||||
};
|
||||
let bar = |s: &str| -> String {
|
||||
if color {
|
||||
theme.frame(s)
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
};
|
||||
let row = |cells: &[&str], bold: bool| -> String {
|
||||
let mut line = String::from("│");
|
||||
let mut line = bar("│");
|
||||
for (i, c) in cells.iter().enumerate() {
|
||||
line.push(' ');
|
||||
let text = if bold {
|
||||
@@ -767,7 +791,7 @@ fn io_table(input: &str, output: &str, theme: &Theme, color: bool) -> String {
|
||||
line.push_str(&text);
|
||||
line.push_str(&" ".repeat(widths[i] - c.chars().count()));
|
||||
line.push(' ');
|
||||
line.push('│');
|
||||
line.push_str(&bar("│"));
|
||||
}
|
||||
line
|
||||
};
|
||||
|
||||
+6
-1
@@ -213,6 +213,11 @@ pub fn kv_table(
|
||||
s.to_string()
|
||||
}
|
||||
};
|
||||
let bar = if color {
|
||||
theme.frame("│")
|
||||
} else {
|
||||
"│".to_string()
|
||||
};
|
||||
let mut out = String::new();
|
||||
out.push_str(&border('╭', '┬', '╮'));
|
||||
out.push('\n');
|
||||
@@ -220,7 +225,7 @@ pub fn kv_table(
|
||||
let key = paint(k, color, crate::theme::Theme::key);
|
||||
let val = paint(v, color, crate::theme::Theme::val);
|
||||
out.push_str(&format!(
|
||||
"│ {} │ {} │\n",
|
||||
"{bar} {} {bar} {} {bar}\n",
|
||||
pad(key.as_str(), key_w),
|
||||
pad(val.as_str(), val_w)
|
||||
));
|
||||
|
||||
+107
-16
@@ -50,8 +50,9 @@ pub struct AmCompleter {
|
||||
/// Short descriptions shown next to candidates in the Tab menu
|
||||
/// (Nushell-style).
|
||||
descriptions: BTreeMap<String, String>,
|
||||
/// Active color theme (used by the boxed menu highlight).
|
||||
theme: &'static crate::theme::Theme,
|
||||
/// Active color theme (used by the boxed menu highlight; follows the
|
||||
/// app theme when it is switched with the REPL 'theme <name>' command).
|
||||
theme: std::cell::Cell<&'static crate::theme::Theme>,
|
||||
}
|
||||
|
||||
/// One-line descriptions for the Tab completion menu.
|
||||
@@ -75,6 +76,7 @@ const COMMAND_DESCRIPTIONS: &[(&str, &str)] = &[
|
||||
("export", "backup config + state"),
|
||||
("import", "restore a backup"),
|
||||
("shell", "show or switch the system shell"),
|
||||
("theme", "show or switch the color theme"),
|
||||
("ls", "list files and directories (Nushell-style)"),
|
||||
("dir", "list files and directories (same as ls)"),
|
||||
("cd", "change directory — Tab completes folders"),
|
||||
@@ -88,12 +90,17 @@ const COMMAND_DESCRIPTIONS: &[(&str, &str)] = &[
|
||||
|
||||
impl AmCompleter {
|
||||
pub fn new(app: &App) -> Self {
|
||||
let mut completer =
|
||||
let completer =
|
||||
Self::from_catalog(&app.catalog, &app.config.aliases, &app.config.groups);
|
||||
completer.theme = app.theme();
|
||||
completer.theme.set(app.theme());
|
||||
completer
|
||||
}
|
||||
|
||||
/// Point the menu at a new theme (after 'theme <name>' in the REPL).
|
||||
pub fn set_theme(&self, theme: &'static crate::theme::Theme) {
|
||||
self.theme.set(theme);
|
||||
}
|
||||
|
||||
pub fn from_catalog(
|
||||
catalog: &crate::catalog::Catalog,
|
||||
aliases: &BTreeMap<String, String>,
|
||||
@@ -126,12 +133,12 @@ impl AmCompleter {
|
||||
descriptions.insert(a.name.clone(), truncate_desc(&desc));
|
||||
}
|
||||
Self {
|
||||
theme: crate::theme::default_theme(),
|
||||
theme: std::cell::Cell::new(crate::theme::default_theme()),
|
||||
commands: vec![
|
||||
"list", "status", "search", "info", "install", "uninstall", "update",
|
||||
"start", "stop", "restart", "run", "doctor", "config", "completion",
|
||||
"self-update", "self-uninstall", "export", "import", "shell", "ls", "dir",
|
||||
"cd", "ps", "where", "get", "help", "version", "exit",
|
||||
"self-update", "self-uninstall", "export", "import", "shell", "theme",
|
||||
"ls", "dir", "cd", "ps", "where", "get", "help", "version", "exit",
|
||||
],
|
||||
config_sub: vec!["show", "path", "edit", "validate", "add"],
|
||||
shells: vec!["bash", "zsh", "fish", "powershell", "elvish"],
|
||||
@@ -178,6 +185,12 @@ impl AmCompleter {
|
||||
"shell" => {
|
||||
pool = self.gateway_shells.iter().map(|s| s.to_string()).collect();
|
||||
}
|
||||
"theme" => {
|
||||
pool = crate::theme::names()
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
}
|
||||
"cd" | "chdir" => {
|
||||
// Complete directories only — dive one level per Tab.
|
||||
let cwd = current_dir();
|
||||
@@ -340,18 +353,21 @@ impl Highlighter for AmCompleter {
|
||||
use std::borrow::Cow;
|
||||
let _ = completion;
|
||||
match candidate.chars().next() {
|
||||
Some('╭') | Some('╰') | Some('├') => Cow::Owned(self.theme.frame(candidate)),
|
||||
Some('╭') | Some('╰') | Some('├') => Cow::Owned(self.theme.get().frame(candidate)),
|
||||
Some('│') => {
|
||||
let cells: Vec<&str> = candidate.split('│').collect();
|
||||
if cells.len() >= 4 {
|
||||
if cells[1].trim() == "name" && cells[2].trim() == "description" {
|
||||
Cow::Owned(self.theme.hdr(candidate))
|
||||
Cow::Owned(self.theme.get().hdr(candidate))
|
||||
} else {
|
||||
let bar = self.theme.get().frame("│");
|
||||
Cow::Owned(format!(
|
||||
"│{}│{}│{}",
|
||||
self.theme.acc(cells[1]),
|
||||
"{}{}{}{}{}",
|
||||
bar,
|
||||
self.theme.get().acc(cells[1]),
|
||||
bar,
|
||||
cells[2],
|
||||
cells[3]
|
||||
bar
|
||||
))
|
||||
}
|
||||
} else {
|
||||
@@ -459,8 +475,6 @@ pub fn banner_box(
|
||||
) -> String {
|
||||
let w = BOX_WIDTH;
|
||||
let mut rows: Vec<String> = Vec::new();
|
||||
let inner = |content: String| format!("│ {} │", pad_visible(content, w));
|
||||
let blank = || inner(" ".to_string());
|
||||
let frame = |s: &str| -> String {
|
||||
if color {
|
||||
theme.frame(s)
|
||||
@@ -468,6 +482,15 @@ pub fn banner_box(
|
||||
s.to_string()
|
||||
}
|
||||
};
|
||||
let inner = |content: String| {
|
||||
format!(
|
||||
"{} {} {}",
|
||||
frame("│"),
|
||||
pad_visible(content, w),
|
||||
frame("│")
|
||||
)
|
||||
};
|
||||
let blank = || inner(" ".to_string());
|
||||
|
||||
// Top border with the centered header (title in the theme header color,
|
||||
// border in the theme frame color).
|
||||
@@ -574,7 +597,7 @@ pub fn banner_box(
|
||||
.to_string(),
|
||||
));
|
||||
rows.push(inner(
|
||||
" shell ls · cd · ps · where · get · shell · help · version · exit".to_string(),
|
||||
" shell ls · cd · ps · where · get · shell · theme · help · version · exit".to_string(),
|
||||
));
|
||||
|
||||
// Stats footer.
|
||||
@@ -894,6 +917,11 @@ fn run_with_editor(app: &App, session: &mut ShellSession) -> Result<i32> {
|
||||
let t = Instant::now();
|
||||
let result = handle_input(app, session, &mut last, &trimmed);
|
||||
last_duration = Some(t.elapsed());
|
||||
// 'theme <name>' may have switched the app theme: repoint the
|
||||
// completion menu at the new palette.
|
||||
if let Some(h) = rl.helper_mut() {
|
||||
h.set_theme(app.theme());
|
||||
}
|
||||
match result {
|
||||
Ok(true) => break,
|
||||
Ok(false) => {}
|
||||
@@ -958,6 +986,10 @@ fn handle_input(
|
||||
crate::help::print_version(app);
|
||||
return Ok(false);
|
||||
}
|
||||
"theme" | "/theme" => {
|
||||
theme_command(app, None);
|
||||
return Ok(false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Slash commands (Hermes-style).
|
||||
@@ -968,6 +1000,7 @@ fn handle_input(
|
||||
};
|
||||
match cmd {
|
||||
"shell" => shell_command(session, arg),
|
||||
"theme" => theme_command(app, arg),
|
||||
"help" => match arg {
|
||||
Some(topic) => {
|
||||
if let Err(e) = crate::help::print_command(app, topic) {
|
||||
@@ -1040,6 +1073,11 @@ fn handle_line(
|
||||
crate::help::print_version(app);
|
||||
return Ok(false);
|
||||
}
|
||||
// theme | theme <name>: list the themes or switch the active one.
|
||||
if cmd == "theme" {
|
||||
theme_command(app, rest.first().map(|s| s.as_str()));
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Session directory change: kept in the am process, not a child shell.
|
||||
if matches!(cmd, "cd" | "chdir") {
|
||||
@@ -1205,7 +1243,7 @@ fn handle_line(
|
||||
fn is_shell_command(word: &str) -> bool {
|
||||
matches!(
|
||||
word,
|
||||
"ls" | "dir" | "ps" | "where" | "get" | "cd" | "shell" | "help" | "version" | "exit"
|
||||
"ls" | "dir" | "ps" | "where" | "get" | "cd" | "shell" | "theme" | "help" | "version" | "exit"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1368,6 +1406,59 @@ fn shell_command(session: &mut ShellSession, arg: Option<&str>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 'theme' in the REPL: list the themes, or switch with 'theme <name>'.
|
||||
fn theme_command(app: &App, arg: Option<&str>) {
|
||||
match arg.map(str::trim).filter(|s| !s.is_empty()) {
|
||||
None => print_theme_status(app),
|
||||
Some(name) => match crate::theme::find(name) {
|
||||
Some(t) => {
|
||||
app.set_theme(t);
|
||||
let color = app.color();
|
||||
println!(
|
||||
"{} {}",
|
||||
if color { t.hdr("theme:") } else { "theme:".to_string() },
|
||||
format!("{} — {}", t.acc(t.name), t.title)
|
||||
);
|
||||
}
|
||||
None => app.log.error(format!(
|
||||
"unknown theme '{name}' — available: {}",
|
||||
crate::theme::names().join(", ")
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// List the available themes, highlighting the active one.
|
||||
fn print_theme_status(app: &App) {
|
||||
let theme = app.theme();
|
||||
let color = app.color();
|
||||
let hdr = |s: &str| -> String {
|
||||
if color {
|
||||
theme.hdr(s)
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
};
|
||||
println!("{}", hdr("Theme"));
|
||||
println!(
|
||||
" ◉ {} — {}",
|
||||
theme.acc(theme.name),
|
||||
theme.title
|
||||
);
|
||||
let others: Vec<&str> = crate::theme::THEMES
|
||||
.iter()
|
||||
.map(|t| t.name)
|
||||
.filter(|n| *n != theme.name)
|
||||
.collect();
|
||||
println!(" {}", theme.dimmed(&format!("⇄ {}", others.join(" · "))));
|
||||
println!(
|
||||
" {}",
|
||||
theme.dimmed(
|
||||
"'theme <name>' to switch · '--theme <name>' or settings.theme to change the default"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn print_shell_status(session: &ShellSession) {
|
||||
let others: Vec<&str> = session
|
||||
.available
|
||||
|
||||
+11
-2
@@ -153,8 +153,17 @@ impl DataTable {
|
||||
line.push(right);
|
||||
line
|
||||
};
|
||||
let bar = |s: &str| -> String {
|
||||
if !color {
|
||||
return s.to_string();
|
||||
}
|
||||
match theme {
|
||||
Some(t) => t.frame(s),
|
||||
None => s.to_string(),
|
||||
}
|
||||
};
|
||||
let row_line = |row: &[String], header: bool| -> String {
|
||||
let mut line = String::from("│");
|
||||
let mut line = bar("│");
|
||||
for (i, c) in row.iter().enumerate().take(all_widths.len()) {
|
||||
line.push(' ');
|
||||
let cell = fit_cell(c, all_widths[i]);
|
||||
@@ -168,7 +177,7 @@ impl DataTable {
|
||||
};
|
||||
line.push_str(&rendered);
|
||||
line.push(' ');
|
||||
line.push('│');
|
||||
line.push_str(&bar("│"));
|
||||
}
|
||||
line
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user