use rustyline::completion::Pair;
const MODIFIER_CHARS: &[char] = &['x', 'j', 'o', 'v', 'z', 'p', 's'];
pub(super) fn generate_completions(current_word: &str) -> Vec<Pair> {
if current_word == "\\" {
let mut completions = vec![Pair {
display: "\\g".to_string(),
replacement: "\\g".to_string(),
}];
for &modifier in MODIFIER_CHARS {
let mut completion = String::from("\\g");
completion.push(modifier);
completions.push(Pair {
display: completion.clone(),
replacement: completion,
});
}
return completions;
}
if !current_word.starts_with("\\g") && !current_word.starts_with("\\G") {
return Vec::new();
}
let after_g = ¤t_word[2..];
let after_g_lower = after_g.to_lowercase();
let mut used_modifiers = Vec::new();
let mut has_set = false;
let mut chars_iter = after_g_lower.chars().peekable();
while let Some(&ch) = chars_iter.peek() {
if ch == 's' {
let mut look = chars_iter.clone();
look.next();
if look.collect::<String>().starts_with('e') {
break;
}
}
if MODIFIER_CHARS.contains(&ch) {
used_modifiers.push(ch);
chars_iter.next();
} else {
break;
}
}
let remaining: String = chars_iter.collect();
if remaining == "set" || "set".starts_with(&remaining) {
has_set = remaining == "set";
}
let mut completions = Vec::new();
if after_g.is_empty() {
completions.push(Pair {
display: "\\g".to_string(),
replacement: "\\g".to_string(),
});
}
for &modifier in MODIFIER_CHARS {
if !used_modifiers.contains(&modifier) {
let mut completion = String::from("\\g");
for &m in &used_modifiers {
completion.push(m);
}
completion.push(modifier);
if completion
.to_lowercase()
.starts_with(¤t_word.to_lowercase())
{
completions.push(Pair {
display: completion.clone(),
replacement: completion,
});
}
}
}
if !has_set && remaining.is_empty() && !used_modifiers.is_empty() {
let mut completion = String::from("\\g");
for &m in &used_modifiers {
completion.push(m);
}
completion.push_str("set");
if completion
.to_lowercase()
.starts_with(¤t_word.to_lowercase())
{
completions.push(Pair {
display: completion.clone(),
replacement: completion,
});
}
}
if !has_set && remaining.is_empty() && used_modifiers.last() == Some(&'s') {
let mut completion = String::from("\\g");
for &m in &used_modifiers[..used_modifiers.len() - 1] {
completion.push(m);
}
completion.push_str("set");
if completion
.to_lowercase()
.starts_with(¤t_word.to_lowercase())
{
completions.push(Pair {
display: completion.clone(),
replacement: completion,
});
}
}
if !has_set && !remaining.is_empty() && "set".starts_with(&remaining) {
let mut completion = String::from("\\g");
for &m in &used_modifiers {
completion.push(m);
}
completion.push_str("set");
if completion
.to_lowercase()
.starts_with(¤t_word.to_lowercase())
{
completions.push(Pair {
display: completion.clone(),
replacement: completion,
});
}
}
completions
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_g_alone() {
let completions = generate_completions("\\g");
assert!(completions.iter().any(|c| c.display == "\\g"));
assert!(completions.iter().any(|c| c.display == "\\gx"));
assert!(completions.iter().any(|c| c.display == "\\gj"));
assert!(completions.iter().any(|c| c.display == "\\go"));
assert!(completions.iter().any(|c| c.display == "\\gv"));
assert!(completions.iter().any(|c| c.display == "\\gz"));
}
#[test]
fn test_generate_g_includes_plain_and_sql() {
let completions = generate_completions("\\g");
assert!(completions.iter().any(|c| c.display == "\\gp"));
assert!(completions.iter().any(|c| c.display == "\\gs"));
}
#[test]
fn test_generate_gs_then_x() {
let completions = generate_completions("\\gs");
assert!(completions.iter().any(|c| c.display == "\\gsx"));
}
#[test]
fn test_generate_gset_still_completes() {
let completions = generate_completions("\\gse");
assert!(completions.iter().any(|c| c.display == "\\gset"));
}
#[test]
fn test_generate_gx() {
let completions = generate_completions("\\gx");
assert!(completions.iter().any(|c| c.display == "\\gxj"));
assert!(completions.iter().any(|c| c.display == "\\gxo"));
assert!(completions.iter().any(|c| c.display == "\\gxv"));
assert!(completions.iter().any(|c| c.display == "\\gxz"));
assert!(completions.iter().any(|c| c.display == "\\gxset"));
assert!(!completions.iter().any(|c| c.display == "\\gxx"));
}
#[test]
fn test_generate_gxj() {
let completions = generate_completions("\\gxj");
assert!(completions.iter().any(|c| c.display == "\\gxjo"));
assert!(completions.iter().any(|c| c.display == "\\gxjv"));
assert!(completions.iter().any(|c| c.display == "\\gxjz"));
assert!(completions.iter().any(|c| c.display == "\\gxjset"));
assert!(!completions.iter().any(|c| c.display == "\\gxjx"));
assert!(!completions.iter().any(|c| c.display == "\\gxjj"));
}
#[test]
fn test_generate_gxz() {
let completions = generate_completions("\\gxz");
assert!(completions.iter().any(|c| c.display == "\\gxzj"));
assert!(completions.iter().any(|c| c.display == "\\gxzo"));
assert!(completions.iter().any(|c| c.display == "\\gxzv"));
assert!(completions.iter().any(|c| c.display == "\\gxzset"));
}
#[test]
fn test_generate_all_modifiers() {
let completions = generate_completions("\\gxjovz");
assert!(completions.iter().any(|c| c.display == "\\gxjovzset"));
assert!(!completions.iter().any(|c| c.display == "\\gxjovzx"));
}
#[test]
fn test_generate_partial_set() {
let completions = generate_completions("\\gxs");
assert!(completions.iter().any(|c| c.display == "\\gxset"));
}
#[test]
fn test_generate_case_insensitive() {
let completions = generate_completions("\\Gx");
assert!(completions.iter().any(|c| c.display == "\\gxj"));
assert!(completions.iter().any(|c| c.display == "\\gxo"));
}
#[test]
fn test_non_g_command() {
let completions = generate_completions("\\q");
assert!(completions.is_empty());
}
#[test]
fn test_backslash_only() {
let completions = generate_completions("\\");
assert!(completions.iter().any(|c| c.display == "\\g"));
assert!(completions.iter().any(|c| c.display == "\\gx"));
assert!(completions.iter().any(|c| c.display == "\\gj"));
assert!(completions.iter().any(|c| c.display == "\\go"));
assert!(completions.iter().any(|c| c.display == "\\gv"));
assert!(completions.iter().any(|c| c.display == "\\gz"));
}
}