feat(command-palette): add grouped suggestions, history tracking, and model/provider fuzzy matching
- Export `PaletteGroup` and `PaletteSuggestion` to represent suggestion metadata. - Implement command history with deduplication, capacity limit, and recent‑command suggestions. - Enhance dynamic suggestion logic to include history, commands, models, and providers with fuzzy ranking. - Add UI rendering for grouped suggestions, header with command palette label, and footer instructions. - Update help text with new shortcuts (Ctrl+P, layout save/load) and expose new agent/layout commands.
This commit is contained in:
@@ -160,6 +160,26 @@ const COMMANDS: &[CommandSpec] = &[
|
||||
keyword: "stop-agent",
|
||||
description: "Stop the running agent",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "agent status",
|
||||
description: "Show current agent status",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "agent start",
|
||||
description: "Arm the agent for the next request",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "agent stop",
|
||||
description: "Stop the running agent",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "layout save",
|
||||
description: "Persist the current pane layout",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "layout load",
|
||||
description: "Restore the last saved pane layout",
|
||||
},
|
||||
];
|
||||
|
||||
/// Return the static catalog of commands.
|
||||
@@ -168,29 +188,35 @@ pub fn all() -> &'static [CommandSpec] {
|
||||
}
|
||||
|
||||
/// Return the default suggestion list (all command keywords).
|
||||
pub fn default_suggestions() -> Vec<String> {
|
||||
COMMANDS
|
||||
.iter()
|
||||
.map(|spec| spec.keyword.to_string())
|
||||
.collect()
|
||||
pub fn default_suggestions() -> Vec<CommandSpec> {
|
||||
COMMANDS.to_vec()
|
||||
}
|
||||
|
||||
/// Generate keyword suggestions for the given input.
|
||||
pub fn suggestions(input: &str) -> Vec<String> {
|
||||
pub fn suggestions(input: &str) -> Vec<CommandSpec> {
|
||||
let trimmed = input.trim();
|
||||
if trimmed.is_empty() {
|
||||
return default_suggestions();
|
||||
}
|
||||
COMMANDS
|
||||
|
||||
let mut matches: Vec<(usize, usize, CommandSpec)> = COMMANDS
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
if spec.keyword.starts_with(trimmed) {
|
||||
Some(spec.keyword.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
match_score(spec.keyword, trimmed).map(|score| (score.0, score.1, *spec))
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
if matches.is_empty() {
|
||||
return default_suggestions();
|
||||
}
|
||||
|
||||
matches.sort_by(|a, b| {
|
||||
a.0.cmp(&b.0)
|
||||
.then(a.1.cmp(&b.1))
|
||||
.then(a.2.keyword.cmp(b.2.keyword))
|
||||
});
|
||||
|
||||
matches.into_iter().map(|(_, _, spec)| spec).collect()
|
||||
}
|
||||
|
||||
pub fn match_score(candidate: &str, query: &str) -> Option<(usize, usize)> {
|
||||
@@ -219,6 +245,19 @@ pub fn match_score(candidate: &str, query: &str) -> Option<(usize, usize)> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn suggestions_prioritize_agent_start() {
|
||||
let results = suggestions("agent st");
|
||||
assert!(!results.is_empty());
|
||||
assert_eq!(results[0].keyword, "agent start");
|
||||
assert!(results.iter().any(|spec| spec.keyword == "agent stop"));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_subsequence(text: &str, pattern: &str) -> bool {
|
||||
if pattern.is_empty() {
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user