feat(Completions): adds completion support for Elvish.

This commit is contained in:
Kevin K 2018-06-26 21:15:48 -04:00
parent 4b5ba7d750
commit 3d3d4b1e9a
4 changed files with 254 additions and 2 deletions

126
src/completions/elvish.rs Normal file
View file

@ -0,0 +1,126 @@
// Std
use std::io::Write;
// Internal
use app::parser::Parser;
use INTERNAL_ERROR_MSG;
pub struct ElvishGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> ElvishGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self { ElvishGen { p: p } }
pub fn generate_to<W: Write>(&self, buf: &mut W) {
let bin_name = self.p.meta.bin_name.as_ref().unwrap();
let mut names = vec![];
let subcommands_cases =
generate_inner(self.p, "", &mut names);
let result = format!(r#"
edit:completion:arg-completer[{bin_name}] = [@words]{{
fn spaces [n]{{
repeat $n ' ' | joins ''
}}
fn cand [text desc]{{
edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
}}
command = '{bin_name}'
for word $words[1:-1] {{
if (has-prefix $word '-') {{
break
}}
command = $command';'$word
}}
completions = [{subcommands_cases}
]
$completions[$command]
}}
"#,
bin_name = bin_name,
subcommands_cases = subcommands_cases
);
w!(buf, result.as_bytes());
}
}
// Escape string inside single quotes
fn escape_string(string: &str) -> String { string.replace("'", "''") }
fn get_tooltip<T : ToString>(help: Option<&str>, data: T) -> String {
match help {
Some(help) => escape_string(help),
_ => data.to_string()
}
}
fn generate_inner<'a, 'b, 'p>(
p: &'p Parser<'a, 'b>,
previous_command_name: &str,
names: &mut Vec<&'p str>,
) -> String {
debugln!("ElvishGen::generate_inner;");
let command_name = if previous_command_name.is_empty() {
p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
} else {
format!("{};{}", previous_command_name, &p.meta.name)
};
let mut completions = String::new();
let preamble = String::from("\n cand ");
for option in p.opts() {
if let Some(data) = option.s.short {
let tooltip = get_tooltip(option.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
}
if let Some(data) = option.s.long {
let tooltip = get_tooltip(option.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
}
}
for flag in p.flags() {
if let Some(data) = flag.s.short {
let tooltip = get_tooltip(flag.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
}
if let Some(data) = flag.s.long {
let tooltip = get_tooltip(flag.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
}
}
for subcommand in &p.subcommands {
let data = &subcommand.p.meta.name;
let tooltip = get_tooltip(subcommand.p.meta.about, data);
completions.push_str(&preamble);
completions.push_str(format!("{} '{}'", data, tooltip).as_str());
}
let mut subcommands_cases = format!(
r"
&'{}'= {{{}
}}",
&command_name,
completions
);
for subcommand in &p.subcommands {
let subcommand_subcommands_cases =
generate_inner(&subcommand.p, &command_name, names);
subcommands_cases.push_str(&subcommand_subcommands_cases);
}
subcommands_cases
}

View file

@ -4,6 +4,7 @@ mod bash;
mod fish; mod fish;
mod zsh; mod zsh;
mod powershell; mod powershell;
mod elvish;
mod shell; mod shell;
// Std // Std
@ -15,6 +16,7 @@ use self::bash::BashGen;
use self::fish::FishGen; use self::fish::FishGen;
use self::zsh::ZshGen; use self::zsh::ZshGen;
use self::powershell::PowerShellGen; use self::powershell::PowerShellGen;
use self::elvish::ElvishGen;
pub use self::shell::Shell; pub use self::shell::Shell;
pub struct ComplGen<'a, 'b>(&'b App<'a, 'b>) pub struct ComplGen<'a, 'b>(&'b App<'a, 'b>)
@ -26,6 +28,7 @@ impl<'a, 'b> ComplGen<'a, 'b> {
pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) { pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) {
match for_shell { match for_shell {
Shell::Elvish => ElvishGen::new(self.p).generate_to(buf),
Shell::Bash => BashGen::new(self.0).generate_to(buf), Shell::Bash => BashGen::new(self.0).generate_to(buf),
Shell::Fish => FishGen::new(self.0).generate_to(buf), Shell::Fish => FishGen::new(self.0).generate_to(buf),
Shell::Zsh => ZshGen::new(self.0).generate_to(buf), Shell::Zsh => ZshGen::new(self.0).generate_to(buf),

View file

@ -15,11 +15,13 @@ pub enum Shell {
Zsh, Zsh,
/// Generates a completion file for PowerShell /// Generates a completion file for PowerShell
PowerShell, PowerShell,
/// Generates a completion file for Elvish
Elvish,
} }
impl Shell { impl Shell {
/// A list of possible variants in `&'static str` form /// A list of possible variants in `&'static str` form
pub fn variants() -> [&'static str; 4] { ["zsh", "bash", "fish", "powershell"] } pub fn variants() -> [&'static str; 5] { ["zsh", "bash", "fish", "powershell", "elvish"] }
} }
impl FromStr for Shell { impl FromStr for Shell {
@ -31,7 +33,8 @@ impl FromStr for Shell {
"FISH" | _ if s.eq_ignore_ascii_case("fish") => Ok(Shell::Fish), "FISH" | _ if s.eq_ignore_ascii_case("fish") => Ok(Shell::Fish),
"BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash), "BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash),
"POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell), "POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell),
_ => Err(String::from("[valid values: bash, fish, zsh, powershell]")), "ELVISH" | _ if s.eq_ignore_ascii_case("elvish") => Ok(Shell::Elvish),
_ => Err(String::from("[valid values: bash, fish, zsh, powershell, elvish]")),
} }
} }
} }
@ -43,6 +46,7 @@ impl fmt::Display for Shell {
Shell::Fish => write!(f, "FISH"), Shell::Fish => write!(f, "FISH"),
Shell::Zsh => write!(f, "ZSH"), Shell::Zsh => write!(f, "ZSH"),
Shell::PowerShell => write!(f, "POWERSHELL"), Shell::PowerShell => write!(f, "POWERSHELL"),
Shell::Elvish => write!(f, "ELVISH"),
} }
} }
} }

View file

@ -228,6 +228,105 @@ Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
} }
"#; "#;
static ELVISH: &'static str = r#"
edit:completion:arg-completer[my_app] = [@words]{
fn spaces [n]{
repeat $n ' ' | joins ''
}
fn cand [text desc]{
edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
}
command = 'my_app'
for word $words[1:-1] {
if (has-prefix $word '-') {
break
}
command = $command';'$word
}
completions = [
&'my_app'= {
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand test 'tests things'
cand help 'Prints this message or the help of the given subcommand(s)'
}
&'my_app;test'= {
cand --case 'the case to test'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
}
&'my_app;help'= {
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
}
]
$completions[$command]
}
"#;
static ELVISH_SPECIAL_CMDS: &'static str = r#"
edit:completion:arg-completer[my_app] = [@words]{
fn spaces [n]{
repeat $n ' ' | joins ''
}
fn cand [text desc]{
edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
}
command = 'my_app'
for word $words[1:-1] {
if (has-prefix $word '-') {
break
}
command = $command';'$word
}
completions = [
&'my_app'= {
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand test 'tests things'
cand some_cmd 'tests other things'
cand some-cmd-with-hypens 'some-cmd-with-hypens'
cand help 'Prints this message or the help of the given subcommand(s)'
}
&'my_app;test'= {
cand --case 'the case to test'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
}
&'my_app;some_cmd'= {
cand --config 'the other case to test'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
}
&'my_app;some-cmd-with-hypens'= {
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
}
&'my_app;help'= {
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
}
]
$completions[$command]
}
"#;
static POWERSHELL_SPECIAL_CMDS: &'static str = r#" static POWERSHELL_SPECIAL_CMDS: &'static str = r#"
using namespace System.Management.Automation using namespace System.Management.Automation
using namespace System.Management.Automation.Language using namespace System.Management.Automation.Language
@ -703,6 +802,26 @@ fn powershell() {
assert!(compare(&*string, POWERSHELL)); assert!(compare(&*string, POWERSHELL));
} }
#[test]
fn elvish() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Elvish, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, ELVISH));
}
#[test]
fn elvish_with_special_commands() {
let mut app = build_app_special_commands();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Elvish, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, ELVISH_SPECIAL_CMDS));
}
#[test] #[test]
fn powershell_with_special_commands() { fn powershell_with_special_commands() {
let mut app = build_app_special_commands(); let mut app = build_app_special_commands();