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
    }
}