From e79482f2dc7fea746a8c21dabf24a71bdcdc01bf Mon Sep 17 00:00:00 2001 From: Zhenhui Xie Date: Tue, 17 Mar 2020 08:20:14 +0800 Subject: [PATCH] Update zsh completion file (#606) - Complete variable names after `--set` - Complete recipe names - Display recipe signature and body below command line - Modify completions subcommand to produce enhanced zsh completion script --- completions/just.zsh | 49 +++++++++++++++---- src/config.rs | 17 +------ src/subcommand.rs | 110 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 24 deletions(-) diff --git a/completions/just.zsh b/completions/just.zsh index 4724fa8..6f04289 100644 --- a/completions/just.zsh +++ b/completions/just.zsh @@ -14,18 +14,18 @@ _just() { fi local context curcontext="$curcontext" state line - _arguments "${_arguments_options[@]}" \ + local common=( '--color=[Print colorful output]: :(auto always never)' \ '-f+[Use as justfile.]' \ '--justfile=[Use as justfile.]' \ -'*--set=[Override with ]' \ +'*--set[Override with ]: :_just_variables' \ '--shell=[Invoke to run recipes]' \ '*--shell-arg=[Invoke shell with as an argument]' \ '-d+[Use as working directory. --justfile must also be set]' \ '--working-directory=[Use as working directory. --justfile must also be set]' \ '--completions=[Print shell completion script for ]: :(zsh bash fish powershell elvish)' \ -'-s+[Show information about ]' \ -'--show=[Show information about ]' \ +'-s+[Show information about ]: :_just_commands' \ +'--show=[Show information about ]: :_just_commands' \ '(-q --quiet)--dry-run[Print what just would do without doing it]' \ '--highlight[Highlight echoed recipe lines in bold]' \ '--no-highlight[Don'\''t highlight echoed recipe lines in bold]' \ @@ -47,17 +47,50 @@ _just() { '--help[Print help information]' \ '-V[Print version information]' \ '--version[Print version information]' \ -'::ARGUMENTS -- Overrides and recipe(s) to run, defaulting to the first recipe in the justfile:_files' \ -&& ret=0 - +) + + _arguments "${_arguments_options[@]}" $common \ + '1: :_just_commands' \ + '*: :->args' \ + && ret=0 + + case $state in + args) + curcontext="${curcontext%:*}-${words[2]}:" + + local lastarg=${words[${#words}]} + + if [[ ${lastarg} = */* ]]; then + # Arguments contain slash would be recognised as a file + _arguments -s -S $common '*:: :_files' + else + # Show usage message + _message "`just --show ${words[2]}`" + # Or complete with other commands + #_arguments -s -S $common '*:: :_just_commands' + fi + ;; + esac + + return ret } (( $+functions[_just_commands] )) || _just_commands() { local commands; commands=( - + ${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: } ) + _describe -t commands 'just commands' commands "$@" } +(( $+functions[_just_variables] )) || +_just_variables() { + local variables; variables=( + ${(s: :)$(_call_program commands just --variables)} + ) + + _describe -t variables 'variables' variables +} + _just "$@" diff --git a/src/config.rs b/src/config.rs index fdda02f..202761f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -417,7 +417,7 @@ impl Config { } if let Completions { shell } = self.subcommand { - return Self::completions(&shell); + return Subcommand::completions(&shell); } let search = @@ -459,21 +459,6 @@ impl Config { } } - fn completions(shell: &str) -> Result<(), i32> { - let shell = shell - .parse::() - .expect("Invalid value for clap::Shell"); - - let buffer = Vec::new(); - let mut cursor = Cursor::new(buffer); - Self::app().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut cursor); - let buffer = cursor.into_inner(); - let text = String::from_utf8(buffer).expect("Clap completion not UTF-8"); - println!("{}", text.trim()); - - Ok(()) - } - fn dump(justfile: Justfile) -> Result<(), i32> { println!("{}", justfile); Ok(()) diff --git a/src/subcommand.rs b/src/subcommand.rs index 82b9c96..ae0f6a7 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -22,3 +22,113 @@ pub(crate) enum Subcommand { Summary, Variables, } + +const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[ + ( + r#" _arguments "${_arguments_options[@]}" \"#, + r#" local common=("#, + ), + ( + r#"'*--set=[Override with ]' \"#, + r#"'*--set[Override with ]: :_just_variables' \"#, + ), + ( + r#"'-s+[Show information about ]' \ +'--show=[Show information about ]' \"#, + r#"'-s+[Show information about ]: :_just_commands' \ +'--show=[Show information about ]: :_just_commands' \"#, + ), + ( + "'::ARGUMENTS -- Overrides and recipe(s) to run, defaulting to the first recipe in the \ + justfile:_files' \\ +&& ret=0 +\x20\x20\x20\x20 +", + r#") + + _arguments "${_arguments_options[@]}" $common \ + '1: :_just_commands' \ + '*: :->args' \ + && ret=0 + + case $state in + args) + curcontext="${curcontext%:*}-${words[2]}:" + + local lastarg=${words[${#words}]} + + if [[ ${lastarg} = */* ]]; then + # Arguments contain slash would be recognised as a file + _arguments -s -S $common '*:: :_files' + else + # Show usage message + _message "`just --show ${words[2]}`" + # Or complete with other commands + #_arguments -s -S $common '*:: :_just_commands' + fi + ;; + esac + + return ret +"#, + ), + ( + " local commands; commands=( +\x20\x20\x20\x20\x20\x20\x20\x20 + )", + r#" local commands; commands=( + ${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: } + ) +"#, + ), + ( + r#"_just "$@""#, + r#"(( $+functions[_just_variables] )) || +_just_variables() { + local variables; variables=( + ${(s: :)$(_call_program commands just --variables)} + ) + + _describe -t variables 'variables' variables +} + +_just "$@""#, + ), +]; + +impl Subcommand { + pub(crate) fn completions(shell: &str) -> Result<(), i32> { + fn replace(haystack: &mut String, needle: &str, replacement: &str) -> Result<(), i32> { + if let Some(index) = haystack.find(needle) { + haystack.replace_range(index..index + needle.len(), replacement); + Ok(()) + } else { + eprintln!("Failed to find text:"); + eprintln!("{}", needle); + eprintln!("…in completion script:"); + eprintln!("{}", haystack); + Err(EXIT_FAILURE) + } + } + + let shell = shell + .parse::() + .expect("Invalid value for clap::Shell"); + + let buffer = Vec::new(); + let mut cursor = Cursor::new(buffer); + Config::app().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut cursor); + let buffer = cursor.into_inner(); + let mut script = String::from_utf8(buffer).expect("Clap completion not UTF-8"); + + if let clap::Shell::Zsh = shell { + for (needle, replacement) in ZSH_COMPLETION_REPLACEMENTS { + replace(&mut script, needle, replacement)?; + } + } + + println!("{}", script.trim()); + + Ok(()) + } +}