use command_trie::{CommandTrie, CommandTrieBuilder, SubTrie};
#[derive(Clone)]
struct CommandInfo {
help: &'static str,
}
fn build() -> CommandTrie<CommandInfo> {
let commands = [
("add", "stage file contents"),
("alias", "create a command alias"),
("branch", "list, create, or delete branches"),
("checkout", "switch branches or restore files"),
("cherry-pick", "apply changes from existing commits"),
("clean", "remove untracked files"),
("clone", "clone a repository"),
("command", "run a raw shell command"),
("commit", "record changes to the repository"),
("config", "get and set configuration"),
("diff", "show changes between commits"),
("fetch", "download objects and refs"),
("merge", "join two histories together"),
("push", "update remote refs"),
("rebase", "reapply commits on top of another base"),
("café", "open the team café board"),
("cafétéria", "daily menu"),
("naïve-merge", "merge without conflict resolution"),
("🚀-deploy", "ship it"),
("🚀-rollback", "unship it"),
];
let mut b = CommandTrieBuilder::new();
for (name, help) in commands {
b.insert(name, CommandInfo { help });
}
b.build()
}
enum TabOutcome<'a> {
NoMatch,
Commit {
extension: String,
info: &'a CommandInfo,
},
Menu {
extension: String,
menu: Vec<String>,
},
}
fn handle_tab<'a>(trie: &'a CommandTrie<CommandInfo>, typed: &str) -> TabOutcome<'a> {
let Some(sub) = trie.subtrie(typed) else {
return TabOutcome::NoMatch;
};
if let Some(info) = sub.unique_value() {
return TabOutcome::Commit {
extension: sub.extension().to_string(),
info,
};
}
let menu = candidates_after_common_prefix(&sub);
TabOutcome::Menu {
extension: sub.extension().to_string(),
menu,
}
}
fn candidates_after_common_prefix(sub: &SubTrie<'_, CommandInfo>) -> Vec<String> {
let cp_len = sub.common_prefix().len();
let mut out = Vec::with_capacity(sub.len());
sub.for_each(|key, _| out.push(key[cp_len..].to_string()));
out
}
fn simulate(trie: &CommandTrie<CommandInfo>, typed: &str) {
print!("typed {typed:>10?} -> ");
match handle_tab(trie, typed) {
TabOutcome::NoMatch => {
println!("no match");
}
TabOutcome::Commit { extension, info } => {
println!(
"splice {extension:?}, COMMIT ({}{}: {})",
typed, extension, info.help
);
}
TabOutcome::Menu { extension, menu } => {
print!("splice {extension:?}, menu of {} -> ", menu.len());
let preview: Vec<_> = menu.iter().take(6).collect();
println!("{preview:?}");
}
}
}
fn main() {
let trie = build();
println!("== fish-style TAB simulation ==\n");
simulate(&trie, "");
simulate(&trie, "c");
simulate(&trie, "co");
simulate(&trie, "comm");
simulate(&trie, "comma");
simulate(&trie, "clone");
simulate(&trie, "xyz");
println!("\n== UTF-8 keys ==\n");
simulate(&trie, "caf");
simulate(&trie, "café");
simulate(&trie, "cafét");
simulate(&trie, "🚀");
simulate(&trie, "🚀-d");
println!("\n== count_completions / completion_prefix ==\n");
for typed in ["c", "co", "comm", "comma", "z", "caf", "🚀"] {
println!(
"{typed:>8?}: {} match(es), LCP = {:?}",
trie.count_completions(typed),
trie.completion_prefix(typed),
);
}
}