use crate::completion_registry::COMP_REGISTRY;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
use rustyline::validate::Validator;
use rustyline::{Context, Helper, Result};
use std::borrow::Cow;
use std::process::Command;
fn extract_command_and_word(line: &str, pos: usize) -> (String, String) {
let current_line = &line[..pos];
let mut words = current_line.split_whitespace().collect::<Vec<&str>>();
if words.is_empty() {
return (String::new(), String::new());
}
let command = words[0].to_string();
let word_being_completed = if current_line.ends_with(char::is_whitespace) {
String::new()
} else {
words.pop().unwrap().to_string()
};
if words.is_empty() {
return (String::new(), command);
}
(command, word_being_completed)
}
fn get_git_branches() -> Vec<String> {
let output = Command::new("git")
.args(["branch", "--format=%(refname:short)"])
.output();
if let Ok(out) = output {
if out.status.success() {
let stdout = String::from_utf8_lossy(&out.stdout);
return stdout.lines().map(|s| s.to_string()).collect();
}
}
Vec::new()
}
fn get_git_remotes() -> Vec<String> {
let output = Command::new("git").arg("remote").output();
if let Ok(out) = output {
if out.status.success() {
let stdout = String::from_utf8_lossy(&out.stdout);
return stdout.lines().map(|s| s.to_string()).collect();
}
}
Vec::new()
}
fn extract_git_context(line: &str, pos: usize) -> Option<(String, usize)> {
let current_line = &line[..pos];
let mut words = current_line.split_whitespace().collect::<Vec<&str>>();
if current_line.ends_with(char::is_whitespace) {
} else {
words.pop();
}
if words.len() >= 2 && words[0] == "git" {
return Some((words[1].to_string(), words.len()));
}
None
}
pub struct PmshHelper {
pub completer: FilenameCompleter,
}
impl PmshHelper {
pub fn new() -> Self {
Self {
completer: FilenameCompleter::new(),
}
}
}
impl Completer for PmshHelper {
type Candidate = Pair;
fn complete(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Result<(usize, Vec<Pair>)> {
let (command, word_being_completed) = extract_command_and_word(line, pos);
if !command.is_empty() {
if command == "git" && !word_being_completed.starts_with('-') {
if let Some((subcommand, num_words)) = extract_git_context(line, pos) {
let mut candidates = Vec::new();
if num_words == 2 {
match subcommand.as_str() {
"checkout" | "switch" | "merge" | "rebase" | "branch" => {
candidates = get_git_branches();
}
"push" | "pull" | "fetch" | "remote" => {
candidates = get_git_remotes();
}
_ => {}
}
} else if num_words == 3 {
match subcommand.as_str() {
"push" | "pull" | "fetch" => {
candidates = get_git_branches();
}
_ => {}
}
}
if !candidates.is_empty() {
let mut matches = Vec::new();
for w in candidates {
if w.starts_with(&word_being_completed) {
matches.push(Pair {
display: w.clone(),
replacement: w,
});
}
}
if !matches.is_empty() || word_being_completed.is_empty() {
let start_pos = pos - word_being_completed.len();
return Ok((start_pos, matches));
}
}
}
}
let registry_key = if command == "git" {
if let Some((subcmd, _)) = extract_git_context(line, pos) {
format!("git-{}", subcmd)
} else {
command.clone()
}
} else {
command.clone()
};
if let Ok(registry) = COMP_REGISTRY.read() {
if let Some(spec) = registry
.get(®istry_key)
.or_else(|| registry.get(&command))
{
if let Some(wordlist) = spec.wordlist {
let mut matches = Vec::new();
let words: Vec<&str> = wordlist.split_whitespace().collect();
for w in words {
if w.starts_with(&word_being_completed) {
matches.push(Pair {
display: w.to_string(),
replacement: w.to_string(),
});
}
}
if !matches.is_empty() || word_being_completed.is_empty() {
let start_pos = pos - word_being_completed.len();
return Ok((start_pos, matches));
}
}
}
}
}
self.completer.complete(line, pos, ctx)
}
}
impl Hinter for PmshHelper {
type Hint = String;
fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<String> {
None
}
}
impl Highlighter for PmshHelper {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
Cow::Borrowed(line)
}
fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool {
false
}
}
impl Validator for PmshHelper {}
impl Helper for PmshHelper {}