2024-06-08 14:56:21 -07:00
|
|
|
use {super::*, clap::ValueEnum};
|
|
|
|
|
|
|
|
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq)]
|
|
|
|
pub(crate) enum Shell {
|
|
|
|
Bash,
|
|
|
|
Elvish,
|
|
|
|
Fish,
|
|
|
|
#[value(alias = "nu")]
|
|
|
|
Nushell,
|
|
|
|
Powershell,
|
|
|
|
Zsh,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Shell {
|
|
|
|
pub(crate) fn script(self) -> RunResult<'static, String> {
|
|
|
|
match self {
|
|
|
|
Self::Bash => completions::clap(clap_complete::Shell::Bash),
|
|
|
|
Self::Elvish => completions::clap(clap_complete::Shell::Elvish),
|
|
|
|
Self::Fish => completions::clap(clap_complete::Shell::Fish),
|
|
|
|
Self::Nushell => Ok(completions::NUSHELL_COMPLETION_SCRIPT.into()),
|
|
|
|
Self::Powershell => completions::clap(clap_complete::Shell::PowerShell),
|
|
|
|
Self::Zsh => completions::clap(clap_complete::Shell::Zsh),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clap(shell: clap_complete::Shell) -> RunResult<'static, String> {
|
|
|
|
fn replace(haystack: &mut String, needle: &str, replacement: &str) -> RunResult<'static, ()> {
|
|
|
|
if let Some(index) = haystack.find(needle) {
|
|
|
|
haystack.replace_range(index..index + needle.len(), replacement);
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(Error::internal(format!(
|
|
|
|
"Failed to find text:\n{needle}\n…in completion script:\n{haystack}"
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut script = {
|
|
|
|
let mut tempfile = tempfile().map_err(|io_error| Error::TempfileIo { io_error })?;
|
|
|
|
|
|
|
|
clap_complete::generate(
|
|
|
|
shell,
|
|
|
|
&mut crate::config::Config::app(),
|
|
|
|
env!("CARGO_PKG_NAME"),
|
|
|
|
&mut tempfile,
|
|
|
|
);
|
|
|
|
|
|
|
|
tempfile
|
|
|
|
.rewind()
|
|
|
|
.map_err(|io_error| Error::TempfileIo { io_error })?;
|
|
|
|
|
|
|
|
let mut buffer = String::new();
|
|
|
|
|
|
|
|
tempfile
|
|
|
|
.read_to_string(&mut buffer)
|
|
|
|
.map_err(|io_error| Error::TempfileIo { io_error })?;
|
|
|
|
|
|
|
|
buffer
|
|
|
|
};
|
|
|
|
|
|
|
|
match shell {
|
|
|
|
clap_complete::Shell::Bash => {
|
|
|
|
for (needle, replacement) in completions::BASH_COMPLETION_REPLACEMENTS {
|
|
|
|
replace(&mut script, needle, replacement)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
clap_complete::Shell::Fish => {
|
|
|
|
script.insert_str(0, completions::FISH_RECIPE_COMPLETIONS);
|
|
|
|
}
|
|
|
|
clap_complete::Shell::PowerShell => {
|
|
|
|
for (needle, replacement) in completions::POWERSHELL_COMPLETION_REPLACEMENTS {
|
|
|
|
replace(&mut script, needle, replacement)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
clap_complete::Shell::Zsh => {
|
|
|
|
for (needle, replacement) in completions::ZSH_COMPLETION_REPLACEMENTS {
|
|
|
|
replace(&mut script, needle, replacement)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(script.trim().into())
|
|
|
|
}
|
|
|
|
|
|
|
|
const NUSHELL_COMPLETION_SCRIPT: &str = r#"def "nu-complete just" [] {
|
|
|
|
(^just --dump --unstable --dump-format json | from json).recipes | transpose recipe data | flatten | where {|row| $row.private == false } | select recipe doc parameters | rename value description
|
|
|
|
}
|
|
|
|
|
|
|
|
# Just: A Command Runner
|
|
|
|
export extern "just" [
|
|
|
|
...recipe: string@"nu-complete just", # Recipe(s) to run, may be with argument(s)
|
|
|
|
]"#;
|
|
|
|
|
|
|
|
const FISH_RECIPE_COMPLETIONS: &str = r#"function __fish_just_complete_recipes
|
2024-06-12 11:38:37 -07:00
|
|
|
if string match -rq '(-f|--justfile)\s*=?(?<justfile>[^\s]+)' -- (string split -- ' -- ' (commandline -pc))[1]
|
|
|
|
set -fx JUST_JUSTFILE "$justfile"
|
|
|
|
end
|
2023-10-30 10:29:33 -07:00
|
|
|
just --list 2> /dev/null | tail -n +2 | awk '{
|
|
|
|
command = $1;
|
|
|
|
args = $0;
|
|
|
|
desc = "";
|
|
|
|
delim = "";
|
|
|
|
sub(/^[[:space:]]*[^[:space:]]*/, "", args);
|
|
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", args);
|
|
|
|
|
|
|
|
if (match(args, /#.*/)) {
|
|
|
|
desc = substr(args, RSTART+2, RLENGTH);
|
|
|
|
args = substr(args, 0, RSTART-1);
|
|
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", args);
|
|
|
|
}
|
|
|
|
|
|
|
|
gsub(/\+|=[`\'"][^`\'"]*[`\'"]/, "", args);
|
|
|
|
gsub(/ /, ",", args);
|
|
|
|
|
|
|
|
if (args != ""){
|
|
|
|
args = "Args: " args;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args != "" && desc != "") {
|
|
|
|
delim = "; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
print command "\t" args delim desc
|
|
|
|
}'
|
2021-07-26 17:19:52 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
# don't suggest files right off
|
|
|
|
complete -c just -n "__fish_is_first_arg" --no-files
|
|
|
|
|
|
|
|
# complete recipes
|
|
|
|
complete -c just -a '(__fish_just_complete_recipes)'
|
|
|
|
|
|
|
|
# autogenerated completions
|
|
|
|
"#;
|
|
|
|
|
2024-06-08 14:56:21 -07:00
|
|
|
const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
2021-07-26 17:19:52 -07:00
|
|
|
(
|
2024-06-13 19:57:12 -07:00
|
|
|
r#" _arguments "${_arguments_options[@]}" : \"#,
|
2023-11-16 13:51:57 -08:00
|
|
|
r" local common=(",
|
2021-07-26 17:19:52 -07:00
|
|
|
),
|
|
|
|
(
|
2024-05-14 20:29:40 -07:00
|
|
|
r"'*--set=[Override <VARIABLE> with <VALUE>]:VARIABLE: :VARIABLE: ' \",
|
|
|
|
r"'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \",
|
2021-07-26 17:19:52 -07:00
|
|
|
),
|
|
|
|
(
|
2024-05-29 18:41:37 -07:00
|
|
|
r"'()-s+[Show recipe at <PATH>]:PATH: ' \
|
|
|
|
'()--show=[Show recipe at <PATH>]:PATH: ' \",
|
|
|
|
r"'-s+[Show recipe at <PATH>]: :(_just_commands)' \
|
|
|
|
'--show=[Show recipe at <PATH>]: :(_just_commands)' \",
|
2021-07-26 17:19:52 -07:00
|
|
|
),
|
|
|
|
(
|
2024-05-14 20:29:40 -07:00
|
|
|
"'*::ARGUMENTS -- Overrides and recipe(s) to run, defaulting to the first recipe in the \
|
|
|
|
justfile:' \\
|
|
|
|
&& ret=0",
|
2021-07-26 17:19:52 -07:00
|
|
|
r#")
|
|
|
|
|
|
|
|
_arguments "${_arguments_options[@]}" $common \
|
|
|
|
'1: :_just_commands' \
|
|
|
|
'*: :->args' \
|
|
|
|
&& ret=0
|
|
|
|
|
|
|
|
case $state in
|
|
|
|
args)
|
|
|
|
curcontext="${curcontext%:*}-${words[2]}:"
|
|
|
|
|
|
|
|
local lastarg=${words[${#words}]}
|
|
|
|
local recipe
|
|
|
|
|
|
|
|
local cmds; cmds=(
|
|
|
|
${(s: :)$(_call_program commands just --summary)}
|
|
|
|
)
|
|
|
|
|
|
|
|
# Find first recipe name
|
|
|
|
for ((i = 2; i < $#words; i++ )) do
|
|
|
|
if [[ ${cmds[(I)${words[i]}]} -gt 0 ]]; then
|
|
|
|
recipe=${words[i]}
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
|
|
|
if [[ $lastarg = */* ]]; then
|
|
|
|
# Arguments contain slash would be recognised as a file
|
|
|
|
_arguments -s -S $common '*:: :_files'
|
|
|
|
elif [[ $lastarg = *=* ]]; then
|
|
|
|
# Arguments contain equal would be recognised as a variable
|
|
|
|
_message "value"
|
|
|
|
elif [[ $recipe ]]; then
|
|
|
|
# Show usage message
|
|
|
|
_message "`just --show $recipe`"
|
|
|
|
# Or complete with other commands
|
|
|
|
#_arguments -s -S $common '*:: :_just_commands'
|
|
|
|
else
|
|
|
|
_arguments -s -S $common '*:: :_just_commands'
|
|
|
|
fi
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
|
|
|
|
return ret
|
|
|
|
"#,
|
|
|
|
),
|
|
|
|
(
|
2024-05-14 20:29:40 -07:00
|
|
|
" local commands; commands=()",
|
2021-07-26 17:19:52 -07:00
|
|
|
r#" [[ $PREFIX = -* ]] && return 1
|
|
|
|
integer ret=1
|
|
|
|
local variables; variables=(
|
|
|
|
${(s: :)$(_call_program commands just --variables)}
|
|
|
|
)
|
|
|
|
local commands; commands=(
|
|
|
|
${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: }
|
|
|
|
)
|
|
|
|
"#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
r#" _describe -t commands 'just commands' commands "$@""#,
|
|
|
|
r#" if compset -P '*='; then
|
|
|
|
case "${${words[-1]%=*}#*=}" in
|
|
|
|
*) _message 'value' && ret=0 ;;
|
|
|
|
esac
|
|
|
|
else
|
|
|
|
_describe -t variables 'variables' variables -qS "=" && ret=0
|
|
|
|
_describe -t commands 'just commands' commands "$@"
|
|
|
|
fi
|
|
|
|
"#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
r#"_just "$@""#,
|
|
|
|
r#"(( $+functions[_just_variables] )) ||
|
|
|
|
_just_variables() {
|
|
|
|
[[ $PREFIX = -* ]] && return 1
|
|
|
|
integer ret=1
|
|
|
|
local variables; variables=(
|
|
|
|
${(s: :)$(_call_program commands just --variables)}
|
|
|
|
)
|
|
|
|
|
|
|
|
if compset -P '*='; then
|
|
|
|
case "${${words[-1]%=*}#*=}" in
|
|
|
|
*) _message 'value' && ret=0 ;;
|
|
|
|
esac
|
|
|
|
else
|
|
|
|
_describe -t variables 'variables' variables && ret=0
|
|
|
|
fi
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
_just "$@""#,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
2024-06-08 14:56:21 -07:00
|
|
|
const POWERSHELL_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
|
2021-07-26 17:19:52 -07:00
|
|
|
r#"$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
|
|
|
Sort-Object -Property ListItemText"#,
|
|
|
|
r#"function Get-JustFileRecipes([string[]]$CommandElements) {
|
|
|
|
$justFileIndex = $commandElements.IndexOf("--justfile");
|
|
|
|
|
2024-06-28 00:52:16 -07:00
|
|
|
if ($justFileIndex -ne -1 -and $justFileIndex + 1 -le $commandElements.Length) {
|
2021-07-26 17:19:52 -07:00
|
|
|
$justFileLocation = $commandElements[$justFileIndex + 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
$justArgs = @("--summary")
|
|
|
|
|
|
|
|
if (Test-Path $justFileLocation) {
|
|
|
|
$justArgs += @("--justfile", $justFileLocation)
|
|
|
|
}
|
|
|
|
|
|
|
|
$recipes = $(just @justArgs) -split ' '
|
|
|
|
return $recipes | ForEach-Object { [CompletionResult]::new($_) }
|
|
|
|
}
|
|
|
|
|
|
|
|
$elementValues = $commandElements | Select-Object -ExpandProperty Value
|
|
|
|
$recipes = Get-JustFileRecipes -CommandElements $elementValues
|
|
|
|
$completions += $recipes
|
|
|
|
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
|
|
|
Sort-Object -Property ListItemText"#,
|
|
|
|
)];
|
|
|
|
|
2024-06-08 14:56:21 -07:00
|
|
|
const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
2021-12-01 19:38:57 -08:00
|
|
|
(
|
|
|
|
r#" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
2021-07-26 17:19:52 -07:00
|
|
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
|
|
|
return 0
|
|
|
|
fi"#,
|
2021-12-01 19:38:57 -08:00
|
|
|
r#" if [[ ${cur} == -* ]] ; then
|
2021-07-26 17:19:52 -07:00
|
|
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
|
|
|
return 0
|
|
|
|
elif [[ ${COMP_CWORD} -eq 1 ]]; then
|
2022-09-23 20:34:55 -07:00
|
|
|
local recipes=$(just --summary 2> /dev/null)
|
|
|
|
|
2023-06-17 17:05:49 -07:00
|
|
|
if echo "${cur}" | \grep -qF '/'; then
|
2022-09-23 20:34:55 -07:00
|
|
|
local path_prefix=$(echo "${cur}" | sed 's/[/][^/]*$/\//')
|
|
|
|
local recipes=$(just --summary 2> /dev/null -- "${path_prefix}")
|
|
|
|
local recipes=$(printf "${path_prefix}%s\t" $recipes)
|
|
|
|
fi
|
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
if [[ $? -eq 0 ]]; then
|
|
|
|
COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
fi"#,
|
2021-12-01 19:38:57 -08:00
|
|
|
),
|
2024-01-27 14:02:38 -08:00
|
|
|
(
|
2024-05-14 20:29:40 -07:00
|
|
|
r"local i cur prev opts cmd",
|
|
|
|
r"local i cur prev words cword opts cmd",
|
2024-01-27 14:02:38 -08:00
|
|
|
),
|
|
|
|
(
|
|
|
|
r#" cur="${COMP_WORDS[COMP_CWORD]}"
|
|
|
|
prev="${COMP_WORDS[COMP_CWORD-1]}""#,
|
|
|
|
r#"
|
|
|
|
# Modules use "::" as the separator, which is considered a wordbreak character in bash.
|
|
|
|
# The _get_comp_words_by_ref function is a hack to allow for exceptions to this rule without
|
|
|
|
# modifying the global COMP_WORDBREAKS environment variable.
|
|
|
|
if type _get_comp_words_by_ref &>/dev/null; then
|
|
|
|
_get_comp_words_by_ref -n : cur prev words cword
|
|
|
|
else
|
|
|
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
|
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
|
|
words=$COMP_WORDS
|
|
|
|
cword=$COMP_CWORD
|
|
|
|
fi
|
|
|
|
"#,
|
|
|
|
),
|
|
|
|
(r"for i in ${COMP_WORDS[@]}", r"for i in ${words[@]}"),
|
|
|
|
(
|
|
|
|
r"elif [[ ${COMP_CWORD} -eq 1 ]]; then",
|
|
|
|
r"elif [[ ${cword} -eq 1 ]]; then",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )"#,
|
|
|
|
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
|
|
|
if type __ltrim_colon_completions &>/dev/null; then
|
|
|
|
__ltrim_colon_completions "$cur"
|
|
|
|
fi"#,
|
|
|
|
),
|
2021-12-01 19:38:57 -08:00
|
|
|
];
|