kakoune/rc/tools/clang.kak
Johannes Altmanninger 7b93567bb1 Reset SIGINT handler in background processes
In a noninteractive shell, asynchronous commands ignore SIGINT and
SIGQUIT. We typically use such shells to feed fifo buffers which we
do want to cancel them on Control-C.  Make it so.

Same for SIGQUIT; that one is not typically used but I expect that
along the Kakoune server it kills any child processes that (haven't
been daemonized).

Note that for unknown reasons, Bash already doesn't ignore SIGINT in
async processes that use "eval".

Note that Dash has a bug that prevents this from working;
proposed fix is at
https://lore.kernel.org/dash/20240329153905.154792-2-aclopte@gmail.com/

(While at it balance out some parens, to help the m command)
2024-03-31 20:13:35 +11:00

197 lines
8.8 KiB
Plaintext

hook -once global BufSetOption filetype=(c|cpp) %{
require-module clang
}
provide-module clang %[
declare-option -docstring "options to pass to the `clang` shell command" \
str clang_options
declare-option -docstring "directory from which to invoke clang" \
str clang_directory
declare-option -hidden completions clang_completions
declare-option -hidden line-specs clang_flags
declare-option -hidden line-specs clang_errors
define-command -params ..1 \
-docstring %{
Parse the contents of the current buffer
The syntaxic errors detected during parsing are shown when auto-diagnostics are enabled
} clang-parse %{
evaluate-commands %sh{
dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-clang.XXXXXXXX)
mkfifo ${dir}/fifo
printf %s\\n "
evaluate-commands -no-hooks write -sync -method replace ${dir}/buf
evaluate-commands -draft %{
edit! -fifo ${dir}/fifo -debug *clang-output*
set-option buffer filetype make
set-option buffer jump_current_line 0
hook -once -always buffer BufCloseFifo .* %{ nop %sh{ rm -r ${dir} } }
}"
# this runs in a detached shell, asynchronously, so that kakoune does
# not hang while clang is running. As completions references a cursor
# position and a buffer timestamp, only valid completions should be
# displayed.
((
trap - INT QUIT
until [ -f ${dir}/buf ]; do :; done # wait for the buffer to be written
if [ -n "$kak_opt_clang_directory" ]; then
cd "$kak_opt_clang_directory"
fi
case ${kak_opt_filetype} in
(c) ft=c ;;
(cpp) ft=c++ ;;
(obj-c) ft=objective-c ;;
(*) ft=c++ ;;
esac
if [ "$1" = "-complete" ]; then
pos=-:${kak_cursor_line}:${kak_cursor_column}
header="${kak_cursor_line}.${kak_cursor_column}@${kak_timestamp}"
compl=$(clang++ -x ${ft} -fsyntax-only ${kak_opt_clang_options} \
-Xclang -code-completion-brief-comments -Xclang -code-completion-at=${pos} - < ${dir}/buf 2> ${dir}/stderr |
awk -F ': ' '
/^COMPLETION:/ && $2 !~ /[(,](Hidden|Inaccessible)[),]/ {
candidate=$3
gsub(/[[<{]#[^#]*#[]>}]/, "", candidate)
gsub(/~/, "~~", candidate)
gsub(/\|/, "\\|", candidate)
gsub(/[[{<]#|#[]}>]/, " ", $3)
gsub(/:: /, "::", $3)
gsub(/ ,/, ",", $3)
gsub(/^ +| +$/, "", $3)
docstring=$4 ? $3 "\n" $4 : $3
gsub(/~|!/, "&&", docstring)
gsub(/\|/, "\\|", docstring)
if (candidate in candidates)
candidates[candidate]=candidates[candidate] "\n" docstring
else
candidates[candidate]=docstring
}
END {
for (candidate in candidates) {
menu=candidate
gsub(/(^|[^[:alnum:]_])(operator|new|delete)($|[^{}_[:alnum:]]+)/, "{keyword}&{}", menu)
gsub(/(^|[[:space:]])(int|size_t|bool|char|unsigned|signed|long)($|[[:space:]])/, "{type}&{}", menu)
gsub(/[^{}_[:alnum:]]+/, "{operator}&{}", menu)
printf "%%~%s|info -style menu %!%s!|%s~ ", candidate, candidates[candidate], menu
}
}')
printf %s\\n "evaluate-commands -client ${kak_client} echo 'clang completion done'
set-option 'buffer=${kak_buffile}' clang_completions ${header} ${compl}" | kak -p ${kak_session}
else
clang++ -x ${ft} -fsyntax-only ${kak_opt_clang_options} - < ${dir}/buf 2> ${dir}/stderr
printf %s\\n "evaluate-commands -client ${kak_client} echo 'clang parsing done'" | kak -p ${kak_session}
fi
flags=$(cat ${dir}/stderr | sed -Ene "
/^<stdin>:[0-9]+:([0-9]+:)? (fatal )?error/ { s/^<stdin>:([0-9]+):.*/'\1|{red}█'/; p }
/^<stdin>:[0-9]+:([0-9]+:)? warning/ { s/^<stdin>:([0-9]+):.*/'\1|{yellow}█'/; p }
" | paste -s -d ' ' -)
errors=$(cat ${dir}/stderr | sed -Ene "
/^<stdin>:[0-9]+:([0-9]+:)? ((fatal )?error|warning)/ {
s/'/''/g; s/^<stdin>:([0-9]+):([0-9]+:)? (.*)/'\1|\3'/; p
}" | sort -n | paste -s -d ' ' -)
sed -e "s|<stdin>|${kak_bufname}|g" < ${dir}/stderr > ${dir}/fifo
printf %s\\n "set-option 'buffer=${kak_buffile}' clang_flags ${kak_timestamp} ${flags}
set-option 'buffer=${kak_buffile}' clang_errors ${kak_timestamp} ${errors}" | kak -p ${kak_session}
) & ) > /dev/null 2>&1 < /dev/null
}
}
define-command clang-complete -docstring "Complete the current selection" %{ clang-parse -complete }
define-command -hidden clang-show-completion-info %[ try %[
evaluate-commands -draft %[
execute-keys ,{( <a-k> ^\( <ret> b <a-k> \A\w+\z <ret>
evaluate-commands %sh[
desc=$(printf %s\\n "${kak_opt_clang_completions}" | sed -e "{ s/\([^\\]\):/\1\n/g }" | sed -ne "/^${kak_selection}|/ { s/^[^|]\+|//; s/|.*$//; s/\\\:/:/g; p }")
if [ -n "$desc" ]; then
printf %s\\n "evaluate-commands -client $kak_client %{info -anchor ${kak_cursor_line}.${kak_cursor_column} -style above %{${desc}}}"
fi
] ]
] ]
define-command clang-enable-autocomplete -docstring "Enable automatic clang completion" %{
set-option window completers "option=clang_completions" %opt{completers}
hook window -group clang-autocomplete InsertIdle .* %{
try %{
execute-keys -draft <a-h><a-k>(\.|->|::).\z<ret>
echo 'completing...'
clang-complete
}
clang-show-completion-info
}
alias window complete clang-complete
}
define-command clang-disable-autocomplete -docstring "Disable automatic clang completion" %{
evaluate-commands %sh{ printf "set-option window completers %s\n" $(printf %s "${kak_opt_completers}" | sed -e "s/'option=clang_completions'//g") }
remove-hooks window clang-autocomplete
unalias window complete clang-complete
}
define-command -hidden clang-show-error-info %{
update-option buffer clang_errors # Ensure we are up to date with buffer changes
evaluate-commands %sh{
eval "set -- ${kak_quoted_opt_clang_errors}"
shift # skip timestamp
desc=$(for error in "$@"; do
if [ "${error%%|*}" = "$kak_cursor_line" ]; then
printf '%s\n' "${error##*|}"
fi
done)
if [ -n "$desc" ]; then
desc=$(printf %s "${desc}" | sed "s/'/''/g")
printf "info -anchor %d.%d '%s'\n" "${kak_cursor_line}" "${kak_cursor_column}" "${desc}"
fi
} }
define-command clang-enable-diagnostics -docstring %{
Activate automatic error reporting and diagnostics
Information about the analysis will be shown after the buffer has been parsed with the clang-parse function
} %{
add-highlighter window/clang_flags flag-lines default clang_flags
hook window -group clang-diagnostics NormalIdle .* %{ clang-show-error-info }
hook window -group clang-diagnostics WinSetOption ^clang_errors=.* %{ info; clang-show-error-info }
}
define-command clang-disable-diagnostics -docstring "Disable automatic error reporting and diagnostics" %{
remove-highlighter window/clang_flags
remove-hooks window clang-diagnostics
}
define-command clang-diagnostics-next -docstring "Jump to the next line that contains an error" %{
update-option buffer clang_errors # Ensure we are up to date with buffer changes
evaluate-commands %sh{
eval "set -- ${kak_quoted_opt_clang_errors}"
shift # skip timestamp
unset line
unset first_line
for error in "$@"; do
candidate=${error%%|*}
first_line=${first_line-$candidate}
if [ "$candidate" -gt $kak_cursor_line ]; then
line=$candidate
break
fi
done
line=${line-$first_line}
if [ -n "$line" ]; then
printf %s\\n "execute-keys ${line} g"
else
echo "fail no next clang diagnostic"
fi
} }
]