From 89c669bce2e7d89ddfe6a3823243c4d809666e99 Mon Sep 17 00:00:00 2001 From: "Wu, Zhenyu" Date: Mon, 24 Aug 2026 18:12:05 +0800 Subject: [PATCH] Added zsh/fish autocomplete scripts. --- CMakeLists.txt | 42 ++++++++++++ Insights.cpp | 8 ++- Readme.md | 19 +++++- scripts/bash-autocomplete.sh | 5 +- scripts/fish-autocomplete.fish | 43 ++++++++++++ scripts/zsh-autocomplete.zsh | 102 ++++++++++++++++++++++++++++ tests/shell/test-bash-completion.sh | 6 ++ tests/test-fish-completion.sh | 40 +++++++++++ tests/test-zsh-completion.sh | 78 +++++++++++++++++++++ 9 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 scripts/fish-autocomplete.fish create mode 100644 scripts/zsh-autocomplete.zsh create mode 100755 tests/test-fish-completion.sh create mode 100755 tests/test-zsh-completion.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 25b90fe7..bccf0571 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -584,6 +584,16 @@ if (NOT WIN32) # Not ready for Windows yet # + # Install shell completion scripts for the supported shells. The files are + # renamed to match each shell's expected completion-script name. + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/scripts/bash-autocomplete.sh + DESTINATION share/bash-completion/completions RENAME insights) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/scripts/zsh-autocomplete.zsh + DESTINATION share/zsh/site-functions RENAME _insights) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/scripts/fish-autocomplete.fish + DESTINATION share/fish/vendor_completions.d RENAME insights.fish) + + # additional includes we need when building outside the llvm-folder if (BUILD_INSIGHTS_OUTSIDE_LLVM) if(INSIGHTS_STRIP) @@ -644,6 +654,34 @@ else() find_package(Python3 COMPONENTS Interpreter ) endif() +# Detect zsh and fish for the shell-completion tests. They are optional; the +# corresponding tests are skipped when the shell is not installed. +find_program(ZSH_EXECUTABLE zsh) +find_program(FISH_EXECUTABLE fish) + +# Build the optional completion-test commands up front. When the shell is +# missing the variable is left empty, so the COMMAND in the test targets +# expands to nothing (no subcommand is generated). +if(ZSH_EXECUTABLE) + set(ZSH_COMPLETION_TEST + ${CMAKE_CURRENT_SOURCE_DIR}/tests/test-zsh-completion.sh + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell + ${CMAKE_CURRENT_SOURCE_DIR}/scripts/zsh-autocomplete.zsh) +else() + set(ZSH_COMPLETION_TEST "") +endif() + +if(FISH_EXECUTABLE) + set(FISH_COMPLETION_TEST + ${CMAKE_CURRENT_SOURCE_DIR}/tests/test-fish-completion.sh + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell + ${CMAKE_CURRENT_SOURCE_DIR}/scripts/fish-autocomplete.fish) +else() + set(FISH_COMPLETION_TEST "") +endif() + if (NOT Python3_FOUND) message(WARNING "Could not find the program Python3. Target tests disabled.") else() @@ -657,6 +695,8 @@ else() COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/testSTDIN.sh ${CMAKE_CURRENT_BINARY_DIR}/$ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/testInvalidOption.sh ${CMAKE_CURRENT_BINARY_DIR}/$ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell/test-bash-completion.sh ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell ${CMAKE_CURRENT_SOURCE_DIR}/scripts/bash-autocomplete.sh ${TEST_FAILURE_IS_OK} + COMMAND ${ZSH_COMPLETION_TEST} + COMMAND ${FISH_COMPLETION_TEST} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/$ ${CMAKE_CURRENT_SOURCE_DIR}/tests/runTest.py ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell/test-bash-completion.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests COMMENT "Running tests" VERBATIM @@ -669,6 +709,8 @@ if (NOT WIN32) COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/testSTDIN.sh ${CMAKE_CURRENT_BINARY_DIR}/insights COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/testInvalidOption.sh ${CMAKE_CURRENT_BINARY_DIR}/insights COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell/test-bash-completion.sh ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell ${CMAKE_CURRENT_SOURCE_DIR}/scripts/bash-autocomplete.sh + COMMAND ${ZSH_COMPLETION_TEST} + COMMAND ${FISH_COMPLETION_TEST} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/insights ${CMAKE_CURRENT_SOURCE_DIR}/tests/runTest.py ${CMAKE_CURRENT_SOURCE_DIR}/tests/shell/test-bash-completion.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests COMMENT "Running tests" VERBATIM diff --git a/Insights.cpp b/Insights.cpp index b6f9976d..c22ca5c8 100644 --- a/Insights.cpp +++ b/Insights.cpp @@ -388,7 +388,13 @@ extern struct __mptr* __vtbl_array[]; if(auto err = opExpected.takeError()) { if(gAutoComplete) { -#define INSIGHTS_OPT(option, name, deflt, description, category) llvm::outs() << "--" << option << " "; +// Emit options in the same format clang uses for its own --autocomplete, i.e. +// "option\tdescription" (a single TAB separates the option from its +// description). This lets every shell completion script reuse clang's parsing +// and show the description alongside the option. The leading "--" is added here +// so the bare option name from InsightsOptions.def can be reused directly. +#define INSIGHTS_OPT(option, name, deflt, description, category) \ + llvm::outs() << "--" << option << "\t" << description << "\n"; #include "InsightsOptions.def" diff --git a/Readme.md b/Readme.md index 9ff81339..ce33d8a6 100644 --- a/Readme.md +++ b/Readme.md @@ -263,10 +263,23 @@ At least for macOS, you can install C++ Insights via Homebrew thanks to [this fo brew install cppinsights ``` -## Bash autocomplete +## Shell autocomplete -There is a bash autocomplete script. It autocompletes the C++ Insights options as well as the Clang options provided after `--`. -You can download it with the following commands: +There are completion scripts for bash, zsh and fish. They autocomplete the C++ Insights options (including their descriptions) as well as the Clang options provided after `--`. + +When building from source, the completion scripts are installed automatically into the shell-specific directories under `share/` (configurable via `CMAKE_INSTALL_PREFIX`): + +``` +cmake --install build +``` + +This places the scripts at: + +* bash: `share/bash-completion/completions/insights` +* zsh: `share/zsh/site-functions/_insights` +* fish: `share/fish/vendor_completions.d/insights.fish` + +If you install C++ Insights yourself (or just want the script), you can download them individually: ``` cd diff --git a/scripts/bash-autocomplete.sh b/scripts/bash-autocomplete.sh index 52e1c791..0cb279b0 100644 --- a/scripts/bash-autocomplete.sh +++ b/scripts/bash-autocomplete.sh @@ -23,6 +23,8 @@ _insights_clang_options() [[ $i != $cword && "${COMP_WORDS[$(($i))]}" != '=' ]] && arg="$arg," done + # Each line is "option\tdescription" (TAB separated, matching clang's + # --autocomplete format). Keep only the option name (the first field). options=$( insights -- --autocomplete="$arg" 2>/dev/null | awk '{print $1}' | tr '\n' ' ' ) if [[ "$options" == "" || "$options" == " " ]]; then @@ -60,7 +62,8 @@ _insights() COMP_WORDS=$words COMP_CWORD=$cword - VALUES=$(insights --autocomplete) + # Each line is "option\tdescription" (TAB separated). Keep only the name. + VALUES=$(insights --autocomplete | awk '{print $1}') COMPREPLY=( $( compgen -W '-h --help --help-list --version -p --extra-arg --extra-arg-before $VALUES' -- "$cur" ) ) # Expanding files if nothing was provided diff --git a/scripts/fish-autocomplete.fish b/scripts/fish-autocomplete.fish new file mode 100644 index 00000000..741baaae --- /dev/null +++ b/scripts/fish-autocomplete.fish @@ -0,0 +1,43 @@ +# +# C++ Insights, copyright (C) by Andreas Fertig +# Distributed under an MIT license. See LICENSE for details +# +#------------------------------------------------------------------------------ +# fish completion for C++ Insights. +# +# The C++ Insights options (with their descriptions) are registered statically +# when this file is loaded: `insights --autocomplete` emits lines of the form +# "option\tdescription" (a single TAB, the same format clang uses), which we +# split on the TAB here. +# +# Options given after "--" are forwarded to Clang. Because fish's dynamic +# completions cannot attach a description to computed arguments, those are +# completed by name only. + +# Clear any previously registered insights completions (idempotent reload). +complete -c insights -e + +# Register the C++ Insights options (and their descriptions) statically. +for line in (insights --autocomplete 2>/dev/null | string split \n) + # Each line is "option\tdescription" (TAB separated). + set -l fields (string split -- (printf '\t') -- $line) + set -l opt $fields[1] + set -l desc $fields[2] + [ -z "$opt" ]; and continue + complete -c insights -l (string replace -- '--' '' $opt) -d "$desc" +end + +# A handful of built-in flags that are not part of InsightsOptions.def. +complete -c insights -s h -l help -d 'Display available options' +complete -c insights -s h -l help-list -d 'Display available options' +complete -c insights -l version -d 'Display the version' +complete -c insights -s p -l build-path -d 'Specify the build path' +complete -c insights -l extra-arg -d 'Additional argument appended to the compiler command line' +complete -c insights -l extra-arg-before -d 'Additional argument prepended to the compiler command line' + +# After "--" the remaining arguments are passed to Clang. Complete them by +# name (no description) using clang's own autocomplete via `insights --`. +complete -c insights -n '__fish_seen_argument --' \ + -a '(set -l cargs (commandline -opc); set -l idx (contains -i -- -- $cargs); set -l rest (string join , -- $cargs[(math $idx + 1)..-1]); insights -- --autocomplete="$rest" 2>/dev/null | string replace -r \'\t.*\' \'\')' + +# ex: ts=4 sw=4 et filetype=fish diff --git a/scripts/zsh-autocomplete.zsh b/scripts/zsh-autocomplete.zsh new file mode 100644 index 00000000..ec106065 --- /dev/null +++ b/scripts/zsh-autocomplete.zsh @@ -0,0 +1,102 @@ +#compdef insights +# +# C++ Insights, copyright (C) by Andreas Fertig +# Distributed under an MIT license. See LICENSE for details +# +#------------------------------------------------------------------------------ +# zsh completion for C++ Insights. +# +# Completes the C++ Insights options (with descriptions) as well as the Clang +# options provided after "--". The option list is obtained from `insights +# --autocomplete` and `insights -- --autocomplete=...` which both emit lines in +# the form "option\tdescription" (a single TAB, the same format clang uses). + +_insights_get_options() +{ + # $1 == "" -> C++ Insights options (insights --autocomplete) + # $1 == arg -> Clang options after "--" (insights -- --autocomplete="arg") + local raw + if [[ -z "$1" ]]; then + raw=$(insights --autocomplete 2>/dev/null) + else + raw=$(insights -- --autocomplete="$1" 2>/dev/null) + fi + + # Split each "option\tdescription" line into option + description. + local IFS=$'\n' + local line opt desc + _insights_opts=() + _insights_descs=() + for line in ${=raw}; do + opt=${line%%$'\t'*} + desc=${line#*$'\t'} + [[ -z "$opt" ]] && continue + _insights_opts+=("$opt") + _insights_descs+=("$desc") + done +} + +_insights() +{ + local sep_index=0 i j clang_args arg builtin_opt builtin_desc + + # Detect a "--" separator: everything after it is passed to Clang. + for (( i = 1; i <= ${#words[@]}; i++ )); do + if [[ "${words[i]}" == "--" ]]; then + sep_index=$i + break + fi + done + + if (( sep_index > 0 )); then + # Build the comma-separated argument list clang expects (words after "--"). + clang_args=("${words[@]:sep_index+1}") + arg="" + for (( j = 1; j <= ${#clang_args[@]}; j++ )); do + arg="$arg${clang_args[j-1]}" + [[ $j != ${#clang_args[@]} && "${clang_args[j]}" != '=' ]] && arg="$arg," + done + + _insights_get_options "$arg" + + if (( ${#_insights_opts[@]} == 0 )); then + _files + return + fi + + compadd -d _insights_descs -a _insights_opts + return + fi + + # No "--" yet: complete C++ Insights options plus a few built-in flags. + _insights_get_options "" + + local -a all_opts all_descs + all_opts=("${_insights_opts[@]}") + all_descs=("${_insights_descs[@]}") + + # A handful of built-in flags that are not part of InsightsOptions.def. + for builtin_desc in \ + '-h:Display available options' \ + '--help:Display available options' \ + '--help-list:Display available options' \ + '--version:Display the version' \ + '-p:Specify the build path' \ + '--extra-arg:Additional argument appended to the compiler command line' \ + '--extra-arg-before:Additional argument prepended to the compiler command line'; do + builtin_opt="${builtin_desc%%:*}" + all_opts+=("$builtin_opt") + all_descs+=("${builtin_desc#*:}") + done + + if (( ${#all_opts[@]} == 0 )); then + _files + return + fi + + compadd -d all_descs -a all_opts +} + +_insights "$@" + +# ex: ts=4 sw=4 et filetype=zsh diff --git a/tests/shell/test-bash-completion.sh b/tests/shell/test-bash-completion.sh index 2ff938b4..761488c4 100755 --- a/tests/shell/test-bash-completion.sh +++ b/tests/shell/test-bash-completion.sh @@ -8,6 +8,12 @@ PATH=$PATH:$1 failureIsOkay=$4 +# Redirect any LLVM profiling output (e.g. default.profraw written by an +# instrumented build) away from the sandbox directory so it does not pollute the +# file-completion list below. +export LLVM_PROFILE_FILE=/tmp/cppinsights-bash-test-%p.profraw +rm -f "$2"/*.profraw + cd $2 source $3 diff --git a/tests/test-fish-completion.sh b/tests/test-fish-completion.sh new file mode 100755 index 00000000..8c659e78 --- /dev/null +++ b/tests/test-fish-completion.sh @@ -0,0 +1,40 @@ +#! /bin/sh +# +# C++ Insights, copyright (C) by Andreas Fertig +# Distributed under an MIT license. See LICENSE for details +# +#------------------------------------------------------------------------------ +# Verifies the fish completion for C++ Insights: +# 1. `insights --autocomplete` emits "option\001description\001" lines. +# 2. `complete -C "insights --use"` offers --use-libc++ (with a description). + +build_dir=$1 +test_dir=$2 +script=$3 + +export PATH="$build_dir:$PATH" + +ret=0 + +echo "Running fish autocomplete tests..." + +# 1. The C++ backend must emit descriptions separated by a TAB. +if insights --autocomplete 2>/dev/null | grep -q "$(printf '\t')"; then + echo "[PASSED] insights --autocomplete emits descriptions" +else + echo "[FAILED] insights --autocomplete does not emit descriptions" + ret=1 +fi + +# 2. Completing "insights --use" must offer --use-libc++. +out=$(fish -c "source '$script'; complete -C 'insights --use'" 2>/dev/null) + +if echo "$out" | grep -q -- '--use-libc++'; then + echo "[PASSED] completing --use offers --use-libc++" +else + echo "[FAILED] completing --use does not offer --use-libc++" + echo "$out" + ret=1 +fi + +exit $ret diff --git a/tests/test-zsh-completion.sh b/tests/test-zsh-completion.sh new file mode 100755 index 00000000..df8b70ec --- /dev/null +++ b/tests/test-zsh-completion.sh @@ -0,0 +1,78 @@ +#! /bin/zsh +# +# C++ Insights, copyright (C) by Andreas Fertig +# Distributed under an MIT license. See LICENSE for details +# +#------------------------------------------------------------------------------ +# Verifies the zsh completion for C++ Insights: +# 1. `insights --autocomplete` emits "option\001description\001" lines. +# 2. The _insights completion function returns the Insights options. +# 3. After "--", the function delegates to clang's autocomplete. + +build_dir=$1 +test_dir=$2 +script=$3 + +export PATH="$build_dir:$PATH" + +ret=0 + +echo "Running zsh autocomplete tests..." + +# 1. The C++ backend must emit descriptions separated by a TAB. +if insights --autocomplete 2>/dev/null | grep -q $'\t'; then + echo "[PASSED] insights --autocomplete emits descriptions" +else + echo "[FAILED] insights --autocomplete does not emit descriptions" + ret=1 +fi + +# 2. Completing "--use" must offer --use-libc++ with a description. +out=$(zsh -fc " + source '$script' + words=(insights --use) + CURRENT=2 + _insights_opts=() + _insights_descs=() + _insights + for ((i=1;i<=\${#_insights_opts[@]};i++)); do + echo \"\${_insights_opts[i]}\t\${_insights_descs[i]}\" + done +" 2>/dev/null) + +if echo "$out" | grep -q -- '--use-libc++'; then + echo "[PASSED] completing --use offers --use-libc++" +else + echo "[FAILED] completing --use does not offer --use-libc++" + echo "$out" + ret=1 +fi + +if echo "$out" | grep -- '--use-libc++' | grep -q $'\t'; then + echo "[PASSED] --use-libc++ has a description" +else + echo "[FAILED] --use-libc++ is missing a description" + echo "$out" + ret=1 +fi + +# 3. After "--", clang options must be offered (e.g. -std=). +out=$(zsh -fc " + source '$script' + words=(insights -- -std) + CURRENT=3 + _insights_opts=() + _insights_descs=() + _insights + echo \"\${_insights_opts[@]}\" +" 2>/dev/null) + +if echo "$out" | grep -q -- '-std='; then + echo "[PASSED] after -- offers clang -std=" +else + echo "[FAILED] after -- does not offer clang -std=" + echo "$out" + ret=1 +fi + +exit $ret