diff options
| author | Alex Leferry 2 <alexherbo2@gmail.com> | 2019-03-18 19:56:34 +0100 |
|---|---|---|
| committer | Alex Leferry 2 <alexherbo2@gmail.com> | 2019-03-21 01:06:16 +0100 |
| commit | c0dccdd90dd615cf663d95fd94fbdbdf2a88b165 (patch) | |
| tree | cb48fb1b7fb74e6e3b98a62f6e2768686bb75c98 /rc/tools | |
| parent | f87e844244d5ee81e9c1ceb04c354726002ae760 (diff) | |
Add categories in rc/
Closes #2783
Diffstat (limited to 'rc/tools')
| -rw-r--r-- | rc/tools/autorestore.kak | 71 | ||||
| -rw-r--r-- | rc/tools/autowrap.kak | 48 | ||||
| -rw-r--r-- | rc/tools/clang.kak | 180 | ||||
| -rw-r--r-- | rc/tools/comment.kak | 180 | ||||
| -rw-r--r-- | rc/tools/ctags.kak | 123 | ||||
| -rw-r--r-- | rc/tools/doc.kak | 165 | ||||
| -rw-r--r-- | rc/tools/formatter.kak | 31 | ||||
| -rw-r--r-- | rc/tools/git.kak | 214 | ||||
| -rw-r--r-- | rc/tools/go/go-tools.kak | 182 | ||||
| -rw-r--r-- | rc/tools/grep.kak | 73 | ||||
| -rw-r--r-- | rc/tools/lint.kak | 176 | ||||
| -rw-r--r-- | rc/tools/make.kak | 84 | ||||
| -rw-r--r-- | rc/tools/man.kak | 68 | ||||
| -rw-r--r-- | rc/tools/python/jedi.kak | 47 | ||||
| -rw-r--r-- | rc/tools/ranger.kak | 59 | ||||
| -rw-r--r-- | rc/tools/rust/racer.kak | 153 | ||||
| -rw-r--r-- | rc/tools/spell.kak | 121 |
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}" +} } |
