summaryrefslogtreecommitdiff
path: root/autoload/filetype/sh.kak
diff options
context:
space:
mode:
Diffstat (limited to 'autoload/filetype/sh.kak')
-rw-r--r--autoload/filetype/sh.kak206
1 files changed, 206 insertions, 0 deletions
diff --git a/autoload/filetype/sh.kak b/autoload/filetype/sh.kak
new file mode 100644
index 0000000..9a9f51e
--- /dev/null
+++ b/autoload/filetype/sh.kak
@@ -0,0 +1,206 @@
+hook global BufCreate .*\.((z|ba|c|k|mk)?sh(rc|_profile)?|profile) %{
+ set-option buffer filetype sh
+}
+
+hook global WinSetOption filetype=sh %{
+ require-module sh
+ set-option window static_words %opt{sh_static_words}
+
+ hook window ModeChange pop:insert:.* -group sh-trim-indent sh-trim-indent
+ hook window InsertChar \n -group sh-insert sh-insert-on-new-line
+ hook window InsertChar \n -group sh-indent sh-indent-on-new-line
+ hook -once -always window WinSetOption filetype=.* %{ remove-hooks window sh-.+ }
+}
+
+hook -group sh-highlight global WinSetOption filetype=sh %{
+ add-highlighter window/sh ref sh
+ hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/sh }
+}
+
+# using non-ascii characters here so that we can use the '[' command
+provide-module sh %§
+
+add-highlighter shared/sh regions
+add-highlighter shared/sh/code default-region group
+add-highlighter shared/sh/arithmetic region -recurse \(.*?\( (\$|(?<=for)\h*)\(\( \)\) group
+add-highlighter shared/sh/double_string region %{(?<!\\)(?:\\\\)*\K"} %{(?<!\\)(?:\\\\)*"} group
+add-highlighter shared/sh/single_string region %{(?<!\\)(?:\\\\)*\K'} %{'} fill string
+add-highlighter shared/sh/expansion region -recurse (?<!\\)(?:\\\\)*\K\$\{ (?<!\\)(?:\\\\)*\K\$\{ \}|\n fill value
+add-highlighter shared/sh/comment region (?<!\\)(?:\\\\)*(?:^|\h)\K# '$' fill comment
+add-highlighter shared/sh/heredoc region -match-capture '<<-?\h*''?(\w+)''?' '^\t*(\w+)$' fill string
+
+add-highlighter shared/sh/arithmetic/expansion ref sh/double_string/expansion
+add-highlighter shared/sh/double_string/fill fill string
+
+evaluate-commands %sh{
+ # Grammar
+ # Generated with `compgen -k` in bash
+ keywords="if then else elif fi case esac for select while until do done in
+ function time coproc"
+
+ # Generated with `compgen -b` in bash
+ builtins="alias bg bind break builtin caller cd command compgen complete
+ compopt continue declare dirs disown echo enable eval exec
+ exit export false fc fg getopts hash help history jobs kill
+ let local logout mapfile popd printf pushd pwd read readarray
+ readonly return set shift shopt source suspend test times trap
+ true type typeset ulimit umask unalias unset wait"
+
+ join() { sep=$2; eval set -- $1; IFS="$sep"; echo "$*"; }
+
+ # Add the language's grammar to the static completion list
+ printf %s\\n "declare-option str-list sh_static_words $(join "${keywords}" ' ') $(join "${builtins}" ' ')"
+
+ # Highlight keywords
+ printf %s\\n "add-highlighter shared/sh/code/ regex (?<!-)\b($(join "${keywords}" '|'))\b(?!-) 0:keyword"
+
+ # Highlight builtins
+ printf %s "add-highlighter shared/sh/code/builtin regex (?<!-)\b($(join "${builtins}" '|'))\b(?!-) 0:builtin"
+}
+
+add-highlighter shared/sh/code/operators regex [\[\]\(\)&|]{1,2} 0:operator
+add-highlighter shared/sh/code/variable regex ((?<![-:])\b\w+)= 1:variable
+add-highlighter shared/sh/code/alias regex \balias(\h+[-+]\w)*\h+([\w-.]+)= 2:variable
+add-highlighter shared/sh/code/function regex ^\h*(\S+(?<!=))\h*\(\) 1:function
+
+add-highlighter shared/sh/code/unscoped_expansion regex (?<!\\)(?:\\\\)*\K\$(\w+|#|@|\?|\$|!|-|\*) 0:value
+add-highlighter shared/sh/double_string/expansion regex (?<!\\)(?:\\\\)*\K\$(\w+|#|@|\?|\$|!|-|\*|\{.+?\}) 0:value
+
+# Commands
+# ‾‾‾‾‾‾‾‾
+
+define-command -hidden sh-trim-indent %{
+ # remove trailing white spaces
+ try %{ execute-keys -draft -itersel x s \h+$ <ret> d }
+}
+
+# This is at best an approximation, since shell syntax is very complex.
+# Also note that this targets plain sh syntax, not bash - bash adds a whole
+# other level of complexity. If your bash code is fairly portable this will
+# probably work.
+#
+# Of necessity, this is also fairly opinionated about indentation styles.
+# Doing it "properly" would require far more context awareness than we can
+# bring to this kind of thing.
+define-command -hidden sh-insert-on-new-line %[
+ evaluate-commands -draft -itersel %[
+ # copy '#' comment prefix and following white spaces
+ try %{ execute-keys -draft k x s ^\h*\K#\h* <ret> y gh j P }
+ ]
+]
+
+# Use custom object matching to copy indentation for the various logical
+# blocks.
+#
+# Note that we're using a weird non-ascii character instead of [ or { here
+# because the '[' and '{' characters need to be available for the commands.
+define-command -hidden sh-indent-on-new-line %¶
+ evaluate-commands -draft -itersel %@
+ # preserve previous line indent
+ try %{ execute-keys -draft <semicolon> K <a-&> }
+ # filter previous line
+ try %{ execute-keys -draft k : sh-trim-indent <ret> }
+
+ # Indent loop syntax, e.g.:
+ # for foo in bar; do
+ # things
+ # done
+ #
+ # or:
+ #
+ # while foo; do
+ # things
+ # done
+ #
+ # or equivalently:
+ #
+ # while foo
+ # do
+ # things
+ # done
+ #
+ # indent after do
+ try %{ execute-keys -draft , k x <a-k> \bdo$ <ret> j <a-gt> }
+ # copy the indentation of the matching for/when - matching on the do
+ # statement, so we don't need to duplicate this for the two loop
+ # structures.
+ try %{ execute-keys -draft , k x <a-k> \bdone$ <ret> gh [c\bdo\b,\bdone\b <ret> x <a-S> 1<a-&> , j K <a-&> }
+
+ # Indent if/then/else syntax, e.g.:
+ # if [ $foo = $bar ]; then
+ # things
+ # else
+ # other_things
+ # fi
+ #
+ # or equivalently:
+ # if [ $foo = $bar ]
+ # then
+ # things
+ # else
+ # other_things
+ # fi
+ #
+ # indent after then
+ try %{ execute-keys -draft , k x <a-k> \bthen$ <ret> j <a-gt> }
+ # copy the indentation of the matching if
+ try %{ execute-keys -draft , k x <a-k> \bfi$ <ret> gh [c\bif\b,\bfi\b <ret> x <a-S> 1<a-&> , j K <a-&> }
+ # copy the indentation of the matching if, and then re-indent afterwards
+ try %{ execute-keys -draft , k x <a-k> \belse$ <ret> gh [c\bif\b,\bfi\b <ret> x <a-S> 1<a-&> , j K <a-&> j <a-gt> }
+
+ # Indent case syntax, e.g.:
+ # case "$foo" in
+ # bar) thing1;;
+ # baz)
+ # things
+ # ;;
+ # *)
+ # default_things
+ # ;;
+ # esac
+ #
+ # or equivalently:
+ # case "$foo"
+ # in
+ # bar) thing1;;
+ # esac
+ #
+ # indent after in
+ try %{ execute-keys -draft , k x <a-k> \bin$ <ret> j <a-gt> }
+ # copy the indentation of the matching case
+ try %{ execute-keys -draft , k x <a-k> \besac$ <ret> gh [c\bcase\b,\besac\b <ret> x <a-S> 1<a-&> , j K <a-&> }
+ # indent after )
+ try %{ execute-keys -draft , k x <a-k> ^\s*\(?[^(]+[^)]\)$ <ret> j <a-gt> }
+ # deindent after ;;
+ try %{ execute-keys -draft , k x <a-k> ^\s*\;\;$ <ret> j <a-lt> }
+
+ # Indent compound commands as logical blocks, e.g.:
+ # {
+ # thing1
+ # thing2
+ # }
+ #
+ # or in a function definition:
+ # foo () {
+ # thing1
+ # thing2
+ # }
+ #
+ # We don't handle () delimited compond commands - these are technically very
+ # similar, but the use cases are quite different and much less common.
+ #
+ # Note that in this context the '{' and '}' characters are reserved
+ # words, and hence must be surrounded by a token separator - typically
+ # white space (including a newline), though technically it can also be
+ # ';'. Only vertical white space makes sense in this context, though,
+ # since the syntax denotes a logical block, not a simple compound command.
+ try %= execute-keys -draft , k x <a-k> (\s|^)\{$ <ret> j <a-gt> =
+ # deindent closing }
+ try %= execute-keys -draft , k x <a-k> ^\s*\}$ <ret> <a-lt> j K <a-&> =
+ # deindent closing } when after cursor
+ try %= execute-keys -draft x <a-k> ^\h*\} <ret> gh / \} <ret> m <a-S> 1<a-&> =
+
+ @
+