#[derive(Clone, Default)]
pub enum MatchStrategy {
All,
#[default]
Prefix,
Contains,
}
#[derive(Clone, Debug)]
pub struct CompletionItem {
pub text: String,
pub description: Option<String>,
pub priority: u32,
}
impl CompletionItem {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
description: None,
priority: 0,
}
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn with_priority(mut self, priority: u32) -> Self {
self.priority = priority;
self
}
}
pub struct TabNode {
trigger: Option<String>,
completions: Vec<CompletionItem>,
children: Vec<TabNode>,
match_strategy: MatchStrategy,
}
impl TabNode {
fn new(trigger: Option<String>) -> Self {
Self {
trigger,
completions: Vec::new(),
children: Vec::new(),
match_strategy: MatchStrategy::default(),
}
}
fn root() -> Self {
Self::new(None)
}
}
#[derive(Clone, Debug)]
pub struct CompletionCandidate {
pub full_text: String,
pub completion: String,
pub description: Option<String>,
}
pub struct TabTree {
root: TabNode,
current_candidates: Vec<CompletionCandidate>,
last_input: String,
}
impl TabTree {
pub fn new() -> Self {
Self {
root: TabNode::root(),
current_candidates: Vec::new(),
last_input: String::new(),
}
}
pub fn register_completions(&mut self, context: &str, completions: &[&str]) {
let items: Vec<CompletionItem> = completions
.iter()
.map(|&text| CompletionItem::new(text))
.collect();
self.register_completions_advanced(context, items, MatchStrategy::default());
}
pub fn register_completions_with_desc(
&mut self,
context: &str,
items: &[(&str, &str)],
) -> Vec<CompletionItem> {
let completion_items: Vec<CompletionItem> = items
.iter()
.map(|&(text, desc)| CompletionItem::new(text).with_description(desc))
.collect();
self.register_completions_advanced(context, completion_items, MatchStrategy::default())
}
pub fn register_completions_advanced(
&mut self,
context: &str,
items: Vec<CompletionItem>,
strategy: MatchStrategy,
) -> Vec<CompletionItem> {
let trigger = if context.is_empty() {
None
} else {
Some(context.to_string())
};
let mut duplicates = Vec::new();
if let Some(node) = self.find_or_create_node(trigger.as_deref()) {
for new_item in &items {
let is_duplicate = node
.completions
.iter()
.any(|existing_item| existing_item.text == new_item.text);
if is_duplicate {
duplicates.push(new_item.clone());
continue; }
node.completions.push(new_item.clone());
}
node.match_strategy = strategy;
}
duplicates
}
pub fn add_completion(&mut self, context: &str, text: &str, description: Option<&str>) {
let trigger = if context.is_empty() {
None
} else {
Some(context.to_string())
};
if let Some(node) = self.find_or_create_node(trigger.as_deref()) {
let mut item = CompletionItem::new(text);
if let Some(desc) = description {
item = item.with_description(desc);
}
node.completions.push(item);
}
}
fn find_or_create_node(&mut self, trigger: Option<&str>) -> Option<&mut TabNode> {
if let Some(trigger_str) = trigger {
fn find_node_exists(node: &TabNode, trigger: &str) -> bool {
if node.trigger.as_deref() == Some(trigger) {
return true;
}
for child in &node.children {
if find_node_exists(child, trigger) {
return true;
}
}
false
}
if !find_node_exists(&self.root, trigger_str) {
let new_node = TabNode::new(Some(trigger_str.to_string()));
self.root.children.push(new_node);
}
fn find_node_mut<'a>(node: &'a mut TabNode, trigger: &str) -> Option<&'a mut TabNode> {
if node.trigger.as_deref() == Some(trigger) {
return Some(node);
}
for child in &mut node.children {
if let Some(found) = find_node_mut(child, trigger) {
return Some(found);
}
}
None
}
find_node_mut(&mut self.root, trigger_str)
} else {
Some(&mut self.root)
}
}
fn find_deepest_match(&self, input: &str) -> &TabNode {
let mut best_match = &self.root;
let mut best_match_len = 0;
fn search<'a>(
node: &'a TabNode,
input: &str,
best: &mut &'a TabNode,
best_len: &mut usize,
) {
if let Some(trigger) = &node.trigger
&& input.starts_with(trigger)
&& trigger.len() > *best_len
{
*best = node;
*best_len = trigger.len();
}
for child in &node.children {
search(child, input, best, best_len);
}
}
search(&self.root, input, &mut best_match, &mut best_match_len);
best_match
}
pub fn get_candidates(&mut self, input: &str) -> Vec<CompletionCandidate> {
if input == self.last_input {
return self.current_candidates.clone();
}
self.last_input = input.to_string();
let node = self.find_deepest_match(input);
let mut candidates = node.completions.clone();
match &node.match_strategy {
MatchStrategy::All => {
}
MatchStrategy::Prefix => {
let suffix = if let Some(trigger) = &node.trigger {
input
.strip_prefix(trigger.as_str())
.unwrap_or("")
.trim_start()
} else {
input
};
if !suffix.is_empty() {
candidates.retain(|item| item.text.starts_with(suffix));
}
}
MatchStrategy::Contains => {
let search = input.split_whitespace().last().unwrap_or("");
if !search.is_empty() {
candidates.retain(|item| item.text.contains(search));
}
}
}
candidates.sort_by(|a, b| b.priority.cmp(&a.priority));
let trigger_prefix = node.trigger.as_deref().unwrap_or("");
let result: Vec<CompletionCandidate> = candidates
.into_iter()
.map(|item| {
let full_text = if trigger_prefix.is_empty() {
item.text.clone()
} else {
format!("{} {}", trigger_prefix, item.text)
};
CompletionCandidate {
full_text,
completion: item.text,
description: item.description,
}
})
.collect();
self.current_candidates = result.clone();
result
}
pub fn get_best_match(&mut self, input: &str) -> Option<String> {
let candidates = self.get_candidates(input);
candidates.first().map(|c| c.full_text.clone())
}
pub fn clear_cache(&mut self) {
self.last_input.clear();
self.current_candidates.clear();
}
pub fn count_total_items(&self) -> usize {
fn count_node(node: &TabNode) -> usize {
let mut count = node.completions.len();
for child in &node.children {
count += count_node(child);
}
count
}
count_node(&self.root)
}
}
impl Default for TabTree {
fn default() -> Self {
Self::new()
}
}