diff --git a/crates/tui/src/commands/mod.rs b/crates/tui/src/commands/mod.rs index 25827988..e1fdd1c7 100644 --- a/crates/tui/src/commands/mod.rs +++ b/crates/tui/src/commands/mod.rs @@ -142,37 +142,37 @@ pub const COMMANDS: &[CommandInfo] = &[ // Core commands CommandInfo { name: "anchor", - aliases: &[], + aliases: &["maodian"], usage: "/anchor | /anchor list | /anchor remove ", description_id: MessageId::CmdAnchorDescription, }, CommandInfo { name: "help", - aliases: &["?"], + aliases: &["?", "bangzhu", "帮助"], usage: "/help [command]", description_id: MessageId::CmdHelpDescription, }, CommandInfo { name: "clear", - aliases: &[], + aliases: &["qingping"], usage: "/clear", description_id: MessageId::CmdClearDescription, }, CommandInfo { name: "exit", - aliases: &["quit", "q"], + aliases: &["quit", "q", "tuichu"], usage: "/exit", description_id: MessageId::CmdExitDescription, }, CommandInfo { name: "model", - aliases: &[], + aliases: &["moxing"], usage: "/model [name]", description_id: MessageId::CmdModelDescription, }, CommandInfo { name: "models", - aliases: &[], + aliases: &["moxingliebiao"], usage: "/models", description_id: MessageId::CmdModelsDescription, }, @@ -196,25 +196,25 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "hooks", - aliases: &["hook"], + aliases: &["hook", "gouzi"], usage: "/hooks [list|events]", description_id: MessageId::CmdHooksDescription, }, CommandInfo { name: "subagents", - aliases: &["agents"], + aliases: &["agents", "zhinengti"], usage: "/subagents", description_id: MessageId::CmdSubagentsDescription, }, CommandInfo { name: "agent", - aliases: &[], + aliases: &["daili"], usage: "/agent [N] ", description_id: MessageId::CmdAgentDescription, }, CommandInfo { name: "links", - aliases: &["dashboard", "api"], + aliases: &["dashboard", "api", "lianjie"], usage: "/links", description_id: MessageId::CmdLinksDescription, }, @@ -226,7 +226,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "home", - aliases: &["stats", "overview"], + aliases: &["stats", "overview", "zhuye", "shouye"], usage: "/home", description_id: MessageId::CmdHomeDescription, }, @@ -244,7 +244,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "attach", - aliases: &["image", "media"], + aliases: &["image", "media", "fujian"], usage: "/attach ", description_id: MessageId::CmdAttachDescription, }, @@ -256,7 +256,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "jobs", - aliases: &["job"], + aliases: &["job", "zuoye"], usage: "/jobs [list|show |poll |wait |stdin |cancel ]", description_id: MessageId::CmdJobsDescription, }, @@ -275,7 +275,7 @@ pub const COMMANDS: &[CommandInfo] = &[ // Session commands CommandInfo { name: "rename", - aliases: &[], + aliases: &["gaiming", "chongmingming"], usage: "/rename ", description_id: MessageId::CmdRenameDescription, }, @@ -293,13 +293,13 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "load", - aliases: &[], + aliases: &["jiazai"], usage: "/load [path]", description_id: MessageId::CmdLoadDescription, }, CommandInfo { name: "compact", - aliases: &[], + aliases: &["yasuo"], usage: "/compact", description_id: MessageId::CmdCompactDescription, }, @@ -317,7 +317,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "cycles", - aliases: &[], + aliases: &["zhouqi"], usage: "/cycles", description_id: MessageId::CmdCyclesDescription, }, @@ -335,7 +335,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "export", - aliases: &[], + aliases: &["daochu"], usage: "/export [path]", description_id: MessageId::CmdExportDescription, }, @@ -348,7 +348,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "mode", - aliases: &[], + aliases: &["jihua", "zidong"], usage: "/mode [agent|plan|yolo|1|2|3]", description_id: MessageId::CmdModeDescription, }, @@ -366,7 +366,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "trust", - aliases: &[], + aliases: &["xinren"], usage: "/trust [on|off|add |remove |list]", description_id: MessageId::CmdTrustDescription, }, @@ -391,7 +391,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "system", - aliases: &[], + aliases: &["xitong"], usage: "/system", description_id: MessageId::CmdSystemDescription, }, @@ -421,7 +421,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "retry", - aliases: &[], + aliases: &["chongshi"], usage: "/retry", description_id: MessageId::CmdRetryDescription, }, @@ -445,7 +445,7 @@ pub const COMMANDS: &[CommandInfo] = &[ }, CommandInfo { name: "goal", - aliases: &[], + aliases: &["mubiao"], usage: "/goal [objective] [budget: N]", description_id: MessageId::CmdGoalDescription, }, @@ -470,19 +470,19 @@ pub const COMMANDS: &[CommandInfo] = &[ // Skills commands CommandInfo { name: "skills", - aliases: &[], + aliases: &["jinengliebiao"], usage: "/skills [--remote|sync|]", description_id: MessageId::CmdSkillsDescription, }, CommandInfo { name: "skill", - aliases: &[], + aliases: &["jineng"], usage: "/skill |update |uninstall |trust >", description_id: MessageId::CmdSkillDescription, }, CommandInfo { name: "review", - aliases: &[], + aliases: &["shencha"], usage: "/review ", description_id: MessageId::CmdReviewDescription, }, @@ -495,7 +495,7 @@ pub const COMMANDS: &[CommandInfo] = &[ // RLM command CommandInfo { name: "rlm", - aliases: &["recursive"], + aliases: &["recursive", "digui"], usage: "/rlm [N] ", description_id: MessageId::CmdRlmDescription, }, @@ -509,7 +509,7 @@ pub const COMMANDS: &[CommandInfo] = &[ // Profile switching (#390) CommandInfo { name: "profile", - aliases: &[], + aliases: &["dangan"], usage: "/profile ", description_id: MessageId::CmdHelpDescription, // reuse for now }, @@ -537,40 +537,40 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult { // Match command or alias match command { // Core commands - "anchor" => anchor::anchor(app, arg), - "help" | "?" => core::help(app, arg), - "clear" => core::clear(app), - "exit" | "quit" | "q" => core::exit(), - "model" => core::model(app, arg), - "models" => core::models(app), + "anchor" | "maodian" => anchor::anchor(app, arg), + "help" | "?" | "bangzhu" | "帮助" => core::help(app, arg), + "clear" | "qingping" => core::clear(app), + "exit" | "quit" | "q" | "tuichu" => core::exit(), + "model" | "moxing" => core::model(app, arg), + "models" | "moxingliebiao" => core::models(app), "provider" => provider::provider(app, arg), "queue" | "queued" => queue::queue(app, arg), "stash" | "park" => stash::stash(app, arg), - "hooks" | "hook" => hooks::hooks(app, arg), - "subagents" | "agents" => core::subagents(app), - "agent" => agent(app, arg), - "links" | "dashboard" | "api" => core::deepseek_links(app), + "hooks" | "hook" | "gouzi" => hooks::hooks(app, arg), + "subagents" | "agents" | "zhinengti" => core::subagents(app), + "agent" | "daili" => agent(app, arg), + "links" | "dashboard" | "api" | "lianjie" => core::deepseek_links(app), "feedback" => feedback::feedback(app, arg), - "home" | "stats" | "overview" => core::home_dashboard(app), + "home" | "stats" | "overview" | "zhuye" | "shouye" => core::home_dashboard(app), "note" => note::note(app, arg), "memory" => memory::memory(app, arg), - "attach" | "image" | "media" => attachment::attach(app, arg), + "attach" | "image" | "media" | "fujian" => attachment::attach(app, arg), "task" | "tasks" => task::task(app, arg), - "jobs" | "job" => jobs::jobs(app, arg), + "jobs" | "job" | "zuoye" => jobs::jobs(app, arg), "mcp" => mcp::mcp(app, arg), "network" => network::network(app, arg), // Session commands - "rename" => rename::rename(app, arg), + "rename" | "gaiming" | "chongmingming" => rename::rename(app, arg), "save" => session::save(app, arg), "sessions" | "resume" => session::sessions(app, arg), - "load" => session::load(app, arg), - "compact" => session::compact(app), "relay" | "batonpass" | "接力" => relay(app, arg), - "cycles" => cycle::list_cycles(app), + "load" | "jiazai" => session::load(app, arg), + "compact" | "yasuo" => session::compact(app), + "cycles" | "zhouqi" => cycle::list_cycles(app), "cycle" => cycle::show_cycle(app, arg), "recall" => cycle::recall_archive(app, arg), - "export" => session::export(app, arg), + "export" | "daochu" => session::export(app, arg), // Config commands "config" => config::config_command(app, arg), @@ -578,9 +578,11 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult { "status" => status::status(app), "statusline" => config::status_line(app), "mode" => config::mode(app, arg), + "jihua" => config::mode(app, Some("plan")), + "zidong" => config::mode(app, Some("yolo")), "theme" => config::theme(app, arg), "verbose" => config::verbose(app, arg), - "trust" => config::trust(app, arg), + "trust" | "xinren" => config::trust(app, arg), "logout" => config::logout(app), // Debug commands @@ -591,7 +593,7 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult { // ChangeLog command "change" => change::change(app, arg), - "system" => debug::system_prompt(app), + "system" | "xitong" => debug::system_prompt(app), "context" | "ctx" => debug::context(app), "edit" => debug::edit(app), "diff" => debug::diff(app), @@ -610,25 +612,25 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult { result } } - "retry" => debug::retry(app), + "retry" | "chongshi" => debug::retry(app), // Project commands "init" => init::init(app), "lsp" => config::lsp_command(app, arg), "share" => share::share(app, arg), - "goal" => goal::goal(app, arg), + "goal" | "mubiao" => goal::goal(app, arg), // Skills commands "skills" => skills::list_skills(app, arg), - "skill" => skills::run_skill(app, arg), - "review" => review::review(app, arg), + "skill" | "jineng" => skills::run_skill(app, arg), + "review" | "shencha" => review::review(app, arg), "restore" => restore::restore(app, arg), // Profile switch (#390) - "profile" => core::profile_switch(app, arg), + "profile" | "dangan" => core::profile_switch(app, arg), // RLM command - "rlm" | "recursive" => rlm(app, arg), + "rlm" | "recursive" | "digui" => rlm(app, arg), // Legacy command migrations (kept out of registry/autocomplete intentionally). "set" => CommandResult::error( @@ -1096,7 +1098,7 @@ mod tests { .iter() .find(|cmd| cmd.name == "links") .expect("links command should exist"); - assert_eq!(links.aliases, &["dashboard", "api"]); + assert_eq!(links.aliases, &["dashboard", "api", "lianjie"]); } #[test] @@ -1281,7 +1283,7 @@ mod tests { #[test] fn execute_links_and_aliases_return_links_message() { let mut app = create_test_app(); - for cmd in ["/links", "/dashboard", "/api"] { + for cmd in ["/links", "/dashboard", "/api", "/lianjie"] { let result = execute(cmd, &mut app); let msg = result.message.expect("links commands should return text"); assert!(msg.contains("https://platform.deepseek.com")); diff --git a/crates/tui/src/modules/text.rs b/crates/tui/src/modules/text.rs index 66553112..35d34232 100644 --- a/crates/tui/src/modules/text.rs +++ b/crates/tui/src/modules/text.rs @@ -448,7 +448,7 @@ fn handle_command_deepseek( } match trimmed { - "/help" => { + "/help" | "/?" | "/bangzhu" | "/帮助" => { print_help(); } "/history" => { @@ -488,7 +488,7 @@ fn handle_command_official( } match trimmed { - "/help" => { + "/help" | "/?" | "/bangzhu" | "/帮助" => { print_help(); } "/history" => { @@ -668,6 +668,9 @@ fn create_editor() -> Result> { let helper = CommandCompleter { commands: vec![ "/help".to_string(), + "/?".to_string(), + "/bangzhu".to_string(), + "/帮助".to_string(), "/clear".to_string(), "/history".to_string(), "/stats".to_string(), diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index d836e851..54e24b12 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -2521,11 +2521,13 @@ fn apply_slash_menu_selection_appends_space_for_arg_commands() { name: "/model".to_string(), description: String::new(), is_skill: false, + alias_hint: None, }, crate::tui::widgets::SlashMenuEntry { name: "/settings".to_string(), description: String::new(), is_skill: false, + alias_hint: None, }, ]; app.slash_menu_selected = 0; @@ -2553,6 +2555,7 @@ fn apply_slash_menu_selection_uses_skill_command_form() { name: "/skill search-files".to_string(), description: "Search files".to_string(), is_skill: true, + alias_hint: None, }]; assert!(apply_slash_menu_selection(&mut app, &entries, true)); diff --git a/crates/tui/src/tui/widgets/mod.rs b/crates/tui/src/tui/widgets/mod.rs index c1a237db..7717ffa8 100644 --- a/crates/tui/src/tui/widgets/mod.rs +++ b/crates/tui/src/tui/widgets/mod.rs @@ -846,8 +846,24 @@ impl Renderable for ComposerWidget<'_> { }; let menu_bottom = (menu_top + menu_visible_rows).min(menu_total); - // Label column width for two-column layout (name + description) - let label_width = 22.min(content_width.saturating_sub(4)); + // Label column width — grows to fit the widest visible name + // (including alias hint like " or /bangzhu") but stays bounded. + let label_width = self + .slash_menu_entries + .iter() + .take(menu_bottom) + .skip(menu_top) + .map(|e| { + if let Some(ref hint) = e.alias_hint { + format!("{} or /{}", e.name, hint).width() + } else { + e.name.width() + } + }) + .max() + .unwrap_or(22) + .min(content_width.saturating_sub(4)) + .max(8); for (idx, entry) in self .slash_menu_entries .iter() @@ -881,12 +897,20 @@ impl Renderable for ComposerWidget<'_> { Style::default().fg(palette::TEXT_DIM) }; + // Build display name: canonical name, with "or /alias" hint + // when the user typed via a pinyin alias. + let display_name = if let Some(ref hint) = entry.alias_hint { + format!("{} or /{}", entry.name, hint) + } else { + entry.name.clone() + }; + let name_display = { - let display_width: usize = entry.name.width(); + let display_width: usize = display_name.width(); if display_width > label_width { let mut s = String::new(); let mut w = 0; - for ch in entry.name.chars() { + for ch in display_name.chars() { let cw = ch.width().unwrap_or(0); if w + cw + 1 > label_width { break; @@ -902,7 +926,7 @@ impl Renderable for ComposerWidget<'_> { s } else { // pad to label_width display cols - let mut s = entry.name.clone(); + let mut s = display_name; while s.width() < label_width { s.push(' '); } @@ -2010,6 +2034,9 @@ pub(crate) struct SlashMenuEntry { pub name: String, pub description: String, pub is_skill: bool, + /// Matching pinyin/alias prefix hint, e.g. when user types `/bang` and + /// the command `/help` matches via alias `bangzhu`. + pub alias_hint: Option, } pub(crate) fn slash_completion_hints( @@ -2035,17 +2062,42 @@ pub(crate) fn slash_completion_hints( // built-in ones from the static registry and use a generic label for // user-defined commands. if completing_skill_arg.is_none() { + let prefix_lower = prefix.to_ascii_lowercase(); for name in commands::all_command_names_matching(prefix, workspace) { let command_key = name.trim_start_matches('/'); - let description = if let Some(info) = commands::get_command_info(command_key) { - info.description_for(locale).to_string() + let (description, alias_hint) = if let Some(info) = commands::get_command_info(command_key) { + // Detect matching alias: if the user typed via pinyin rather + // than the canonical name, record which alias matched. + let hint = if !command_key.to_ascii_lowercase().starts_with(&prefix_lower) { + info.aliases + .iter() + .find(|a| a.to_ascii_lowercase().starts_with(&prefix_lower)) + .map(|a| a.to_string()) + } else { + None + }; + let desc = if info.aliases.is_empty() { + info.description_for(locale).to_string() + } else { + format!( + "{} (aliases: {})", + info.description_for(locale), + info.aliases + .iter() + .map(|a| format!("/{a}")) + .collect::>() + .join(", ") + ) + }; + (desc, hint) } else { - String::from("User-defined command") + (String::from("User-defined command"), None) }; entries.push(SlashMenuEntry { name, description, is_skill: false, + alias_hint, }); } } @@ -2062,6 +2114,7 @@ pub(crate) fn slash_completion_hints( name: format!("/skill {skill_name}"), description: skill_desc.clone(), is_skill: true, + alias_hint: None, }); } } @@ -2074,6 +2127,7 @@ pub(crate) fn slash_completion_hints( name: format!("/model {model_name}"), description: String::from("Switch to this model"), is_skill: false, + alias_hint: None, }); } } @@ -2639,12 +2693,14 @@ mod tests { name: format!("/skill{i}"), description: String::new(), is_skill: false, + alias_hint: None, }) .collect(); let one_match = vec![SlashMenuEntry { name: "/skill".to_string(), description: String::new(), is_skill: false, + alias_hint: None, }]; let no_matches = Vec::::new();