-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
//! Complete commands within shells | ||
/// Complete commands within bash | ||
pub mod bash { | ||
use std::io::Write; | ||
use unicode_xid::UnicodeXID; | ||
|
||
/// The recommended file name for the registration code | ||
pub fn file_name(name: &str) -> String { | ||
format!("{}.bash", name) | ||
} | ||
|
||
/// Define the completion behavior | ||
pub enum Behavior { | ||
/// Bare bones behavior | ||
Minimal, | ||
/// Fallback to readline behavior when no matches are generated | ||
Readline, | ||
/// Customize bash's completion behavior | ||
Custom(String), | ||
} | ||
|
||
/// Generate code to register the dynamic completion | ||
pub fn register( | ||
name: &str, | ||
executables: impl IntoIterator<Item = impl AsRef<str>>, | ||
completer: &str, | ||
behavior: &Behavior, | ||
buf: &mut dyn Write, | ||
) -> Result<(), std::io::Error> { | ||
let escaped_name = name.replace("-", "_"); | ||
debug_assert!( | ||
escaped_name.chars().all(|c| c.is_xid_continue()), | ||
"`name` must be an identifier, got `{}`", | ||
escaped_name | ||
); | ||
let mut upper_name = escaped_name.clone(); | ||
upper_name.make_ascii_uppercase(); | ||
|
||
let executables = executables | ||
.into_iter() | ||
.map(|s| shlex::quote(s.as_ref()).into_owned()) | ||
.collect::<Vec<_>>() | ||
.join(" "); | ||
|
||
let options = match behavior { | ||
Behavior::Minimal => "-o nospace -o bashdefault", | ||
Behavior::Readline => "-o nospace -o default -o bashdefault", | ||
Behavior::Custom(c) => c.as_str(), | ||
}; | ||
|
||
let completer = shlex::quote(completer); | ||
|
||
let script = r#" | ||
# Run something, muting output or redirecting it to the debug stream | ||
# depending on the value of _ARC_DEBUG. | ||
# If UPPER_COMPLETE_USE_TEMPFILES is set, use tempfiles for IPC. | ||
__clap_complete_run_NAME() { | ||
if [[ -z "${UPPER_COMPLETE_USE_TEMPFILES-}" ]]; then | ||
__clap_complete_run_inner_NAME "$@" | ||
return | ||
fi | ||
local tmpfile="$(mktemp)" | ||
CLAP_COMPLETE_STDOUT_FILE="$tmpfile" __clap_complete_run_inner_NAME "$@" | ||
local code=$? | ||
cat "$tmpfile" | ||
rm "$tmpfile" | ||
return $code | ||
} | ||
__clap_complete_run_inner_NAME() { | ||
if [[ -z "${_ARC_DEBUG-}" ]]; then | ||
"$@" 8>&1 9>&2 1>/dev/null 2>&1 | ||
else | ||
"$@" 8>&1 9>&2 1>&9 2>&1 | ||
fi | ||
} | ||
_clap_complete_NAME() { | ||
local IFS=$'\013' | ||
local SUPPRESS_SPACE=0 | ||
if compopt +o nospace 2> /dev/null; then | ||
SUPPRESS_SPACE=1 | ||
fi | ||
COMPREPLY=( $(IFS="$IFS" \ | ||
COMP_LINE="$COMP_LINE" \ | ||
COMP_POINT="$COMP_POINT" \ | ||
COMP_TYPE="$COMP_TYPE" \ | ||
CLAP_COMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \ | ||
CLAP_COMPLETE=1 \ | ||
CLAP_COMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \ | ||
__clap_complete_run_NAME COMPLETER) ) | ||
if [[ $? != 0 ]]; then | ||
unset COMPREPLY | ||
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then | ||
compopt -o nospace | ||
fi | ||
} | ||
complete OPTIONS -F _clap_complete_NAME EXECUTABLES | ||
"# | ||
.replace("NAME", &escaped_name) | ||
.replace("EXECUTABLES", &executables) | ||
.replace("OPTIONS", options) | ||
.replace("COMPLETER", &completer) | ||
.replace("UPPER", &upper_name); | ||
|
||
writeln!(buf, "{}", script)?; | ||
Ok(()) | ||
} | ||
|
||
/// Complete the command specified by env setup by [`register`] | ||
pub fn complete_env(buf: &mut dyn Write) -> Result<(), std::io::Error> { | ||
let comp_line = std::env::var_os("COMP_LINE") | ||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "`COMP_LINE` not set"))?; | ||
let comp_point = std::env::var_os("COMP_POINT").ok_or_else(|| { | ||
std::io::Error::new(std::io::ErrorKind::Other, "`COMP_POINT` not set") | ||
})?; | ||
let comp_point = comp_point.to_str().ok_or_else(|| { | ||
std::io::Error::new(std::io::ErrorKind::Other, "`COMP_POINT` format is invalid") | ||
})?; | ||
let comp_point = comp_point.parse::<usize>().map_err(|e| { | ||
std::io::Error::new( | ||
std::io::ErrorKind::Other, | ||
format!("`COMP_POINT` format is invalid: {}", e), | ||
) | ||
})?; | ||
complete(&comp_line, comp_point, buf)?; | ||
Ok(()) | ||
} | ||
|
||
/// Complete the command specified | ||
pub fn complete( | ||
comp_line: &std::ffi::OsStr, | ||
comp_point: usize, | ||
_buf: &mut dyn Write, | ||
) -> Result<(), std::io::Error> { | ||
let comp_line_raw = os_str_bytes::RawOsStr::new(comp_line); | ||
let comp_line_bytes = comp_line_raw.as_raw_bytes(); | ||
let _comp_line_prefix_bytes = comp_line_bytes.get(..comp_point).unwrap_or(comp_line_bytes); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
|
||
# Run something, muting output or redirecting it to the debug stream | ||
# depending on the value of _ARC_DEBUG. | ||
# If MY_APP_COMPLETE_USE_TEMPFILES is set, use tempfiles for IPC. | ||
__clap_complete_run_my_app() { | ||
if [[ -z "${MY_APP_COMPLETE_USE_TEMPFILES-}" ]]; then | ||
__clap_complete_run_inner_my_app "$@" | ||
return | ||
fi | ||
local tmpfile="$(mktemp)" | ||
CLAP_COMPLETE_STDOUT_FILE="$tmpfile" __clap_complete_run_inner_my_app "$@" | ||
local code=$? | ||
cat "$tmpfile" | ||
rm "$tmpfile" | ||
return $code | ||
} | ||
__clap_complete_run_inner_my_app() { | ||
if [[ -z "${_ARC_DEBUG-}" ]]; then | ||
"$@" 8>&1 9>&2 1>/dev/null 2>&1 | ||
else | ||
"$@" 8>&1 9>&2 1>&9 2>&1 | ||
fi | ||
} | ||
_clap_complete_my_app() { | ||
local IFS=$'/013' | ||
local SUPPRESS_SPACE=0 | ||
if compopt +o nospace 2> /dev/null; then | ||
SUPPRESS_SPACE=1 | ||
fi | ||
COMPREPLY=( $(IFS="$IFS" / | ||
COMP_LINE="$COMP_LINE" / | ||
COMP_POINT="$COMP_POINT" / | ||
COMP_TYPE="$COMP_TYPE" / | ||
CLAP_COMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" / | ||
CLAP_COMPLETE=1 / | ||
CLAP_COMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE / | ||
__clap_complete_run_my_app my-app) ) | ||
if [[ $? != 0 ]]; then | ||
unset COMPREPLY | ||
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then | ||
compopt -o nospace | ||
fi | ||
} | ||
complete -o nospace -o bashdefault -F _clap_complete_my_app my-app | ||
|