Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ serial_test = "4.0.0"
sha1 = "0.11.0"
sha2 = "0.11.0"
shell-escape = "0.1.5"
shlex = "2.0.1"
simd-utf16-len = "0.3.0"
simdutf8 = "0.1.5"
smallvec = { version = "1.16.0", features = ["union"] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
set -eu
vp_bin=$(command -v vp)
VP_COMPLETE=bash "$vp_bin" > completion.bash
source completion.bash
COMP_WORDBREAKS=' :='
COMP_TYPE=9
vp() {
local IFS=" "
[[ $_CLAP_COMPLETE_INDEX == "$expected_index" && "$*" == "$expected_args" ]] || {
printf 'unexpected completion argv: index=%s %s\n' "$_CLAP_COMPLETE_INDEX" "$*" >&2
return 1
}
printf 'build:prod'
}
for invocation in vp vpr /path/vpr; do
for style in separate attached equals; do
case $style in
separate) COMP_WORDS=("$invocation" -C 'apps\ web'); expected_index=4 ;;
attached) COMP_WORDS=("$invocation" '-Capps\ web'); expected_index=3 ;;
equals) COMP_WORDS=("$invocation" -C = 'apps\ web'); expected_index=3 ;;
esac
expected_args="-- $invocation"
case $style in
separate) expected_args+=' -C apps\ web' ;;
attached) expected_args+=' -Capps\ web' ;;
equals) expected_args+=' -C=apps\ web' ;;
esac
[[ $invocation != vp ]] || COMP_WORDS+=(run)
expected_args+=' run build:pr'
COMP_WORDS+=(build : pr)
COMP_CWORD=$((${#COMP_WORDS[@]} - 1))
COMP_LINE="${COMP_WORDS[*]}"
COMP_LINE=${COMP_LINE//build : pr/build:pr}
[[ $style != equals ]] || COMP_LINE=${COMP_LINE//-C = /-C=}
_clap_complete_vp || exit 1
[[ ${COMPREPLY[*]} == prod ]]
done
done
mkdir -p 'apps web/unique-child'
for dir in 'apps\ web' "'apps web'" '"apps web"'; do
result=$(VP_COMPLETE=bash _CLAP_COMPLETE_INDEX=4 "$vp_bin" -- vp -C "$dir" -C unique)
[[ $result == unique-child* ]]
done
printf 'completion argv, colon trimming, and escaped cwd: ok\n'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[[case]]
name = "completion_chdir"
vp = "global"
skip-platforms = ["windows"]
requires = ["bash"]
steps = [["bash", "--noprofile", "--norc", "assert_completion.bash"]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# completion_chdir

## `bash --noprofile --norc assert_completion.bash`

```
completion argv, colon trimming, and escaped cwd: ok
```
1 change: 1 addition & 0 deletions crates/vp_global_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
console = { workspace = true }
same-file = { workspace = true }
shlex = { workspace = true }
oxc_resolver = { workspace = true }
pathdiff = { workspace = true }
vt_pty_terminal_test_client = { workspace = true, features = ["testing"] }
Expand Down
32 changes: 18 additions & 14 deletions crates/vp_global_cli/completion-register.bash
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
_clap_reassemble_words() {
if [[ "$COMP_WORDBREAKS" != *:* ]]; then
if [[ "$COMP_WORDBREAKS" != *:* && "$COMP_WORDBREAKS" != *=* ]]; then
return
fi
local i j=0 line=$COMP_LINE
words=()
_CLAP_COMPLETE_INDEX=0
for ((i = 0; i < ${#COMP_WORDS[@]}; i++)); do
if ((i > 0 && j > 0)) && [[ "${COMP_WORDS[i]}" == :* || "${words[j-1]}" == *: ]] && [[ "$line" != [[:blank:]]* ]]; then
if ((i > 0 && j > 0)) && [[ "${COMP_WORDS[i]}" == [:=]* || "${words[j-1]}" == *[:=] ]] && [[ "$line" != [[:blank:]]* ]]; then
words[j-1]="${words[j-1]}${COMP_WORDS[i]}"
else
words[j]="${COMP_WORDS[i]}"
Expand All @@ -21,18 +21,18 @@ _clap_reassemble_words() {

_clap_trim_completions() {
local cur="${words[_CLAP_COMPLETE_INDEX]}"
if [[ "$cur" != *:* || "$COMP_WORDBREAKS" != *:* ]]; then
local breaks=${COMP_WORDBREAKS//[^:=]}
if [[ ! $breaks || "$cur" != *["$breaks"]* ]]; then
return
fi
local colon_word=${cur%"${cur##*:}"}
local colon_word=${cur%"${cur##*["$breaks"]}"}
local i=${#COMPREPLY[*]}
while [[ $((--i)) -ge 0 ]]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
done
}

_clap_complete_vp() {
local IFS=$'\013'
local _CLAP_COMPLETE_INDEX=${COMP_CWORD}
local _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
if compopt +o nospace 2> /dev/null; then
Expand All @@ -42,6 +42,17 @@ _clap_complete_vp() {
fi
local words=("${COMP_WORDS[@]}")
_clap_reassemble_words
local insert=1
if [[ ${words[0]##*/} == vpr ]]; then
if [[ ${words[1]-} == -C ]]; then insert=3
elif [[ ${words[1]-} == -C?* ]]; then insert=2
fi
if (( _CLAP_COMPLETE_INDEX >= insert )); then
words=("${words[@]:0:insert}" run "${words[@]:insert}")
((_CLAP_COMPLETE_INDEX++))
fi
fi
local IFS=$'\013'
COMPREPLY=( $( \
_CLAP_IFS="$IFS" \
_CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
Expand All @@ -58,17 +69,10 @@ _clap_complete_vp() {
_clap_trim_completions
}

_clap_complete_vpr() {
local COMP_WORDS=("vp" "run" "${COMP_WORDS[@]:1}")
local COMP_CWORD=$((COMP_CWORD + 1))
local COMP_LINE="vp run ${COMP_LINE#vpr}"
_clap_complete_vp
}

if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -o nospace -o bashdefault -o nosort -F _clap_complete_vp vp
complete -o nospace -o bashdefault -o nosort -F _clap_complete_vpr vpr
complete -o nospace -o bashdefault -o nosort -F _clap_complete_vp vpr
else
complete -o nospace -o bashdefault -F _clap_complete_vp vp
complete -o nospace -o bashdefault -F _clap_complete_vpr vpr
complete -o nospace -o bashdefault -F _clap_complete_vp vpr
fi
2 changes: 1 addition & 1 deletion crates/vp_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub struct Args {
pub version: bool,

/// Run as if vp was started in <DIR> instead of the current working directory
#[arg(short = 'C', value_name = "DIR")]
#[arg(short = 'C', value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
pub chdir: Option<String>,

#[clap(subcommand)]
Expand Down
15 changes: 13 additions & 2 deletions crates/vp_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,21 @@ async fn main() -> ExitCode {
// the target directory here too: cwd-sensitive completers (run tasks)
// should suggest from <dir>, where the completed command will run.
if env::var_os("VP_COMPLETE").is_some()
&& let Some((dir, _)) = parse_leading_chdir(&args[1..])
&& let Some(sep) = args.iter().position(|a| a == "--")
&& let Some(user_args) = args.get(sep + 2..)
&& let Some((mut dir, consumed)) = parse_leading_chdir(user_args)
&& env::var("_CLAP_COMPLETE_INDEX")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(user_args.len())
> consumed
&& let Ok(current) = vt_path::current_dir()
{
// Best-effort and silent: mid-typing values are often not (yet) dirs.
if env::var_os("VP_COMPLETE").is_some_and(|s| s == "bash") {
let Some(parts) = shlex::split(&dir) else { return ExitCode::SUCCESS };
let [value] = parts.as_slice() else { return ExitCode::SUCCESS };
dir = value.clone();
}
let _ = apply_chdir(&current, &dir);
}
CompleteEnv::with_factory(command_for_completion).var("VP_COMPLETE").complete();
Expand Down
Loading