Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 82 additions & 84 deletions duva-client/src/cli/editor/hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ pub struct CommandHint {
}

impl CommandHint {
fn new(text: &str, complete_up_to: &str) -> Self {
assert!(text.starts_with(complete_up_to));
Self { display: text.into(), complete_up_to: complete_up_to.len() }
fn new(text: &str) -> Self {
let first = text.split_whitespace().next().unwrap_or("");
Self { display: text.into(), complete_up_to: first.len() + 1 }
}

fn suffix(&self, strip_chars: usize) -> Self {
Expand Down Expand Up @@ -55,95 +55,93 @@ impl Hinter for DuvaHinter {

let command = parts[0];
let args_count = parts.len() - 1;
let ends_with_space = line.ends_with(" ");

if let Some(patterns) = self.dynamic_hints.get(command.to_lowercase().as_str()) {
for hint in patterns {
// Skip if we have more args than required (unless it's a repeating pattern)
if args_count > hint.args_required && !hint.repeat_last_arg {
continue;
}

// For repeating patterns, show the hint after reaching minimum args
if hint.repeat_last_arg && args_count >= hint.args_required {
let hint_text = if hint.args_required == 0 && args_count == 0 {
hint.hint_text
} else {
// After first argument, show just the repeating part
hint.hint_text
.split_once(' ')
.map(|(_, rest)| rest)
.unwrap_or(hint.hint_text)
};

return Some(CommandHint::new(
if ends_with_space {
hint_text.to_string()
} else {
format!(" {hint_text}")
}
.as_str(),
"",
));
}

// Original behavior for non-repeating patterns
if args_count == hint.args_required {
return Some(CommandHint::new(
if ends_with_space {
hint.hint_text.to_string()
} else {
format!(" {}", hint.hint_text)
}
.as_str(),
"",
));
}
}

let needs_space = !line.ends_with(" ");

if let Some(hint) = self.get_dynamic_hint(command, args_count, needs_space) {
return Some(hint);
}

// Default behavior - try to match against full hints
let mut matching_hints =
self.default_hints
.iter()
.filter_map(|hint| {
if hint.display.starts_with(line) { Some(hint.suffix(pos)) } else { None }
})
.collect::<Vec<_>>();
// Fall back to default hints
self.get_default_hint(line, pos)
}
}

impl DuvaHinter {
fn get_dynamic_hint(
&self,
command: &str,
args_count: usize,
needs_space: bool,
) -> Option<CommandHint> {
let patterns = self.dynamic_hints.get(command.to_lowercase().as_str())?;

for hint in patterns {
let hint_text = if hint.repeat_last_arg && args_count >= hint.args_required {
// Show repeating pattern
self.get_repeating_hint_text(hint, args_count)
} else if args_count == hint.args_required && !hint.repeat_last_arg {
// Show exact match
hint.hint_text
} else {
continue;
};

let hint_text = if needs_space { &format!(" {}", hint_text) } else { hint_text };
return Some(CommandHint::new(hint_text));
}

matching_hints.sort_by(|a, b| a.display.len().cmp(&b.display.len()));
matching_hints.into_iter().next()
None
}

fn get_repeating_hint_text(&self, hint: &DynamicHint, args_count: usize) -> &str {
if hint.args_required == 0 && args_count == 0 {
hint.hint_text
} else {
hint.hint_text.split_once(' ').map(|(_, rest)| rest).unwrap_or(hint.hint_text)
}
}

fn get_default_hint(&self, line: &str, pos: usize) -> Option<CommandHint> {
self.default_hints
.iter()
.filter_map(
|hint| {
if hint.display.starts_with(line) { Some(hint.suffix(pos)) } else { None }
},
)
.min_by_key(|hint| hint.display.len())
}
}

pub(crate) fn default_hints() -> HashSet<CommandHint> {
let mut set = HashSet::new();
set.insert(CommandHint::new("get key", "get "));
set.insert(CommandHint::new("set key value", "set "));
set.insert(CommandHint::new("set key value [px expr]", "set "));
set.insert(CommandHint::new("append key value", "append "));
set.insert(CommandHint::new("incr key", "incr "));
set.insert(CommandHint::new("incrby key value", "incrby "));
set.insert(CommandHint::new("decr key", "decr "));
set.insert(CommandHint::new("decrby key value", "decrby "));
set.insert(CommandHint::new("cluster info", "cluster "));
set.insert(CommandHint::new("cluster nodes", "cluster "));
set.insert(CommandHint::new("cluster forget node", "cluster "));
set.insert(CommandHint::new("cluster reshard", "cluster "));
set.insert(CommandHint::new("cluster meet node [lazy|eager]", "cluster "));
set.insert(CommandHint::new("ping", ""));
set.insert(CommandHint::new("keys pattern", "keys "));
set.insert(CommandHint::new("info [section]", ""));
set.insert(CommandHint::new("info replication", ""));
set.insert(CommandHint::new("exists key [key ...]", "exists "));
set.insert(CommandHint::new("mget key [key ...]", "mget "));
set.insert(CommandHint::new("del key [key ...]", "del "));
set.insert(CommandHint::new("ttl key", "ttl "));
set.insert(CommandHint::new("replicaof host port", "replicaof "));
set.insert(CommandHint::new("lpush key value [value ...]", "lpush "));
set.insert(CommandHint::new("lpushx key value [value ...]", "lpushx "));
set.insert(CommandHint::new("rpush key value [value ...]", "rpush "));
set.insert(CommandHint::new("rpushx key value [value ...]", "rpushx "));
set.insert(CommandHint::new("get key"));
set.insert(CommandHint::new("set key value"));
set.insert(CommandHint::new("set key value [px expr]"));
set.insert(CommandHint::new("append key value"));
set.insert(CommandHint::new("incr key"));
set.insert(CommandHint::new("incrby key value"));
set.insert(CommandHint::new("decr key"));
set.insert(CommandHint::new("decrby key value"));
set.insert(CommandHint::new("cluster info"));
set.insert(CommandHint::new("cluster nodes"));
set.insert(CommandHint::new("cluster forget node"));
set.insert(CommandHint::new("cluster reshard"));
set.insert(CommandHint::new("cluster meet node [lazy|eager]"));
set.insert(CommandHint::new("ping"));
set.insert(CommandHint::new("keys pattern"));
set.insert(CommandHint::new("info [section]"));
set.insert(CommandHint::new("info replication"));
set.insert(CommandHint::new("exists key [key ...]"));
set.insert(CommandHint::new("mget key [key ...]"));
set.insert(CommandHint::new("del key [key ...]"));
set.insert(CommandHint::new("ttl key"));
set.insert(CommandHint::new("replicaof host port"));
set.insert(CommandHint::new("lpush key value [value ...]"));
set.insert(CommandHint::new("lpushx key value [value ...]"));
set.insert(CommandHint::new("rpush key value [value ...]"));
set.insert(CommandHint::new("rpushx key value [value ...]"));

set
}
Expand Down
Loading