vim9script
# Language: Vim script
# Maintainer: github user lacygoill
# Last Change: 2022 Sep 24
# Config {{{1
const TIMEOUT: number = get(g:, 'vim_indent', {})
->get('searchpair_timeout', 100)
def IndentMoreInBracketBlock(): number # {{{2
if get(g:, 'vim_indent', {})
->get('more_in_bracket_block', false)
return shiftwidth()
else
return 0
endif
enddef
def IndentMoreLineContinuation(): number # {{{2
var n: any = get(g:, 'vim_indent', {})
# We inspect `g:vim_indent_cont` to stay backward compatible.
->get('line_continuation', get(g:, 'vim_indent_cont', shiftwidth() * 3))
if n->typename() == 'string'
return n->eval()
else
return n
endif
enddef
# }}}2
# Init {{{1
var patterns: list<string>
# Tokens {{{2
# BAR_SEPARATION {{{3
const BAR_SEPARATION: string = '[^|\\]\@1<=|'
# OPENING_BRACKET {{{3
const OPENING_BRACKET: string = '[[{(]'
# CLOSING_BRACKET {{{3
const CLOSING_BRACKET: string = '[]})]'
# NON_BRACKET {{{3
const NON_BRACKET: string = '[^[\]{}()]'
# LIST_OR_DICT_CLOSING_BRACKET {{{3
const LIST_OR_DICT_CLOSING_BRACKET: string = '[]}]'
# LIST_OR_DICT_OPENING_BRACKET {{{3
const LIST_OR_DICT_OPENING_BRACKET: string = '[[{]'
# CHARACTER_UNDER_CURSOR {{{3
const CHARACTER_UNDER_CURSOR: string = '\%.c.'
# INLINE_COMMENT {{{3
# TODO: It is not required for an inline comment to be surrounded by whitespace.
# But it might help against false positives.
# To be more reliable, we should inspect the syntax, and only require whitespace
# before the `#` comment leader. But that might be too costly (because of
# `synstack()`).
const INLINE_COMMENT: string = '\s[#"]\%(\s\|[{}]\{3}\)'
# INLINE_VIM9_COMMENT {{{3
const INLINE_VIM9_COMMENT: string = '\s#'
# COMMENT {{{3
# TODO: Technically, `"\s` is wrong.
#
# First, whitespace is not required.
# Second, in Vim9, a string might appear at the start of the line.
# To be sure, we should also inspect the syntax.
# We can't use `INLINE_COMMENT` here. {{{
#
# const COMMENT: string = $'^\s*{INLINE_COMMENT}'
# ^------------^
# ✘
#
# Because `INLINE_COMMENT` asserts the presence of a whitespace before the
# comment leader. This assertion is not satisfied for a comment starting at the
# start of the line.
#}}}
const COMMENT: string = '^\s*\%(#\|"\\\=\s\).*$'
# DICT_KEY {{{3
const DICT_KEY: string = '^\s*\%('
.. '\%(\w\|-\)\+'
.. '\|'
.. '"[^"]*"'
.. '\|'
.. "'[^']*'"
.. '\|'
.. '\[[^]]\+\]'
.. '\)'
.. ':\%(\s\|$\)'
# END_OF_COMMAND {{{3
const END_OF_COMMAND: string = $'\s*\%($\|||\@!\|{INLINE_COMMENT}\)'
# END_OF_LINE {{{3
const END_OF_LINE: string = $'\s*\%($\|{INLINE_COMMENT}\)'
# END_OF_VIM9_LINE {{{3
const END_OF_VIM9_LINE: string = $'\s*\%($\|{INLINE_VIM9_COMMENT}\)'
# OPERATOR {{{3
const OPERATOR: string = '\%(^\|\s\)\%([-+*/%]\|\.\.\|||\|&&\|??\|?\|<<\|>>\|\%([=!]=\|[<>]=\=\|[=!]\~\|is\|isnot\)[?#]\=\)\%(\s\|$\)\@=\%(\s*[|<]\)\@!'
# assignment operators
.. '\|' .. '\s\%([-+*/%]\|\.\.\)\==\%(\s\|$\)\@='
# support `:` when used inside conditional operator `?:`
.. '\|' .. '\%(\s\|^\):\%(\s\|$\)'
# HEREDOC_OPERATOR {{{3
const HEREDOC_OPERATOR: string = '\s=<<\s\@=\%(\s\+\%(trim\|eval\)\)\{,2}'
# PATTERN_DELIMITER {{{3
# A better regex would be:
#
# [^-+*/%.:# \t[:alnum:]\"|]\@=.\|->\@!\%(=\s\)\@!\|[+*/%]\%(=\s\)\@!
#
# But sometimes, it can be too costly and cause `E363` to be given.
const PATTERN_DELIMITER: string = '[-+*/%]\%(=\s\)\@!'
# QUOTE {{{3
const QUOTE: string = '["'']'
# }}}2
# Syntaxes {{{2
# ASSIGNS_HEREDOC {{{3
const ASSIGNS_HEREDOC: string = $'^\%({COMMENT}\)\@!.*\%({HEREDOC_OPERATOR}\)\s\+\zs[A-Z]\+{END_OF_LINE}'
# CD_COMMAND {{{3
const CD_COMMAND: string = $'[lt]\=cd!\=\s\+-{END_OF_COMMAND}'
# HIGHER_ORDER_COMMAND {{{3
patterns =<< trim eval END
argdo\>!\=
bufdo\>!\=
cdo\>!\=
folddoc\%[losed]\>
foldd\%[oopen]\>
ldo\=\>!\=
tabdo\=\>
windo\>
au\%[tocmd]\>.*
com\%[mand]\>.*
g\%[lobal]!\={PATTERN_DELIMITER}.*
v\%[global]!\={PATTERN_DELIMITER}.*
END
const HIGHER_ORDER_COMMAND: string = $'\%(^\|{BAR_SEPARATION}\)\s*\<\%(' .. patterns->join('\|') .. '\):\@!'
# MAPPING_COMMAND {{{3
const MAPPING_COMMAND: string = '\%(\<sil\%[ent]!\=\s\+\)\=[nvxsoilct]\=\%(nore\|un\)map!\=\s'
# NORMAL_COMMAND {{{3
const NORMAL_COMMAND: string = '\<norm\%[al]!\=\s*\S\+$'
# PLUS_MINUS_COMMAND {{{3
# In legacy, the `:+` and `:-` commands are not required to be preceded by a colon.
# As a result, when `+` or `-` is alone on a line, there is ambiguity.
# It might be an operator or a command.
# To not break the indentation in legacy scripts, we might need to consider such
# lines as commands.
const PLUS_MINUS_COMMAND: string = '^\s*[+-]\s*$'
# ENDS_BLOCK {{{3
const ENDS_BLOCK: string = '^\s*\%('
.. 'en\%[dif]'
.. '\|' .. 'endfor\='
.. '\|' .. 'endw\%[hile]'
.. '\|' .. 'endt\%[ry]'
.. '\|' .. 'enddef'
.. '\|' .. 'endf\%[unction]'
.. '\|' .. 'aug\%[roup]\s\+[eE][nN][dD]'
.. '\|' .. CLOSING_BRACKET
.. $'\){END_OF_COMMAND}'
# ENDS_BLOCK_OR_CLAUSE {{{3
patterns =<< trim END
en\%[dif]
el\%[se]
endfor\=
endw\%[hile]
endt\%[ry]
fina\|finally\=
enddef
endf\%[unction]
aug\%[roup]\s\+[eE][nN][dD]
END
const ENDS_BLOCK_OR_CLAUSE: string = '^\s*\%(' .. patterns->join('\|') .. $'\){END_OF_COMMAND}'
.. $'\|^\s*cat\%[ch]\%(\s\+\({PATTERN_DELIMITER}\).*\1\)\={END_OF_COMMAND}'
.. $'\|^\s*elseif\=\s\+\%({OPERATOR}\)\@!'
# STARTS_CURLY_BLOCK {{{3
# TODO: `{` alone on a line is not necessarily the start of a block.
# It could be a dictionary if the previous line ends with a binary/ternary
# operator. This can cause an issue whenever we use `STARTS_CURLY_BLOCK` or
# `LINE_CONTINUATION_AT_EOL`.
const STARTS_CURLY_BLOCK: string = '\%('
.. '^\s*{'
.. '\|' .. '^.*\zs\s=>\s\+{'
.. '\|' .. $'^\%(\s*\|.*{BAR_SEPARATION}\s*\)\%(com\%[mand]\|au\%[tocmd]\).*\zs\s{{'
.. '\)' .. END_OF_COMMAND
# STARTS_NAMED_BLOCK {{{3
# All of these will be used at the start of a line (or after a bar).
# NOTE: Don't replace `\%x28` with `(`.{{{
#
# Otherwise, the paren would be unbalanced which might cause syntax highlighting
# issues much later in the code of