summaryrefslogtreecommitdiff
path: root/rc/tools
diff options
context:
space:
mode:
authorAlex Leferry 2 <alexherbo2@gmail.com>2019-03-18 19:56:34 +0100
committerAlex Leferry 2 <alexherbo2@gmail.com>2019-03-21 01:06:16 +0100
commitc0dccdd90dd615cf663d95fd94fbdbdf2a88b165 (patch)
treecb48fb1b7fb74e6e3b98a62f6e2768686bb75c98 /rc/tools
parentf87e844244d5ee81e9c1ceb04c354726002ae760 (diff)
Add categories in rc/
Closes #2783
Diffstat (limited to 'rc/tools')
-rw-r--r--rc/tools/autorestore.kak71
-rw-r--r--rc/tools/autowrap.kak48
-rw-r--r--rc/tools/clang.kak180
-rw-r--r--rc/tools/comment.kak180
-rw-r--r--rc/tools/ctags.kak123
-rw-r--r--rc/tools/doc.kak165
-rw-r--r--rc/tools/formatter.kak31
-rw-r--r--rc/tools/git.kak214
-rw-r--r--rc/tools/go/go-tools.kak182
-rw-r--r--rc/tools/grep.kak73
-rw-r--r--rc/tools/lint.kak176
-rw-r--r--rc/tools/make.kak84
-rw-r--r--rc/tools/man.kak68
-rw-r--r--rc/tools/python/jedi.kak47
-rw-r--r--rc/tools/ranger.kak59
-rw-r--r--rc/tools/rust/racer.kak153
-rw-r--r--rc/tools/spell.kak121
17 files changed, 1975 insertions, 0 deletions
diff --git a/rc/tools/autorestore.kak b/rc/tools/autorestore.kak
new file mode 100644
index 00000000..f8e3e14d
--- /dev/null
+++ b/rc/tools/autorestore.kak
@@ -0,0 +1,71 @@
+declare-option -docstring "remove backups once they've been restored" \
+ bool autorestore_purge_restored true
+
+## Insert the content of the backup file into the current buffer, if a suitable one is found
+define-command autorestore-restore-buffer -docstring "Restore the backup for the current file if it exists" %{
+ evaluate-commands %sh{
+ buffer_basename="${kak_buffile##*/}"
+ buffer_dirname=$(dirname "${kak_buffile}")
+
+ if [ -f "${kak_buffile}" ]; then
+ newer=$(find "${buffer_dirname}"/".${buffer_basename}.kak."* -newer "${kak_buffile}" -exec ls -1t {} + 2>/dev/null | head -n 1)
+
+ older=$(find "${buffer_dirname}"/".${buffer_basename}.kak."* \! -newer "${kak_buffile}" -exec ls -1t {} + 2>/dev/null | head -n 1)
+ else
+ # New buffers that were never written to disk.
+ newer=$(ls -1t "${buffer_dirname}"/".${buffer_basename}.kak."* 2>/dev/null | head -n 1)
+ fi
+
+ if [ -z "${newer}" ]; then
+ if [ -n "${older}" ]; then
+ printf %s\\n "
+ echo -debug Old backup file(s) found: will not restore ${older} .
+ "
+ fi
+ exit
+ fi
+
+ printf %s\\n "
+ ## Replace the content of the buffer with the content of the backup file
+ echo -debug Restoring file: ${newer}
+
+ execute-keys -draft %{ %d!cat<space>\"${newer}\"<ret>d }
+
+ ## If the backup file has to be removed, issue the command once
+ ## the current buffer has been saved
+ ## If the autorestore_purge_restored option has been unset right after the
+ ## buffer was restored, do not remove the backup
+ hook -group autorestore buffer BufWritePost '${kak_buffile}' %{
+ nop %sh{
+ if [ \"\${kak_opt_autorestore_purge_restored}\" = true ];
+ then
+ rm -f \"${buffer_dirname}/.${buffer_basename}.kak.\"*
+ fi
+ }
+ }
+ "
+ }
+}
+
+## Remove all the backups that have been created for the current buffer
+define-command autorestore-purge-backups -docstring "Remove all the backups of the current buffer" %{
+ evaluate-commands %sh{
+ [ ! -f "${kak_buffile}" ] && exit
+
+ buffer_basename="${kak_bufname##*/}"
+ buffer_dirname=$(dirname "${kak_bufname}")
+
+ rm -f "${buffer_dirname}/.${buffer_basename}.kak."*
+
+ printf %s\\n "
+ echo -markup {Information}Backup files removed.
+ "
+ }
+}
+
+## If for some reason, backup files need to be ignored
+define-command autorestore-disable -docstring "Disable automatic backup recovering" %{
+ remove-hooks global autorestore
+}
+
+hook -group autorestore global BufCreate .* %{ autorestore-restore-buffer }
diff --git a/rc/tools/autowrap.kak b/rc/tools/autowrap.kak
new file mode 100644
index 00000000..0fb12123
--- /dev/null
+++ b/rc/tools/autowrap.kak
@@ -0,0 +1,48 @@
+declare-option -docstring "maximum amount of characters per line, after which a newline character will be inserted" \
+ int autowrap_column 80
+
+declare-option -docstring %{when enabled, paragraph formatting will reformat the whole paragraph in which characters are being inserted
+This can potentially break formatting of documents containing markup (e.g. markdown)} \
+ bool autowrap_format_paragraph no
+declare-option -docstring %{command to which the paragraphs to wrap will be passed
+all occurences of '%c' are replaced with `autowrap_column`} \
+ str autowrap_fmtcmd 'fold -s -w %c'
+
+define-command -hidden autowrap-cursor %{ evaluate-commands -save-regs '/"|^@m' %{
+ try %{
+ ## if the line isn't too long, do nothing
+ execute-keys -draft "<a-x><a-k>^[^\n]{%opt{autowrap_column},}[^\n]<ret>"
+
+ try %{
+ reg m "%val{selections_desc}"
+
+ ## if we're adding characters past the limit, just wrap them around
+ execute-keys -draft "<a-h><a-k>.{%opt{autowrap_column}}\h*[^\s]*<ret>1s(\h+)[^\h]*\z<ret>c<ret>"
+ } catch %{
+ ## if we're adding characters in the middle of a sentence, use
+ ## the `fmtcmd` command to wrap the entire paragraph
+ evaluate-commands %sh{
+ if [ "${kak_opt_autowrap_format_paragraph}" = true ] \
+ && [ -n "${kak_opt_autowrap_fmtcmd}" ]; then
+ format_cmd=$(printf %s "${kak_opt_autowrap_fmtcmd}" \
+ | sed "s/%c/${kak_opt_autowrap_column}/g")
+ printf %s "
+ evaluate-commands -draft %{
+ execute-keys '<a-]>p<a-x><a-j>|${format_cmd}<ret>'
+ try %{ execute-keys s\h+$<ret> d }
+ }
+ select '${kak_main_reg_m}'
+ "
+ fi
+ }
+ }
+ }
+} }
+
+define-command autowrap-enable -docstring "Automatically wrap the lines in which characters are inserted" %{
+ hook -group autowrap window InsertChar [^\n] autowrap-cursor
+}
+
+define-command autowrap-disable -docstring "Disable automatic line wrapping" %{
+ remove-hooks window autowrap
+}
diff --git a/rc/tools/clang.kak b/rc/tools/clang.kak
new file mode 100644
index 00000000..3386073b
--- /dev/null
+++ b/rc/tools/clang.kak
@@ -0,0 +1,180 @@
+declare-option -docstring "options to pass to the `clang` shell command" \
+ str clang_options
+
+declare-option -hidden str clang_tmp_dir
+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 "set-option buffer clang_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks write -sync ${dir}/buf"
+ }
+ # end the previous %sh{} so that its output gets interpreted by kakoune
+ # before launching the following as a background task.
+ evaluate-commands %sh{
+ dir=${kak_opt_clang_tmp_dir}
+ printf %s\\n "evaluate-commands -draft %{
+ edit! -fifo ${dir}/fifo -debug *clang-output*
+ set-option buffer filetype make
+ set-option buffer make_current_error_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.
+ (
+ 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:/ && ! /\(Hidden\)/ {
+ id=$2
+ gsub(/ +$/, "", id)
+ gsub(/~/, "~~", id)
+ gsub(/\|/, "\\|", id)
+
+ gsub(/[[{<]#|#[}>]/, "", $3)
+ gsub(/#]/, " ", $3)
+ gsub(/:: /, "::", $3)
+ gsub(/ +$/, "", $3)
+ desc=$4 ? $3 "\n" $4 : $3
+
+ gsub(/~/, "~~", desc)
+ gsub(/\|/, "\\|", desc)
+ if (id in docstrings)
+ docstrings[id]=docstrings[id] "\n" desc
+ else
+ docstrings[id]=desc
+ }
+ END {
+ for (id in docstrings) {
+ menu=id
+ 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|%s|%s~ ", id, docstrings[id], 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 -rne "
+ /^<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 -rne "
+ /^<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 <space>{( <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} -placement 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_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 are showned 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_opt_clang_errors}"
+ shift # skip timestamp
+ 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 "echo -markup '{Error}no next clang diagnostic'"
+ fi
+ } }
diff --git a/rc/tools/comment.kak b/rc/tools/comment.kak
new file mode 100644
index 00000000..fbfb8267
--- /dev/null
+++ b/rc/tools/comment.kak
@@ -0,0 +1,180 @@
+# Line comments
+declare-option -docstring "characters inserted at the beginning of a commented line" \
+ str comment_line '#'
+
+# Block comments
+declare-option -docstring "characters inserted before a commented block" \
+ str comment_block_begin
+declare-option -docstring "characters inserted after a commented block" \
+ str comment_block_end
+
+# Default comments for all languages
+hook global BufSetOption filetype=asciidoc %{
+ set-option buffer comment_block_begin '///'
+ set-option buffer comment_block_end '///'
+}
+
+hook global BufSetOption filetype=(c|cpp|dart|go|java|javascript|objc|php|rust|sass|scala|scss|swift|typescript) %{
+ set-option buffer comment_line '//'
+ set-option buffer comment_block_begin '/*'
+ set-option buffer comment_block_end '*/'
+}
+
+hook global BufSetOption filetype=(cabal|haskell|moon|idris|elm) %{
+ set-option buffer comment_line '--'
+ set-option buffer comment_block_begin '{-'
+ set-option buffer comment_block_end '-}'
+}
+
+hook global BufSetOption filetype=clojure %{
+ set-option buffer comment_line '#_'
+ set-option buffer comment_block_begin '(comment '
+ set-option buffer comment_block_end ')'
+}
+
+hook global BufSetOption filetype=coffee %{
+ set-option buffer comment_block_begin '###'
+ set-option buffer comment_block_end '###'
+}
+
+hook global BufSetOption filetype=css %{
+ set-option buffer comment_line ''
+ set-option buffer comment_block_begin '/*'
+ set-option buffer comment_block_end '*/'
+}
+
+hook global BufSetOption filetype=d %{
+ set-option buffer comment_line '//'
+ set-option buffer comment_block_begin '/+'
+ set-option buffer comment_block_end '+/'
+}
+
+hook global BufSetOption filetype=(gas|ini) %{
+ set-option buffer comment_line ';'
+}
+
+hook global BufSetOption filetype=haml %{
+ set-option buffer comment_line '-#'
+}
+
+hook global BufSetOption filetype=(html|xml) %{
+ set-option buffer comment_line ''
+ set-option buffer comment_block_begin '<!--'
+ set-option buffer comment_block_end '-->'
+}
+
+hook global BufSetOption filetype=latex %{
+ set-option buffer comment_line '%'
+}
+
+hook global BufSetOption filetype=lisp %{
+ set-option buffer comment_line ';'
+ set-option buffer comment_block_begin '#|'
+ set-option buffer comment_block_end '|#'
+}
+
+hook global BufSetOption filetype=lua %{
+ set-option buffer comment_line '--'
+ set-option buffer comment_block_begin '--[['
+ set-option buffer comment_block_end ']]'
+}
+
+hook global BufSetOption filetype=markdown %{
+ set-option buffer comment_line ''
+ set-option buffer comment_block_begin '[//]'
+ set-option buffer comment_block_end '# (:)'
+}
+
+hook global BufSetOption filetype=perl %{
+ set-option buffer comment_block_begin '#['
+ set-option buffer comment_block_end ']'
+}
+
+hook global BufSetOption filetype=pug %{
+ set-option buffer comment_line '//'
+}
+
+hook global BufSetOption filetype=python %{
+ set-option buffer comment_block_begin "'''"
+ set-option buffer comment_block_end "'''"
+}
+
+hook global BufSetOption filetype=ragel %{
+ set-option buffer comment_line '%%'
+ set-option buffer comment_block_begin '%%{'
+ set-option buffer comment_block_end '}%%'
+}
+
+hook global BufSetOption filetype=ruby %{
+ set-option buffer comment_block_begin '^begin='
+ set-option buffer comment_block_end '^=end'
+}
+
+define-command comment-block -docstring '(un)comment selections using block comments' %{
+ evaluate-commands %sh{
+ if [ -z "${kak_opt_comment_block_begin}" ] || [ -z "${kak_opt_comment_block_end}" ]; then
+ echo "fail \"The 'comment_block' options are empty, could not comment the selection\""
+ fi
+ }
+ evaluate-commands -save-regs '"/' -draft %{
+ # Keep non-empty selections
+ execute-keys <a-K>\A\s*\z<ret>
+
+ try %{
+ # Assert that the selection has been commented
+ set-register / "\A\Q%opt{comment_block_begin}\E.*\Q%opt{comment_block_end}\E\n*\z"
+ execute-keys "s<ret>"
+ # Uncomment it
+ set-register / "\A\Q%opt{comment_block_begin}\E|\Q%opt{comment_block_end}\E\n*\z"
+ execute-keys s<ret>d
+ } catch %{
+ # Comment the selection
+ set-register '"' "%opt{comment_block_begin}"
+ execute-keys P
+ set-register '"' "%opt{comment_block_end}"
+ execute-keys p
+ }
+ }
+}
+
+define-command comment-line -docstring '(un)comment selected lines using line comments' %{
+ evaluate-commands %sh{
+ if [ -z "${kak_opt_comment_line}" ]; then
+ echo "fail \"The 'comment_line' option is empty, could not comment the line\""
+ fi
+ }
+ evaluate-commands -save-regs '"/' -draft %{
+ # Select the content of the lines, without indentation
+ execute-keys <a-s>gi<a-l>
+
+ try %{
+ # Keep non-empty lines
+ execute-keys <a-K>\A\s*\z<ret>
+ }
+
+ try %{
+ set-register / "\A\Q%opt{comment_line}\E\h?"
+
+ try %{
+ # See if there are any uncommented lines in the selection
+ execute-keys -draft <a-K><ret>
+
+ # There are uncommented lines, so comment everything
+ set-register '"' "%opt{comment_line} "
+ align-selections-left
+ execute-keys P
+ } catch %{
+ # All lines were commented, so uncomment everything
+ execute-keys s<ret>d
+ }
+ }
+ }
+}
+
+define-command align-selections-left -docstring 'extend selections to the left to align with the leftmost selected column' %{
+ evaluate-commands %sh{
+ leftmost_column=$(echo "$kak_selections_desc" | tr ' ' '\n' | cut -d',' -f1 | cut -d'.' -f2 | sort -n | head -n1)
+ aligned_selections=$(echo "$kak_selections_desc" | sed -E "s/\.[0-9]+,/.$leftmost_column,/g")
+ echo "select $aligned_selections"
+ }
+}
diff --git a/rc/tools/ctags.kak b/rc/tools/ctags.kak
new file mode 100644
index 00000000..20eabac0
--- /dev/null
+++ b/rc/tools/ctags.kak
@@ -0,0 +1,123 @@
+# Kakoune Exuberant CTags support script
+#
+# This script requires the readtags command available in ctags source but
+# not installed by default
+
+declare-option -docstring "list of paths to tag files to parse when looking up a symbol" \
+ str-list ctagsfiles 'tags'
+
+define-command -params ..1 \
+ -shell-script-candidates %{
+ realpath() { ( cd "$(dirname "$1")"; printf "%s/%s\n" "$(pwd -P)" "$(basename "$1")" ) }
+ eval "set -- $kak_opt_ctagsfiles"
+ for candidate in "$@"; do
+ [ -f "$candidate" ] && realpath "$candidate"
+ done | awk '!x[$0]++' | # remove duplicates
+ while read -r tags; do
+ namecache="${tags%/*}/.kak.${tags##*/}.namecache"
+ if [ -z "$(find "$namecache" -prune -newer "$tags")" ]; then
+ cut -f 1 "$tags" | grep -v '^!' | uniq > "$namecache"
+ fi
+ cat "$namecache"
+ done} \
+ -docstring %{ctags-search [<symbol>]: jump to a symbol's definition
+If no symbol is passed then the current selection is used as symbol name} \
+ ctags-search \
+ %{ evaluate-commands %sh{
+ realpath() { ( cd "$(dirname "$1")"; printf "%s/%s\n" "$(pwd -P)" "$(basename "$1")" ) }
+ export tagname=${1:-${kak_selection}}
+ eval "set -- $kak_opt_ctagsfiles"
+ for candidate in "$@"; do
+ [ -f "$candidate" ] && realpath "$candidate"
+ done | awk '!x[$0]++' | # remove duplicates
+ while read -r tags; do
+ printf '!TAGROOT\t%s\n' "$(realpath "${tags%/*}")/"
+ readtags -t "$tags" $tagname
+ done | awk -F '\t|\n' '
+ /^!TAGROOT\t/ { tagroot=$2 }
+ /[^\t]+\t[^\t]+\t\/\^.*\$?\// {
+ re=$0;
+ sub(".*\t/\\^", "", re); sub("\\$?/$", "", re); gsub("(\\{|\\}|\\\\E).*$", "", re);
+ keys=re; gsub(/</, "<lt>", keys); gsub(/\t/, "<c-v><c-i>", keys);
+ out = out " %{" $2 " {MenuInfo}" re "} %{evaluate-commands %{ try %{ edit %{" path($2) "}; execute-keys %{/\\Q" keys "<ret>vc} } catch %{ echo %{unable to find tag} } } }"
+ }
+ /[^\t]+\t[^\t]+\t[0-9]+/ { out = out " %{" $2 ":" $3 "} %{evaluate-commands %{ edit %{" path($2) "} %{" $3 "}}}" }
+ END { print ( length(out) == 0 ? "echo -markup %{{Error}no such tag " ENVIRON["tagname"] "}" : "menu -markup -auto-single " out ) }
+
+ # Ensure x is an absolute file path, by prepending with tagroot
+ function path(x) { return x ~/^\// ? x : tagroot x }'
+ }}
+
+define-command ctags-complete -docstring "Insert completion candidates for the current selection into the buffer's local variables" %{ evaluate-commands -draft %{
+ execute-keys <space>hb<a-k>^\w+$<ret>
+ nop %sh{ {
+ compl=$(readtags -p "$kak_selection" | cut -f 1 | sort | uniq | sed -e 's/:/\\:/g' | sed -e 's/\n/:/g' )
+ compl="${kak_cursor_line}.${kak_cursor_column}+${#kak_selection}@${kak_timestamp}:${compl}"
+ printf %s\\n "set-option buffer=$kak_bufname ctags_completions '${compl}'" | kak -p ${kak_session}
+ } > /dev/null 2>&1 < /dev/null & }
+}}
+
+define-command ctags-funcinfo -docstring "Display ctags information about a selected function" %{
+ evaluate-commands -draft %{
+ try %{
+ execute-keys '[(;B<a-k>[a-zA-Z_]+\(<ret><a-;>'
+ evaluate-commands %sh{
+ f=${kak_selection%?}
+ sig='\tsignature:(.*)'
+ csn='\t(class|struct|namespace):(\S+)'
+ sigs=$(readtags -e -Q '(eq? $kind "f")' "${f}" | sed -re "s/^.*${csn}.*${sig}$/\3 [\2::${f}]/ ;t ;s/^.*${sig}$/\1 [${f}]/")
+ if [ -n "$sigs" ]; then
+ printf %s\\n "evaluate-commands -client ${kak_client} %{info -anchor $kak_cursor_line.$kak_cursor_column -placement above '$sigs'}"
+ fi
+ }
+ }
+ }
+}
+
+define-command ctags-enable-autoinfo -docstring "Automatically display ctags information about function" %{
+ hook window -group ctags-autoinfo NormalIdle .* ctags-funcinfo
+ hook window -group ctags-autoinfo InsertIdle .* ctags-funcinfo
+}
+
+define-command ctags-disable-autoinfo -docstring "Disable automatic ctags information displaying" %{ remove-hooks window ctags-autoinfo }
+
+declare-option -docstring "shell command to run" \
+ str ctagscmd "ctags -R --fields=+S"
+declare-option -docstring "path to the directory in which the tags file will be generated" str ctagspaths "."
+
+define-command ctags-generate -docstring 'Generate tag file asynchronously' %{
+ echo -markup "{Information}launching tag generation in the background"
+ nop %sh{ (
+ while ! mkdir .tags.kaklock 2>/dev/null; do sleep 1; done
+ trap 'rmdir .tags.kaklock' EXIT
+
+ if ${kak_opt_ctagscmd} -f .tags.kaktmp ${kak_opt_ctagspaths}; then
+ mv .tags.kaktmp tags
+ msg="tags generation complete"
+ else
+ msg="tags generation failed"
+ fi
+
+ printf %s\\n "evaluate-commands -client $kak_client echo -markup '{Information}${msg}'" | kak -p ${kak_session}
+ ) > /dev/null 2>&1 < /dev/null & }
+}
+
+define-command ctags-update-tags -docstring 'Update tags for the given file' %{
+ nop %sh{ (
+ while ! mkdir .tags.kaklock 2>/dev/null; do sleep 1; done
+ trap 'rmdir .tags.kaklock' EXIT
+
+ if ${kak_opt_ctagscmd} -f .file_tags.kaktmp $kak_bufname; then
+ export LC_COLLATE=C LC_ALL=C # ensure ASCII sorting order
+ # merge the updated tags tags with the general tags (filtering out out of date tags from it) into the target file
+ grep -Fv "$(printf '\t%s\t' "$kak_bufname")" tags | grep -v '^!' | sort --merge - .file_tags.kaktmp >> .tags.kaktmp
+ rm .file_tags.kaktmp
+ mv .tags.kaktmp tags
+ msg="tags updated for $kak_bufname"
+ else
+ msg="tags update failed for $kak_bufname"
+ fi
+
+ printf %s\\n "evaluate-commands -client $kak_client echo -markup '{Information}${msg}'" | kak -p ${kak_session}
+ ) > /dev/null 2>&1 < /dev/null & }
+}
diff --git a/rc/tools/doc.kak b/rc/tools/doc.kak
new file mode 100644
index 00000000..f1dbc850
--- /dev/null
+++ b/rc/tools/doc.kak
@@ -0,0 +1,165 @@
+declare-option -docstring "name of the client in which documentation is to be displayed" \
+ str docsclient
+
+declare-option -hidden range-specs doc_render_ranges
+declare-option -hidden range-specs doc_render_links
+declare-option -hidden range-specs doc_links
+declare-option -hidden range-specs doc_anchors
+
+define-command -hidden -params 4 doc-render-regex %{
+ evaluate-commands -draft %{ try %{
+ execute-keys \%s %arg{1} <ret>
+ execute-keys -draft s %arg{2} <ret> d
+ execute-keys "%arg{3}"
+ evaluate-commands %sh{
+ face="$4"
+ eval "set -- $kak_selections_desc"
+ for desc in "$@"; do ranges="$ranges '$desc|$face'"; done
+ echo "update-option buffer doc_render_ranges"
+ echo "set-option -add buffer doc_render_ranges $ranges"
+ }
+ } }
+}
+
+define-command -hidden doc-parse-links %{
+ evaluate-commands -draft %{ try %{
+ execute-keys \%s <lt><lt>(.*?),.*?<gt><gt> <ret>
+ execute-keys -draft s <lt><lt>.*,|<gt><gt> <ret> d
+ execute-keys H
+ set-option buffer doc_links %val{timestamp}
+ set-option buffer doc_render_links %val{timestamp}
+ evaluate-commands -itersel %{
+ set-option -add buffer doc_links "%val{selection_desc}|%reg{1}"
+ set-option -add buffer doc_render_links "%val{selection_desc}|default+u"
+ }
+ } }
+}
+
+define-command -hidden doc-parse-anchors %{
+ evaluate-commands -draft %{ try %{
+ set-option buffer doc_anchors %val{timestamp}
+ # Find sections as add them as imlicit anchors
+ execute-keys \%s ^={2,}\h+([^\n]+)$ <ret>
+ evaluate-commands -itersel %{
+ set-option -add buffer doc_anchors "%val{selection_desc}|%sh{printf '%s' ""$kak_main_reg_1"" | tr '[A-Z ]' '[a-z-]'}"
+ }
+
+ # Parse explicit anchors and remove their text
+ execute-keys \%s \[\[(.*?)\]\]\s* <ret>
+ evaluate-commands -itersel %{
+ set-option -add buffer doc_anchors "%val{selection_desc}|%reg{1}"
+ }
+ execute-keys d
+ update-option buffer doc_anchors
+ } }
+}
+
+define-command doc-jump-to-anchor -params 1 %{
+ update-option buffer doc_anchors
+ evaluate-commands %sh{
+ anchor="$1"
+ eval "set -- $kak_opt_doc_anchors"
+
+ shift
+ for range in "$@"; do
+ if [ "${range#*|}" = "$anchor" ]; then
+ printf '%s\n' "select '${range%|*}'; execute-keys vv"
+ exit
+ fi
+ done
+ printf "echo -markup {Error}No such anchor '%s'" "${anchor}"
+ }
+}
+
+define-command doc-follow-link %{
+ update-option buffer doc_links
+ evaluate-commands %sh{
+ eval "set -- $kak_opt_doc_links"
+ for link in "$@"; do
+ printf '%s\n' "$link"
+ done | awk -v FS='[.,|#]' '
+ BEGIN {
+ l=ENVIRON["kak_cursor_line"];
+ c=ENVIRON["kak_cursor_column"];
+ }
+ l >= $1 && c >= $2 && l <= $3 && c <= $4 {
+ if (NF == 6) {
+ print "doc " $5
+ if ($6 != "") {
+ print "doc-jump-to-anchor %{" $6 "}"
+ }
+ } else {
+ print "doc-jump-to-anchor %{" $5 "}"
+ }
+ exit
+ }
+ '
+ }
+}
+
+define-command -params 1 -hidden doc-render %{
+ edit! -scratch "*doc-%sh{basename $1 .asciidoc}*"
+ execute-keys "!cat %arg{1}<ret>gg"
+
+ doc-parse-anchors
+
+ # Join paragraphs together
+ try %{
+ execute-keys -draft '%S\n{2,}|(?<=\+)\n|^[^\n]+::\n|^\h*[*-]\h+<ret>' \
+ <a-K>^\h*-{2,}(\n|\z)<ret> S\n\z<ret> <a-k>\n<ret> <a-j>
+ }
+
+ # Remove some line end markers
+ try %{ execute-keys -draft \%s \h*(\+|:{2,})$ <ret> d }
+
+ # Setup the doc_render_ranges option
+ set-option buffer doc_render_ranges %val{timestamp}
+ doc-render-regex \B(?<!\\)\*(?=\S)[^\n]+?(?<=\S)(?<!\\)\*\B \A|.\z 'H' default+b
+ doc-render-regex \b(?<!\\)_(?=\S)[^\n]+?(?<=\S)(?<!\\)_\b \A|.\z 'H' default+i
+ doc-render-regex \B(?<!\\)`(?=\S)[^\n]+?(?<=\S)(?<!\\)`\B \A|.\z 'H' mono
+ doc-render-regex ^=\h+[^\n]+ ^=\h+ '~' title
+ doc-render-regex ^={2,}\h+[^\n]+ ^={2,}\h+ '' header
+ doc-render-regex ^\h*-{2,}\n\h*.*?^\h*-{2,}\n ^\h*-{2,}\n '' block
+
+ doc-parse-links
+
+ # Remove escaping of * and `
+ try %{ execute-keys -draft \%s \\((?=\*)|(?=`)) <ret> d }
+
+ set-option buffer readonly true
+ add-highlighter buffer/ ranges doc_render_ranges
+ add-highlighter buffer/ ranges doc_render_links
+ add-highlighter buffer/ wrap -word -indent
+ map buffer normal <ret> ': doc-follow-link<ret>'
+}
+
+define-command -params 1..2 \
+ -shell-script-candidates %{
+ if [ "$kak_token_to_complete" -eq 0 ]; then
+ find "${kak_runtime}/doc/" -type f -name "*.asciidoc" | sed 's,.*/,,; s/\.[^/]*$//'
+ elif [ "$kak_token_to_complete" -eq 1 ]; then
+ readonly page="${kak_runtime}/doc/${1}.asciidoc"
+ if [ -f "${page}" ]; then
+ awk '
+ /^==+ +/ { sub(/^==+ +/, ""); print }
+ /^\[\[[^\]]+\]\]/ { sub(/^\[\[/, ""); sub(/\]\].*/, ""); print }
+ ' < $page | tr '[A-Z ]' '[a-z-]'
+ fi
+ fi
+ } \
+ doc -docstring %{doc <topic> [<keyword>]: open a buffer containing documentation about a given topic
+An optional keyword argument can be passed to the function, which will be automatically selected in the documentation} %{
+ evaluate-commands %sh{
+ readonly page="${kak_runtime}/doc/${1}.asciidoc"
+ if [ -f "${page}" ]; then
+ if [ $# -eq 2 ]; then
+ jump_cmd="doc-jump-to-anchor '$2'"
+ fi
+ printf %s\\n "evaluate-commands -try-client %opt{docsclient} %{ doc-render ${page}; ${jump_cmd} }"
+ else
+ printf %s\\n "echo -markup '{Error}No such doc file: ${page}'"
+ fi
+ }
+}
+
+alias global help doc
diff --git a/rc/tools/formatter.kak b/rc/tools/formatter.kak
new file mode 100644
index 00000000..40c38444
--- /dev/null
+++ b/rc/tools/formatter.kak
@@ -0,0 +1,31 @@
+declare-option -docstring "shell command to which the contents of the current buffer is piped" \
+ str formatcmd
+
+define-command format -docstring "Format the contents of the current buffer" %{ evaluate-commands -draft -no-hooks %{
+ evaluate-commands %sh{
+ if [ -n "${kak_opt_formatcmd}" ]; then
+ path_file_tmp=$(mktemp "${TMPDIR:-/tmp}"/kak-formatter-XXXXXX)
+ printf %s\\n "
+ write -sync \"${path_file_tmp}\"
+
+ evaluate-commands %sh{
+ readonly path_file_out=\$(mktemp \"${TMPDIR:-/tmp}\"/kak-formatter-XXXXXX)
+
+ if cat \"${path_file_tmp}\" | eval \"${kak_opt_formatcmd}\" > \"\${path_file_out}\"; then
+ printf '%s\\n' \"execute-keys \\%|cat<space>'\${path_file_out}'<ret>\"
+ printf '%s\\n' \"nop %sh{ rm -f '\${path_file_out}' }\"
+ else
+ printf '%s\\n' \"
+ evaluate-commands -client '${kak_client}' echo -markup '{Error}formatter returned an error (\$?)'
+ \"
+ rm -f \"\${path_file_out}\"
+ fi
+
+ rm -f \"${path_file_tmp}\"
+ }
+ "
+ else
+ printf '%s\n' "evaluate-commands -client '${kak_client}' echo -markup '{Error}formatcmd option not specified'"
+ fi
+ }
+} }
diff --git a/rc/tools/git.kak b/rc/tools/git.kak
new file mode 100644
index 00000000..21b5627b
--- /dev/null
+++ b/rc/tools/git.kak
@@ -0,0 +1,214 @@
+declare-option -docstring "name of the client in which documentation is to be displayed" \
+ str docsclient
+
+hook -group git-log-highlight global WinSetOption filetype=git-log %{
+ add-highlighter window/git-log group
+ add-highlighter window/git-log/ regex '^(commit) ([0-9a-f]+)$' 1:keyword 2:meta
+ add-highlighter window/git-log/ regex '^([a-zA-Z_-]+:) (.*?)$' 1:variable 2:value
+ add-highlighter window/git-log/ ref diff # highlight potential diffs from the -p option
+
+ hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-log }
+}
+
+
+hook -group git-status-highlight global WinSetOption filetype=git-status %{
+ add-highlighter window/git-status group
+ add-highlighter window/git-status/ regex '^\h+(?:((?:both )?modified:)|(added:|new file:)|(deleted(?: by \w+)?:)|(renamed:)|(copied:))(?:.*?)$' 1:yellow 2:green 3:red 4:cyan 5:blue 6:magenta
+
+ hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-status }
+}
+
+
+declare-option -hidden line-specs git_blame_flags
+declare-option -hidden line-specs git_diff_flags
+
+define-command -params 1.. \
+ -docstring %sh{printf 'git [<arguments>]: git wrapping helper
+All the optional arguments are forwarded to the git utility
+Available commands:\n add\n rm\n blame\n commit\n checkout\n diff\n hide-blame\n hide-diff\n log\n show\n show-diff\n status\n update-diff'} \
+ -shell-script-candidates %{
+ if [ $kak_token_to_complete -eq 0 ]; then
+ printf "add\nrm\nblame\ncommit\ncheckout\ndiff\nhide-blame\nhide-diff\nlog\nshow\nshow-diff\nstatus\nupdate-diff\n"
+ else
+ case "$1" in
+ commit) printf -- "--amend\n--no-edit\n--all\n--reset-author\n--fixup\n--squash\n"; git ls-files -m ;;
+ add) git ls-files -dmo --exclude-standard ;;
+ rm) git ls-files -c ;;
+ esac
+ fi
+ } \
+ git %{ evaluate-commands %sh{
+ show_git_cmd_output() {
+ local filetype
+ case "$1" in
+ show|diff) filetype=diff ;;
+ log) filetype=git-log ;;
+ status) filetype=git-status ;;
+ esac
+ output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-git.XXXXXXXX)/fifo
+ mkfifo ${output}
+ ( git "$@" > ${output} 2>&1 ) > /dev/null 2>&1 < /dev/null &
+
+ printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
+ edit! -fifo ${output} *git*
+ set-option buffer filetype '${filetype}'
+ hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
+ }"
+ }
+
+ run_git_blame() {
+ (
+ printf %s "evaluate-commands -client '$kak_client' %{
+ try %{ add-highlighter window/git-blame flag-lines Information git_blame_flags }
+ set-option buffer=$kak_bufname git_blame_flags '$kak_timestamp'
+ }" | kak -p ${kak_session}
+ git blame "$@" --incremental ${kak_buffile} | awk '
+ function send_flags(text, flag, i) {
+ if (line == "") { return; }
+ text=substr(sha,1,8) " " dates[sha] " " authors[sha]
+ # gsub("|", "\\|", text)
+ gsub("~", "~~", text)
+ flag="%~" line "|" text "~"
+ for ( i=1; i < count; i++ ) {
+ flag=flag " %~" line+i "|" text "~"
+ }
+ cmd = "kak -p " ENVIRON["kak_session"]
+ print "set-option -add buffer=" ENVIRON["kak_bufname"] " git_blame_flags " flag | cmd
+ close(cmd)
+ }
+ /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/ {
+ send_flags()
+ sha=$1
+ line=$3
+ count=$4
+ }
+ /^author / { authors[sha]=substr($0,8) }
+ /^author-time ([0-9]*)/ {
+ cmd = "date -d @" $2 " +\"%F %T\""
+ cmd | getline dates[sha]
+ close(cmd)
+ }
+ END { send_flags(); }'
+ ) > /dev/null 2>&1 < /dev/null &
+ }
+
+ update_diff() {
+ git --no-pager diff -U0 "$kak_buffile" | perl -e '
+ $flags = $ENV{"kak_timestamp"};
+ foreach $line (<STDIN>) {
+ if ($line =~ /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))?/) {
+ $from_line = $1;
+ $from_count = ($2 eq "" ? 1 : $2);
+ $to_line = $3;
+ $to_count = ($4 eq "" ? 1 : $4);
+
+ if ($from_count == 0 and $to_count > 0) {
+ for $i (0..$to_count - 1) {
+ $line = $to_line + $i;
+ $flags .= " $line|\{green\}+";
+ }
+ }
+ elsif ($from_count > 0 and $to_count == 0) {
+ if ($to_line == 0) {
+ $flags .= " 1|\{red\}‾";
+ } else {
+ $flags .= " $to_line|\{red\}_";
+ }
+ }
+ elsif ($from_count > 0 and $from_count == $to_count) {
+ for $i (0..$to_count - 1) {
+ $line = $to_line + $i;
+ $flags .= " $line|\{blue\}~";
+ }
+ }
+ elsif ($from_count > 0 and $from_count < $to_count) {
+ for $i (0..$from_count - 1) {
+ $line = $to_line + $i;
+ $flags .= " $line|\{blue\}~";
+ }
+ for $i ($from_count..$to_count - 1) {
+ $line = $to_line + $i;
+ $flags .= " $line|\{green\}+";
+ }
+ }
+ elsif ($to_count > 0 and $from_count > $to_count) {
+ for $i (0..$to_count - 2) {
+ $line = $to_line + $i;
+ $flags .= " $line|\{blue\}~";
+ }
+ $last = $to_line + $to_count - 1;
+ $flags .= " $last|\{blue+u\}~";
+ }
+ }
+ }
+ print "set-option buffer git_diff_flags $flags"
+ '
+ }
+
+ commit() {
+ # Handle case where message needs not to be edited
+ if grep -E -q -e "-m|-F|-C|--message=.*|--file=.*|--reuse-message=.*|--no-edit|--fixup.*|--squash.*"; then
+ if git commit "$@" > /dev/null 2>&1; then
+ echo 'echo -markup "{Information}Commit succeeded"'
+ else
+ echo 'echo -markup "{Error}Commit failed"'
+ fi
+ exit
+ fi <<-EOF
+ $@
+ EOF
+
+ # fails, and generate COMMIT_EDITMSG
+ GIT_EDITOR='' EDITOR='' git commit "$@" > /dev/null 2>&1
+ msgfile="$(git rev-parse --git-dir)/COMMIT_EDITMSG"
+ printf %s "edit '$msgfile'
+ hook buffer BufWritePost '.*\Q$msgfile\E' %{ evaluate-commands %sh{
+ if git commit -F '$msgfile' --cleanup=strip $* > /dev/null; then
+ printf %s 'evaluate-commands -client $kak_client echo -markup %{{Information}Commit succeeded}; delete-buffer'
+ else
+ printf %s 'evaluate-commands -client $kak_client echo -markup %{{Error}Commit failed}'
+ fi
+ } }"
+ }
+
+ case "$1" in
+ show|log|diff|status) show_git_cmd_output "$@" ;;
+ blame) shift; run_git_blame "$@" ;;
+ hide-blame)
+ printf %s "try %{
+ set-option buffer=$kak_bufname git_blame_flags $kak_timestamp
+ remove-highlighter window/git-blame
+ }"
+ ;;
+ show-diff)
+ echo 'try %{ add-highlighter window/git-diff flag-lines Default git_diff_flags }'
+ update_diff
+ ;;
+ hide-diff)
+ echo 'try %{ remove-highlighter window/git-diff }'
+ ;;
+ update-diff) update_diff ;;
+ commit) shift; commit "$@" ;;
+ checkout)
+ name="${2:-${kak_buffile}}"
+ git checkout "${name}" > /dev/null 2>&1
+ ;;
+ add)
+ name="${2:-${kak_buffile}}"
+ if git add -- "${name}" > /dev/null 2>&1; then
+ printf %s "echo -markup '{Information}git: added ${name}'"
+ else
+ printf %s "echo -markup '{Error}git: unable to add ${name}'"
+ fi
+ ;;
+ rm)
+ name="${2:-${kak_buffile}}"
+ if git rm -- "${name}" > /dev/null 2>&1; then
+ printf %s "echo -markup '{Information}git: removed ${name}'"
+ else
+ printf %s "echo -markup '{Error}git: unable to remove ${name}'"
+ fi
+ ;;
+ *) printf %s "echo -markup %{{Error}unknown git command '$1'}"; exit ;;
+ esac
+}}
diff --git a/rc/tools/go/go-tools.kak b/rc/tools/go/go-tools.kak
new file mode 100644
index 00000000..ee26698e
--- /dev/null
+++ b/rc/tools/go/go-tools.kak
@@ -0,0 +1,182 @@
+# Provides integration of the following tools:
+# - gocode for code completion (github.com/nsf/gocode)
+# - goimports for code formatting on save
+# - gogetdoc for documentation display and source jump (needs jq) (github.com/zmb3/gogetdoc)
+# Needs the following tools in the path:
+# - jq for json deserializaton
+
+evaluate-commands %sh{
+ for dep in gocode goimports gogetdoc jq; do
+ if ! command -v $dep > /dev/null 2>&1; then
+ echo "echo -debug %{Dependency unmet: $dep, please install it to use go-tools}"
+ fi
+ done
+}
+
+# Auto-completion
+# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+declare-option -hidden str go_complete_tmp_dir
+declare-option -hidden completions gocode_completions
+
+define-command go-complete -docstring "Complete the current selection with gocode" %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-go.XXXXXXXX)
+ printf %s\\n "set-option buffer go_complete_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks write ${dir}/buf"
+ }
+ nop %sh{
+ dir=${kak_opt_go_complete_tmp_dir}
+ (
+ gocode_data=$(gocode -f=godit --in=${dir}/buf autocomplete ${kak_cursor_byte_offset})
+ rm -r ${dir}
+ column_offset=$(printf %s "${gocode_data}" | head -n1 | cut -d, -f1)
+
+ header="${kak_cursor_line}.$((${kak_cursor_column} - $column_offset))@${kak_timestamp}"
+ compl=$(echo "${gocode_data}" | sed 1d | awk -F ",," '{
+ gsub(/~/, "~~", $1)
+ gsub(/~/, "~~", $2)
+ print "%~" $2 "||" $1 "~"
+ }' | paste -s -)
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ set-option 'buffer=${kak_bufname}' gocode_completions ${header} ${compl}
+ }" | kak -p ${kak_session}
+ ) > /dev/null 2>&1 < /dev/null &
+ }
+}
+
+define-command go-enable-autocomplete -docstring "Add gocode completion candidates to the completer" %{
+ set-option window completers "option=gocode_completions" %opt{completers}
+ hook window -group go-autocomplete InsertIdle .* %{ try %{
+ execute-keys -draft <a-h><a-k>[\w\.].\z<ret>
+ go-complete
+ } }
+ alias window complete go-complete
+}
+
+define-command go-disable-autocomplete -docstring "Disable gocode completion" %{
+ set-option window completers %sh{ printf %s\\n "${kak_opt_completers}" | sed "s/'option=gocode_completions'//g" }
+ remove-hooks window go-autocomplete
+ unalias window complete go-complete
+}
+
+# Auto-format
+# ‾‾‾‾‾‾‾‾‾‾‾
+
+declare-option -hidden str go_format_tmp_dir
+
+define-command -params ..1 go-format \
+ -docstring "go-format [-use-goimports]: custom formatter for go files" %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-go.XXXXXXXX)
+ printf %s\\n "set-option buffer go_format_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks write ${dir}/buf"
+ }
+ evaluate-commands %sh{
+ dir=${kak_opt_go_format_tmp_dir}
+ if [ "$1" = "-use-goimports" ]; then
+ fmt_cmd="goimports -srcdir '${kak_buffile}'"
+ else
+ fmt_cmd="gofmt -s"
+ fi
+ eval "${fmt_cmd} -e -w ${dir}/buf 2> ${dir}/stderr"
+ if [ $? ]; then
+ cp ${dir}/buf "${kak_buffile}"
+ else
+ # we should report error if linting isn't active
+ printf %s\\n "echo -debug '$(cat ${dir}/stderr)'"
+ fi
+ rm -r ${dir}
+ }
+ edit!
+}
+
+# Documentation
+# ‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+declare-option -hidden str go_doc_tmp_dir
+
+# FIXME text escaping
+define-command -hidden -params 1..2 gogetdoc-cmd %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-go.XXXXXXXX)
+ printf %s\\n "set-option buffer go_doc_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks write ${dir}/buf"
+ }
+ evaluate-commands %sh{
+ dir=${kak_opt_go_doc_tmp_dir}
+ (
+ printf %s\\n "${kak_buffile}" > ${dir}/modified
+ cat ${dir}/buf | wc -c >> ${dir}/modified
+ cat ${dir}/buf >> ${dir}/modified
+
+ if [ "$2" = "1" ]; then
+ args="-json"
+ fi
+ output=$(cat ${dir}/modified \
+ | gogetdoc $args -pos "${kak_buffile}:#${kak_cursor_byte_offset}" -modified \
+ | sed 's/%/%%/g')
+ rm -r ${dir}
+ printf %s "${output}" | grep -v -q "^gogetdoc: "
+ status=$?
+
+ case "$1" in
+ "info")
+ if [ ${status} -eq 0 ]; then
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ info -anchor ${kak_cursor_line}.${kak_cursor_column} %@${output}@
+ }" | kak -p ${kak_session}
+ else
+ msg=$(printf %s "${output}" | cut -d' ' -f2-)
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ echo '${msg}'
+ }" | kak -p ${kak_session}
+ fi
+ ;;
+ "echo")
+ if [ ${status} -eq 0 ]; then
+ signature=$(printf %s "${output}" | sed -n 3p)
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ echo '${signature}'
+ }" | kak -p ${kak_session}
+ fi
+ ;;
+ "jump")
+ if [ ${status} -eq 0 ]; then
+ pos=$(printf %s "${output}" | jq -r .pos)
+ file=$(printf %s "${pos}" | cut -d: -f1)
+ line=$(printf %s "${pos}" | cut -d: -f2)
+ col=$(printf %s "${pos}" | cut -d: -f3)
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ evaluate-commands -try-client '${kak_opt_jumpclient}' edit -existing ${file} ${line} ${col}
+ try %{ focus '${kak_opt_jumpclient}' }
+ }" | kak -p ${kak_session}
+ fi
+ ;;
+ *)
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ echo -error %{unkown command '$1'}
+ }" | kak -p ${kak_session}
+ ;;
+
+ esac
+ ) > /dev/null 2>&1 < /dev/null &
+ }
+}
+
+define-command go-doc-info -docstring "Show the documention of the symbol under the cursor" %{
+ gogetdoc-cmd "info"
+}
+
+define-command go-print-signature -docstring "Print the signature of the symbol under the cursor" %{
+ gogetdoc-cmd "echo"
+}
+
+define-command go-jump -docstring "Jump to the symbol definition" %{
+ gogetdoc-cmd "jump" 1
+}
+
+define-command go-share-selection -docstring "Share the selection using the Go Playground" %{ evaluate-commands %sh{
+ snippet_id=$(printf %s\\n "${kak_selection}" | curl -s https://play.golang.org/share --data-binary @-)
+ printf "echo https://play.golang.org/p/%s" ${snippet_id}
+} }
diff --git a/rc/tools/grep.kak b/rc/tools/grep.kak
new file mode 100644
index 00000000..9e6ed5ab
--- /dev/null
+++ b/rc/tools/grep.kak
@@ -0,0 +1,73 @@
+declare-option -docstring "shell command run to search for subtext in a file/directory" \
+ str grepcmd 'grep -RHn'
+declare-option -docstring "name of the client in which utilities display information" \
+ str toolsclient
+declare-option -hidden int grep_current_line 0
+
+define-command -params .. -file-completion \
+ -docstring %{grep [<arguments>]: grep utility wrapper
+All the optional arguments are forwarded to the grep utility} \
+ grep %{ evaluate-commands %sh{
+ output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-grep.XXXXXXXX)/fifo
+ mkfifo ${output}
+ if [ $# -gt 0 ]; then
+ ( ${kak_opt_grepcmd} "$@" | tr -d '\r' > ${output} 2>&1 ) > /dev/null 2>&1 < /dev/null &
+ else
+ ( ${kak_opt_grepcmd} "${kak_selection}" | tr -d '\r' > ${output} 2>&1 ) > /dev/null 2>&1 < /dev/null &
+ fi
+
+ printf %s\\n "evaluate-commands -try-client '$kak_opt_toolsclient' %{
+ edit! -fifo ${output} -scroll *grep*
+ set-option buffer filetype grep
+ set-option buffer grep_current_line 0
+ hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
+ }"
+}}
+
+hook -group grep-highlight global WinSetOption filetype=grep %{
+ add-highlighter window/grep group
+ add-highlighter window/grep/ regex "^((?:\w:)?[^:\n]+):(\d+):(\d+)?" 1:cyan 2:green 3:green
+ add-highlighter window/grep/ line %{%opt{grep_current_line}} default+b
+ hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/grep }
+}
+
+hook global WinSetOption filetype=grep %{
+ hook buffer -group grep-hooks NormalKey <ret> grep-jump
+ hook -once -always window WinSetOption filetype=.* %{ remove-hooks buffer grep-hooks }
+}
+
+declare-option -docstring "name of the client in which all source code jumps will be executed" \
+ str jumpclient
+
+define-command -hidden grep-jump %{
+ evaluate-commands %{ # use evaluate-commands to ensure jumps are collapsed
+ try %{
+ execute-keys '<a-x>s^((?:\w:)?[^:]+):(\d+):(\d+)?<ret>'
+ set-option buffer grep_current_line %val{cursor_line}
+ evaluate-commands -try-client %opt{jumpclient} edit -existing %reg{1} %reg{2} %reg{3}
+ try %{ focus %opt{jumpclient} }
+ }
+ }
+}
+
+define-command grep-next-match -docstring 'Jump to the next grep match' %{
+ evaluate-commands -try-client %opt{jumpclient} %{
+ buffer '*grep*'
+ # First jump to enf of buffer so that if grep_current_line == 0
+ # 0g<a-l> will be a no-op and we'll jump to the first result.
+ # Yeah, thats ugly...
+ execute-keys "ge %opt{grep_current_line}g<a-l> /^[^:]+:\d+:<ret>"
+ grep-jump
+ }
+ try %{ evaluate-commands -client %opt{toolsclient} %{ execute-keys gg %opt{grep_current_line}g } }
+}
+
+define-command grep-previous-match -docstring 'Jump to the previous grep match' %{
+ evaluate-commands -try-client %opt{jumpclient} %{
+ buffer '*grep*'
+ # See comment in grep-next-match
+ execute-keys "ge %opt{grep_current_line}g<a-h> <a-/>^[^:]+:\d+:<ret>"
+ grep-jump
+ }
+ try %{ evaluate-commands -client %opt{toolsclient} %{ execute-keys gg %opt{grep_current_line}g } }
+}
diff --git a/rc/tools/lint.kak b/rc/tools/lint.kak
new file mode 100644
index 00000000..11189315
--- /dev/null
+++ b/rc/tools/lint.kak
@@ -0,0 +1,176 @@
+declare-option -docstring %{shell command to which the path of a copy of the current buffer will be passed
+The output returned by this command is expected to comply with the following format:
+ {filename}:{line}:{column}: {kind}: {message}} \
+ str lintcmd
+
+declare-option -hidden line-specs lint_flags
+declare-option -hidden range-specs lint_errors
+declare-option -hidden int lint_error_count
+declare-option -hidden int lint_warning_count
+
+define-command lint -docstring 'Parse the current buffer with a linter' %{
+ evaluate-commands %sh{
+ if [ -z "${kak_opt_lintcmd}" ]; then
+ printf %s\\n 'echo -markup {Error}The `lintcmd` option is not set'
+ exit 1
+ fi
+
+ extension=""
+ if printf %s "${kak_buffile}" | grep -qE '[^/.]\.[[:alnum:]]+$'; then
+ extension=".${kak_buffile##*.}"
+ fi
+
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-lint.XXXXXXXX)
+ mkfifo "$dir"/fifo
+ printf '%s\n' "evaluate-commands -no-hooks write -sync $dir/buf${extension}"
+
+ printf '%s\n' "evaluate-commands -draft %{
+ edit! -fifo $dir/fifo -debug *lint-output*
+ set-option buffer filetype make
+ set-option buffer make_current_error_line 0
+ hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r '$dir' } }
+ }"
+
+ { # do the parsing in the background and when ready send to the session
+
+ eval "$kak_opt_lintcmd '$dir'/buf${extension}" | sort -t: -k2,2 -n > "$dir"/stderr
+
+ # Flags for the gutter:
+ # stamp l3|{red}█ l11|{yellow}█
+ # Contextual error messages:
+ # stamp 'l1.c1,l1.c1|kind:message' 'l2.c2,l2.c2|kind:message'
+ awk -F: -v file="$kak_buffile" -v stamp="$kak_timestamp" -v client="$kak_client" '
+ BEGIN {
+ error_count = 0
+ warning_count = 0
+ }
+ /:[1-9][0-9]*:[1-9][0-9]*: ([Ff]atal )?[Ee]rror/ {
+ flags = flags " " $2 "|{red}█"
+ error_count++
+ }
+ /:[1-9][0-9]*:[1-9][0-9]*:/ {
+ if ($4 !~ /[Ee]rror/) {
+ flags = flags " " $2 "|{yellow}█"
+ warning_count++
+ }
+ }
+ /:[1-9][0-9]*:[1-9][0-9]*:/ {
+ kind = substr($4, 2)
+ error = $2 "." $3 "," $2 "." $3 "|" kind
+ msg = ""
+ # fix case where $5 is not the last field because of extra colons in the message
+ for (i=5; i<=NF; i++) msg = msg ":" $i
+ gsub(/\|/, "\\|", msg)
+ gsub("'\''", "'"''"'", msg)
+ error = error msg " (col " $3 ")"
+ errors = errors " '\''" error "'\''"
+ }
+ END {
+ print "set-option \"buffer=" file "\" lint_flags " stamp flags
+ gsub("~", "\\~", errors)
+ print "set-option \"buffer=" file "\" lint_errors " stamp errors
+ print "set-option \"buffer=" file "\" lint_error_count " error_count
+ print "set-option \"buffer=" file "\" lint_warning_count " warning_count
+ print "evaluate-commands -client " client " lint-show-counters"
+ }
+ ' "$dir"/stderr | kak -p "$kak_session"
+
+ cut -d: -f2- "$dir"/stderr | awk -v bufname="${kak_bufname}" '
+ /^[1-9][0-9]*:[1-9][0-9]*:/ {
+ print bufname ":" $0
+ }
+ ' > "$dir"/fifo
+
+ } >/dev/null 2>&1 </dev/null &
+ }
+}
+
+define-command -hidden lint-show %{
+ update-option buffer lint_errors
+ evaluate-commands %sh{
+ eval "set -- ${kak_opt_lint_errors}"
+ shift
+
+ s=""
+ for i in "$@"; do
+ s="${s}
+${i}"
+ done
+
+ printf %s\\n "${s}" | awk -v line="${kak_cursor_line}" \
+ -v column="${kak_cursor_column}" \
+ "/^${kak_cursor_line}\./"' {
+ gsub(/"|%/, "&&")
+ msg = substr($0, index($0, "|"))
+ sub(/^[^ \t]+[ \t]+/, "", msg)
+ printf "info -anchor %d.%d \"%s\"\n", line, column, msg
+ }'
+ }
+}
+
+define-command -hidden lint-show-counters %{
+ echo -markup linting results:{red} %opt{lint_error_count} error(s){yellow} %opt{lint_warning_count} warning(s)
+}
+
+define-command lint-enable -docstring "Activate automatic diagnostics of the code" %{
+ add-highlighter window/lint flag-lines default lint_flags
+ hook window -group lint-diagnostics NormalIdle .* %{ lint-show }
+ hook window -group lint-diagnostics WinSetOption lint_flags=.* %{ info; lint-show }
+}
+
+define-command lint-disable -docstring "Disable automatic diagnostics of the code" %{
+ remove-highlighter window/lint
+ remove-hooks window lint-diagnostics
+}
+
+define-command lint-next-error -docstring "Jump to the next line that contains an error" %{
+ update-option buffer lint_errors
+
+ evaluate-commands %sh{
+ eval "set -- ${kak_opt_lint_errors}"
+ shift
+
+ for i in "$@"; do
+ candidate="${i%%|*}"
+ if [ "${candidate%%.*}" -gt "${kak_cursor_line}" ]; then
+ range="${candidate}"
+ break
+ fi
+ done
+
+ range="${range-${1%%|*}}"
+ if [ -n "${range}" ]; then
+ printf 'select %s\n' "${range}"
+ else
+ printf 'echo -markup "{Error}no lint diagnostics"\n'
+ fi
+ }
+}
+
+define-command lint-previous-error -docstring "Jump to the previous line that contains an error" %{
+ update-option buffer lint_errors
+
+ evaluate-commands %sh{
+ eval "set -- ${kak_opt_lint_errors}"
+ shift
+
+ for i in "$@"; do
+ candidate="${i%%|*}"
+
+ if [ "${candidate%%.*}" -ge "${kak_cursor_line}" ]; then
+ range="${last_candidate}"
+ break
+ fi
+
+ last_candidate="${candidate}"
+ done
+
+ if [ $# -ge 1 ]; then
+ shift $(($# - 1))
+ range="${range:-${1%%|*}}"
+ printf 'select %s\n' "${range}"
+ else
+ printf 'echo -markup "{Error}no lint diagnostics"\n'
+ fi
+ }
+}
diff --git a/rc/tools/make.kak b/rc/tools/make.kak
new file mode 100644
index 00000000..89533a59
--- /dev/null
+++ b/rc/tools/make.kak
@@ -0,0 +1,84 @@
+declare-option -docstring "shell command run to build the project" \
+ str makecmd make
+declare-option -docstring "pattern that describes lines containing information about errors in the output of the `makecmd` command" \
+ str make_error_pattern " (?:fatal )?error:"
+
+declare-option -docstring "name of the client in which utilities display information" \
+ str toolsclient
+declare-option -hidden int make_current_error_line
+
+define-command -params .. \
+ -docstring %{make [<arguments>]: make utility wrapper
+All the optional arguments are forwarded to the make utility} \
+ make %{ evaluate-commands %sh{
+ output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-make.XXXXXXXX)/fifo
+ mkfifo ${output}
+ ( eval ${kak_opt_makecmd} "$@" > ${output} 2>&1 ) > /dev/null 2>&1 < /dev/null &
+
+ printf %s\\n "evaluate-commands -try-client '$kak_opt_toolsclient' %{
+ edit! -fifo ${output} -scroll *make*
+ set-option buffer filetype make
+ set-option buffer make_current_error_line 0
+ hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
+ }"
+}}
+
+add-highlighter shared/make group
+add-highlighter shared/make/ regex "^((?:\w:)?[^:\n]+):(\d+):(?:(\d+):)?\h+(?:((?:fatal )?error)|(warning)|(note)|(required from(?: here)?))?.*?$" 1:cyan 2:green 3:green 4:red 5:yellow 6:blue 7:yellow
+add-highlighter shared/make/ regex "^\h*(~*(?:(\^)~*)?)$" 1:green 2:cyan+b
+add-highlighter shared/make/ line '%opt{make_current_error_line}' default+b
+
+hook -group make-highlight global WinSetOption filetype=make %{
+ add-highlighter window/make ref make
+ hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/make }
+}
+
+hook global WinSetOption filetype=make %{
+ hook buffer -group make-hooks NormalKey <ret> make-jump
+ hook -once -always window WinSetOption filetype=.* %{ remove-hooks buffer make-hooks }
+}
+
+declare-option -docstring "name of the client in which all source code jumps will be executed" \
+ str jumpclient
+
+define-command -hidden make-open-error -params 4 %{
+ evaluate-commands -try-client %opt{jumpclient} %{
+ edit -existing "%arg{1}" %arg{2} %arg{3}
+ echo -markup "{Information}%arg{4}"
+ try %{ focus }
+ }
+}
+
+define-command -hidden make-jump %{
+ evaluate-commands %{
+ try %{
+ execute-keys gl<a-?> "Entering directory" <ret><a-:>
+ # Try to parse the error into capture groups, failing on absolute paths
+ execute-keys s "Entering directory [`']([^']+)'.*\n([^:/][^:]*):(\d+):(?:(\d+):)?([^\n]+)\z" <ret>l
+ set-option buffer make_current_error_line %val{cursor_line}
+ make-open-error "%reg{1}/%reg{2}" "%reg{3}" "%reg{4}" "%reg{5}"
+ } catch %{
+ execute-keys <a-h><a-l> s "((?:\w:)?[^:]+):(\d+):(?:(\d+):)?([^\n]+)\z" <ret>l
+ set-option buffer make_current_error_line %val{cursor_line}
+ make-open-error "%reg{1}" "%reg{2}" "%reg{3}" "%reg{4}"
+ }
+ }
+}
+
+define-command make-next-error -docstring 'Jump to the next make error' %{
+ evaluate-commands -try-client %opt{jumpclient} %{
+ buffer '*make*'
+ execute-keys "%opt{make_current_error_line}ggl" "/^(?:\w:)?[^:\n]+:\d+:(?:\d+:)?%opt{make_error_pattern}<ret>"
+ make-jump
+ }
+ try %{ evaluate-commands -client %opt{toolsclient} %{ execute-keys %opt{make_current_error_line}g } }
+}
+
+define-command make-previous-error -docstring 'Jump to the previous make error' %{
+ evaluate-commands -try-client %opt{jumpclient} %{
+ buffer '*make*'
+ execute-keys "%opt{make_current_error_line}g" "<a-/>^(?:\w:)?[^:\n]+:\d+:(?:\d+:)?%opt{make_error_pattern}<ret>"
+ make-jump
+ }
+ try %{ evaluate-commands -client %opt{toolsclient} %{ execute-keys %opt{make_current_error_line}g } }
+}
diff --git a/rc/tools/man.kak b/rc/tools/man.kak
new file mode 100644
index 00000000..41d91104
--- /dev/null
+++ b/rc/tools/man.kak
@@ -0,0 +1,68 @@
+declare-option -docstring "name of the client in which documentation is to be displayed" \
+ str docsclient
+
+declare-option -hidden str manpage
+
+hook -group man-highlight global WinSetOption filetype=man %{
+ add-highlighter window/man-highlight group
+ # Sections
+ add-highlighter window/man-highlight/ regex ^\S.*?$ 0:title
+ # Subsections
+ add-highlighter window/man-highlight/ regex '^ {3}\S.*?$' 0:default+b
+ # Command line options
+ add-highlighter window/man-highlight/ regex '^ {7}-[^\s,]+(,\s+-[^\s,]+)*' 0:list
+ # References to other manpages
+ add-highlighter window/man-highlight/ regex [-a-zA-Z0-9_.]+\([a-z0-9]+\) 0:header
+
+ hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/man-highlight }
+}
+
+hook global WinSetOption filetype=man %{
+ hook -group man-hooks window WinResize .* %{ man-impl %val{bufname} %opt{manpage} }
+ hook -once -always window WinSetOption filetype=.* %{ remove-hooks window man-hooks }
+}
+
+define-command -hidden -params 2..3 man-impl %{ evaluate-commands %sh{
+ buffer_name="$1"
+ shift
+ manout=$(mktemp "${TMPDIR:-/tmp}"/kak-man-XXXXXX)
+ colout=$(mktemp "${TMPDIR:-/tmp}"/kak-man-XXXXXX)
+ MANWIDTH=${kak_window_width} man "$@" > $manout 2>/dev/null
+ retval=$?
+ col -b -x > ${colout} < ${manout}
+ rm ${manout}
+ if [ "${retval}" -eq 0 ]; then
+ printf %s\\n "
+ edit -scratch '$buffer_name'
+ execute-keys '%|cat<space>${colout}<ret>gk'
+ nop %sh{rm ${colout}}
+ set-option buffer filetype man
+ set-option window manpage '$@'
+ "
+ else
+ printf %s\\n "echo -markup %{{Error}man '$@' failed: see *debug* buffer for details}"
+ rm ${colout}
+ fi
+} }
+
+define-command -params ..1 \
+ -shell-script-candidates %{
+ find /usr/share/man/ -name '*.[1-8]*' | sed 's,^.*/\(.*\)\.\([1-8][a-zA-Z]*\).*$,\1(\2),'
+ } \
+ -docstring %{man [<page>]: manpage viewer wrapper
+If no argument is passed to the command, the selection will be used as page
+The page can be a word, or a word directly followed by a section number between parenthesis, e.g. kak(1)} \
+ man %{ evaluate-commands %sh{
+ subject=${1-$kak_selection}
+
+ ## The completion suggestions display the page number, strip them if present
+ case "${subject}" in
+ *\([1-8]*\))
+ pagenum="${subject##*(}"
+ pagenum="${pagenum%)}"
+ subject="${subject%%(*}"
+ ;;
+ esac
+
+ printf %s\\n "evaluate-commands -try-client %opt{docsclient} man-impl *man* $pagenum $subject"
+} }
diff --git a/rc/tools/python/jedi.kak b/rc/tools/python/jedi.kak
new file mode 100644
index 00000000..a0fe9449
--- /dev/null
+++ b/rc/tools/python/jedi.kak
@@ -0,0 +1,47 @@
+declare-option -hidden str jedi_tmp_dir
+declare-option -hidden completions jedi_completions
+declare-option -docstring "colon separated list of path added to `python`'s $PYTHONPATH environment variable" \
+ str jedi_python_path
+
+define-command jedi-complete -docstring "Complete the current selection" %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-jedi.XXXXXXXX)
+ mkfifo ${dir}/fifo
+ printf %s\\n "set-option buffer jedi_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks write -sync ${dir}/buf"
+ }
+ evaluate-commands %sh{
+ dir=${kak_opt_jedi_tmp_dir}
+ printf %s\\n "evaluate-commands -draft %{ edit! -fifo ${dir}/fifo *jedi-output* }"
+ (
+ cd $(dirname ${kak_buffile})
+ header="${kak_cursor_line}.${kak_cursor_column}@${kak_timestamp}"
+
+ export PYTHONPATH="$kak_opt_jedi_python_path:$PYTHONPATH"
+ compl=$(python 2> "${dir}/fifo" <<-END
+ import jedi
+ script=jedi.Script(open('$dir/buf', 'r').read(), $kak_cursor_line, $kak_cursor_column - 1, '$kak_buffile')
+ print(' '.join(["'" + (str(c.name).replace("|", "\\|") + "|" + str(c.docstring()).replace("|", "\\|") + "|" + str(c.name).replace("|", "\\|")).replace("~", "~~").replace("'", "''") + "'" for c in script.completions()]))
+ END
+ )
+ printf %s\\n "evaluate-commands -client ${kak_client} %~echo completed; set-option %{buffer=${kak_buffile}} jedi_completions ${header} ${compl}~" | kak -p ${kak_session}
+ rm -r ${dir}
+ ) > /dev/null 2>&1 < /dev/null &
+ }
+}
+
+define-command jedi-enable-autocomplete -docstring "Add jedi completion candidates to the completer" %{
+ set-option window completers option=jedi_completions %opt{completers}
+ hook window -group jedi-autocomplete InsertIdle .* %{ try %{
+ execute-keys -draft <a-h><a-k>\..\z<ret>
+ echo 'completing...'
+ jedi-complete
+ } }
+ alias window complete jedi-complete
+}
+
+define-command jedi-disable-autocomplete -docstring "Disable jedi completion" %{
+ set-option window completers %sh{ printf %s\\n "'${kak_opt_completers}'" | sed -e 's/option=jedi_completions://g' }
+ remove-hooks window jedi-autocomplete
+ unalias window complete jedi-complete
+}
diff --git a/rc/tools/ranger.kak b/rc/tools/ranger.kak
new file mode 100644
index 00000000..4bb49a7b
--- /dev/null
+++ b/rc/tools/ranger.kak
@@ -0,0 +1,59 @@
+# http://ranger.nongnu.org
+# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+define-command ranger-open-on-edit-directory \
+ -docstring 'Start the ranger file system explorer when trying to edit a directory' %{
+ hook global RuntimeError "\d+:\d+: '\w+' (.*): is a directory" %{ evaluate-commands %sh{
+ directory=$kak_hook_param_capture_1
+ echo ranger $directory
+ }}
+}
+
+define-command \
+ -params .. -file-completion \
+ -docstring %{ranger [<arguments>]: open the file system explorer to select buffers to open
+ All the optional arguments are forwarded to the ranger utility} \
+ ranger %{ evaluate-commands %sh{
+ if [ -n "${TMUX}" ]; then
+ tmux split-window -h \
+ ranger $@ --cmd " \
+ map <return> eval \
+ fm.execute_console('shell \
+ echo evaluate-commands -client ' + ranger.ext.shell_escape.shell_escape('$kak_client') + ' edit {file} | \
+ kak -p '.format(file=fm.thisfile.path) + ranger.ext.shell_escape.shell_escape('$kak_session') + '; \
+ tmux select-pane -t $kak_client_env_TMUX_PANE') \
+ if fm.thisfile.is_file else fm.execute_console('move right=1')"
+
+ elif [ -n "${STY}" ]; then
+
+ script="/tmp/kak-ranger-${kak_client}-${kak_session}.sh"
+ selections="/tmp/kak-ranger-${kak_client}-${kak_session}.txt"
+ cat > "$script" << EOF
+#! /usr/bin/env sh
+cd "$PWD"
+ranger --choosefiles="$selections" $@
+while read -r f; do
+ printf %s "evaluate-commands -client '${kak_client}' edit '\"\$f\"'" | kak -p '${kak_session}'
+done < "$selections"
+screen -X remove
+rm -f "$selections" "$script"
+EOF
+
+ tty="$(ps -o tty ${kak_client_pid} | tail -n 1)"
+ screen -X eval \
+ 'split -h' \
+ 'focus down' \
+ "screen sh '$script'" \
+ < "/dev/$tty"
+
+ elif [ -n "$WINDOWID" ]; then
+ setsid $kak_opt_termcmd " \
+ ranger $@ --cmd "'"'" \
+ map <return> eval \
+ fm.execute_console('shell \
+ echo evaluate-commands -client ' + ranger.ext.shell_escape.shell_escape('$kak_client') + ' edit {file} | \
+ kak -p '.format(file=fm.thisfile.path) + ranger.ext.shell_escape.shell_escape('$kak_session') + '; \
+ xdotool windowactivate $kak_client_env_WINDOWID') \
+ if fm.thisfile.is_file else fm.execute_console('move right=1')"'"' < /dev/null > /dev/null 2>&1 &
+ fi
+}}
diff --git a/rc/tools/rust/racer.kak b/rc/tools/rust/racer.kak
new file mode 100644
index 00000000..2371c597
--- /dev/null
+++ b/rc/tools/rust/racer.kak
@@ -0,0 +1,153 @@
+declare-option -hidden str racer_tmp_dir
+declare-option -hidden completions racer_completions
+
+define-command racer-complete -docstring "Complete the current selection with racer" %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-racer.XXXXXXXX)
+ printf %s\\n "set-option buffer racer_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks %{ write ${dir}/buf }"
+ }
+ evaluate-commands %sh{
+ dir=${kak_opt_racer_tmp_dir}
+ (
+ cursor="${kak_cursor_line} $((${kak_cursor_column} - 1))"
+ racer_data=$(racer --interface tab-text complete-with-snippet ${cursor} ${kak_buffile} ${dir}/buf)
+ compl=$(printf %s\\n "${racer_data}" | awk '
+ BEGIN { FS = "\t"; ORS = ":" }
+ /^PREFIX/ {
+ column = ENVIRON["kak_cursor_column"] + $2 - $3
+ print ENVIRON["kak_cursor_line"] "." column "@@" ENVIRON["kak_timestamp"]
+ }
+ /^MATCH/ {
+ word = $2
+ type = $7
+ desc = substr($9, 2, length($9) - 2)
+ gsub(/\|/, "\\|", desc)
+ gsub(/\\n/, "\n", desc)
+ menu = $8
+ sub(/^pub /, "", menu)
+ gsub(/\|/, "\\|", menu)
+ if (type == "Function") {
+ sub(word, "{default+F}" word "{default+d}", menu)
+ gsub(/^fn /, " fn ", menu) # The extra spaces are there to vertically align
+ # the type of element on the menu to make it easy
+ # to read
+ menu = "{default+d}" menu
+ } else if (type == "Enum") {
+ menu = substr(menu, 0, length(menu) - 2)
+ sub(word, "{default+F}" word "{default+d}", menu)
+ gsub(/^enum /, " enum ", menu) # The extra spaces are there to vertically align
+ # the type of element on the menu to make it easy
+ # to read
+ } else if (type == "Module") {
+ if (length(menu) > 30) { # The "menu" bit (as returned by racer),
+ # contains the path to the source file
+ # containing the module...
+
+ menu = substr(menu, length(menu) - 29, 30) # ... trimming it, so the completion menu
+ # doesn''t get distorted if it''s too long
+ }
+ menu = " mod {default+F}" word "{default+d} .." menu # The extra spaces are there to vertically align
+ # the type of element on the menu to make it easy
+ # to read
+ } else if (type == "Trait") {
+ sub(word, "{default+F}" word "{default+d}", menu)
+ gsub(/^trait /, " trait ", menu) # The extra spaces are there to vertically align
+ # the type of element on the menu to make it easy
+ # to read
+ } else if (type == "Type") {
+ sub(word, "{default+F}" word "{default+d}", menu)
+ gsub(/^type /, " type ", menu) # The extra spaces are there to vertically align
+ # the type of element on the menu to make it easy
+ # to read
+ } else if (type == "Struct") {
+ sub(word, "{default+F}" word "{default+d}", menu) # Struct doesn''t have extra spaces because it''s
+ # the longest keyword
+ } else {
+ menu = "{default+F}" word "{default+d} " menu
+ }
+ candidate = word "|" desc "|" menu
+ gsub(/:/, "\\:", candidate)
+ print candidate
+ }'
+ )
+ printf %s\\n "evaluate-commands -client '${kak_client}' %{
+ set-option buffer=${kak_bufname} racer_completions %@${compl%?}@
+ }" | kak -p ${kak_session}
+ rm -r ${dir}
+ ) > /dev/null 2>&1 < /dev/null &
+ }
+}
+
+define-command racer-enable-autocomplete -docstring "Add racer completion candidates to the completer" %{
+ set-option window completers option=racer_completions %opt{completers}
+ hook window -group racer-autocomplete InsertIdle .* %{ try %{
+ execute-keys -draft <a-h><a-k>([\w\.]|::).\z<ret>
+ racer-complete
+ } }
+ alias window complete racer-complete
+}
+
+define-command racer-disable-autocomplete -docstring "Disable racer completion" %{
+ evaluate-commands %sh{ printf "set-option window completers %s\n" $(printf %s "${kak_opt_completers}" | sed -e "s/'option=racer_completions'//g") }
+ remove-hooks window racer-autocomplete
+ unalias window complete racer-complete
+}
+
+define-command racer-go-definition -docstring "Jump to where the rust identifier below the cursor is defined" %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-racer.XXXXXXXX)
+ printf %s\\n "set-option buffer racer_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks %{ write ${dir}/buf }"
+ }
+ evaluate-commands %sh{
+ dir=${kak_opt_racer_tmp_dir}
+ cursor="${kak_cursor_line} $((${kak_cursor_column} - 1))"
+ racer_data=$(racer --interface tab-text find-definition ${cursor} "${kak_buffile}" "${dir}/buf" | head -n 1)
+
+ racer_match=$(printf %s\\n "$racer_data" | cut -f1 )
+ if [ "$racer_match" = "MATCH" ]; then
+ racer_line=$(printf %s\\n "$racer_data" | cut -f3 )
+ racer_column=$(printf %s\\n "$racer_data" | cut -f4 )
+ racer_file=$(printf %s\\n "$racer_data" | cut -f5 )
+ printf %s\\n "edit -existing '$racer_file' $racer_line $racer_column"
+ case ${racer_file} in
+ "${RUST_SRC_PATH}"* | "${CARGO_HOME:-$HOME/.cargo}"/registry/src/*)
+ printf %s\\n "set-option buffer readonly true";;
+ esac
+ else
+ printf %s\\n "echo -debug 'racer could not find a definition'"
+ fi
+ }
+}
+
+define-command racer-show-doc -docstring "Show the documentation about the rust identifier below the cursor" %{
+ evaluate-commands %sh{
+ dir=$(mktemp -d "${TMPDIR:-/tmp}"/kak-racer.XXXXXXXX)
+ printf %s\\n "set-option buffer racer_tmp_dir ${dir}"
+ printf %s\\n "evaluate-commands -no-hooks %{ write ${dir}/buf }"
+ }
+ evaluate-commands %sh{
+ dir=${kak_opt_racer_tmp_dir}
+ cursor="${kak_cursor_line} ${kak_cursor_column}"
+ racer_data=$(racer --interface tab-text complete-with-snippet ${cursor} "${kak_buffile}" "${dir}/buf" | sed -n 2p )
+ racer_match=$(printf %s\\n "$racer_data" | cut -f1)
+ if [ "$racer_match" = "MATCH" ]; then
+ racer_doc=$(
+ printf %s\\n "$racer_data" |
+ cut -f9 |
+ sed -e '
+
+ # Remove leading and trailing quotes
+ s/^"\(.*\)"$/\1/g
+
+ # Escape all @ so that it can be properly used in the string expansion
+ s/@/\\@/g
+
+ ')
+ printf "info %%@$racer_doc@"
+ else
+ printf %s\\n "echo -debug 'racer could not find a definition'"
+ fi
+ }
+}
diff --git a/rc/tools/spell.kak b/rc/tools/spell.kak
new file mode 100644
index 00000000..10c0f915
--- /dev/null
+++ b/rc/tools/spell.kak
@@ -0,0 +1,121 @@
+declare-option -hidden range-specs spell_regions
+declare-option -hidden str spell_lang
+declare-option -hidden str spell_tmp_file
+
+define-command -params ..1 \
+ -docstring %{spell [<language>]: spell check the current buffer
+The first optional argument is the language against which the check will be performed
+Formats of language supported:
+ - ISO language code, e.g. 'en'
+ - language code above followed by a dash or underscore with an ISO country code, e.g. 'en-US'} \
+ spell %{
+ try %{ add-highlighter window/ ranges 'spell_regions' }
+ evaluate-commands %sh{
+ file=$(mktemp -d "${TMPDIR:-/tmp}"/kak-spell.XXXXXXXX)/buffer
+ printf 'eval -no-hooks write -sync %s\n' "${file}"
+ printf 'set-option buffer spell_tmp_file %s\n' "${file}"
+ }
+ evaluate-commands %sh{
+ if [ $# -ge 1 ]; then
+ if [ ${#1} -ne 2 ] && [ ${#1} -ne 5 ]; then
+ echo "echo -markup '{Error}Invalid language code (examples of expected format: en, en_US, en-US)'"
+ rm -rf "$(dirname "$kak_opt_spell_tmp_file")"
+ exit 1
+ else
+ options="-l '$1'"
+ printf 'set-option buffer spell_lang %s\n' "$1"
+ fi
+ fi
+
+ {
+ sed 's/^/^/' "$kak_opt_spell_tmp_file" | eval "aspell --byte-offsets -a $options" 2>&1 | {
+ line_num=1
+ regions=$kak_timestamp
+ read line # drop the identification message
+ while read -r line; do
+ case "$line" in
+ [\#\&]*)
+ if expr "$line" : '^&' >/dev/null; then
+ pos=$(printf %s\\n "$line" | cut -d ' ' -f 4 | sed 's/:$//')
+ else
+ pos=$(printf %s\\n "$line" | cut -d ' ' -f 3)
+ fi
+ word=$(printf %s\\n "$line" | cut -d ' ' -f 2)
+ len=$(printf %s "$word" | wc -c)
+ regions="$regions $line_num.$pos+${len}|Error"
+ ;;
+ '') line_num=$((line_num + 1));;
+ \*) ;;
+ *) printf 'echo -markup %%{{Error}%s}\n' "${line}" | kak -p "${kak_session}";;
+ esac
+ done
+ printf 'set-option "buffer=%s" spell_regions %s' "${kak_bufname}" "${regions}" \
+ | kak -p "${kak_session}"
+ }
+ rm -rf $(dirname "$kak_opt_spell_tmp_file")
+ } </dev/null >/dev/null 2>&1 &
+ }
+}
+
+define-command spell-clear %{
+ unset-option buffer spell_regions
+}
+
+define-command spell-next %{ evaluate-commands %sh{
+ anchor_line="${kak_selection_desc%%.*}"
+ anchor_col="${kak_selection_desc%%,*}"
+ anchor_col="${anchor_col##*.}"
+
+ start_first="${kak_opt_spell_regions#* }"
+ start_first="${start_first%%|*}"
+ start_first="${start_first#\'}"
+
+ find_next_word_desc() {
+ ## XXX: the `spell` command adds sorted selection descriptions to the range
+ printf %s\\n "${1}" \
+ | sed -e "s/'//g" -e 's/^[0-9]* //' -e 's/|[^ ]*//g' \
+ | tr ' ' '\n' \
+ | while IFS=, read -r start end; do
+ start_line="${start%.*}"
+ start_col="${start#*.}"
+ end_line="${end%.*}"
+ end_col="${end#*.}"
+
+ if [ "${start_line}" -lt "${anchor_line}" ]; then
+ continue
+ elif [ "${start_line}" -eq "${anchor_line}" ] \
+ && [ "${start_col}" -le "${anchor_col}" ]; then
+ continue
+ fi
+
+ printf 'select %s,%s\n' "${start}" "${end}"
+ break
+ done
+ }
+
+ # no selection descriptions are in `spell_regions`
+ if ! expr "${start_first}" : '[0-9][0-9]*\.[0-9][0-9]*,[0-9][0-9]*\.[0-9]' >/dev/null; then
+ exit
+ fi
+
+ next_word_desc=$(find_next_word_desc "${kak_opt_spell_regions}")
+ if [ -n "${next_word_desc}" ]; then
+ printf %s\\n "${next_word_desc}"
+ else
+ printf 'select %s\n' "${start_first}"
+ fi
+} }
+
+define-command spell-replace %{ evaluate-commands %sh{
+ if [ -n "$kak_opt_spell_lang" ]; then
+ options="-l '$kak_opt_spell_lang'"
+ fi
+ suggestions=$(printf %s "$kak_selection" | eval "aspell -a $options" | grep '^&' | cut -d: -f2)
+ menu=$(printf %s "${suggestions#?}" | awk -F', ' '
+ {
+ for (i=1; i<=NF; i++)
+ printf "%s", "%{"$i"}" "%{execute-keys -itersel %{c"$i"<esc>be}}"
+ }
+ ')
+ printf 'try %%{ menu -auto-single %s }' "${menu}"
+} }