diff options
author | Bram Moolenaar <Bram@vim.org> | 2022-09-27 17:30:34 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2022-09-27 17:30:34 +0100 |
commit | 9fbdbb814f4ad67a14979aba4a6a49800c2f1a99 (patch) | |
tree | cbf6e2990b41ad67092e529497df96bd9199e353 /runtime | |
parent | 26f09ea54b2c60abf21df42c60bdfc60eca17b0d (diff) |
Update runtime files
Diffstat (limited to 'runtime')
43 files changed, 4211 insertions, 376 deletions
diff --git a/runtime/autoload/context.vim b/runtime/autoload/context.vim index e42b99e2e9..fc59309768 100644 --- a/runtime/autoload/context.vim +++ b/runtime/autoload/context.vim @@ -3,13 +3,18 @@ vim9script # Language: ConTeXt typesetting engine # Maintainer: Nicola Vitacolonna <nvitacolonna@gmail.com> # Former Maintainers: Nikolai Weibull <now@bitwi.se> -# Latest Revision: 2022 Aug 12 +# Latest Revision: 2022 Sep 19 # Typesetting {{{ import autoload './typeset.vim' export def ConTeXtCmd(path: string): list<string> - return ['mtxrun', '--script', 'context', '--nonstopmode', '--autogenerate', path] + var cmd = ['mtxrun', '--script', 'context', '--nonstopmode', '--autogenerate'] + if !empty(get(g:, 'context_extra_options', '')) + cmd += g:context_extra_options + endif + cmd->add(path) + return cmd enddef export def Typeset(bufname: string, env = {}, Cmd = ConTeXtCmd): bool diff --git a/runtime/autoload/dist/vimindent.vim b/runtime/autoload/dist/vimindent.vim new file mode 100644 index 0000000000..572fc7c6cb --- /dev/null +++ b/runtime/autoload/dist/vimindent.vim @@ -0,0 +1,1200 @@ +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 the current script (sometimes, the syntax +# highlighting plugin fails to correctly recognize a heredoc which is far away +# and/or not displayed because inside a fold). +# }}} +patterns =<< trim END + if + el\%[se] + elseif\= + for + wh\%[ile] + try + cat\%[ch] + fina\|finally\= + fu\%[nction]\%x28\@! + \%(export\s\+\)\=def + aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+ +END +const STARTS_NAMED_BLOCK: string = '^\s*\%(sil\%[ent]\s\+\)\=\%(' .. patterns->join('\|') .. '\)\>:\@!' + +# STARTS_FUNCTION {{{3 + +const STARTS_FUNCTION: string = '^\s*\%(export\s\+\)\=def\>:\@!' + +# ENDS_FUNCTION {{{3 + +const ENDS_FUNCTION: string = $'^\s*enddef\>:\@!{END_OF_COMMAND}' + +# START_MIDDLE_END {{{3 + +const START_MIDDLE_END: dict<list<string>> = { + if: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], + else: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], + elseif: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], + endif: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], + for: ['for', '', 'endfor\='], + endfor: ['for', '', 'endfor\='], + while: ['wh\%[ile]', '', 'endw\%[hile]'], + endwhile: ['wh\%[ile]', '', 'endw\%[hile]'], + try: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], + catch: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], + finally: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], + endtry: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], + def: ['\%(export\s\+\)\=def', '', 'enddef'], + enddef: ['\%(export\s\+\)\=def', '', 'enddef'], + function: ['fu\%[nction]', '', 'endf\%[unction]'], + endfunction: ['fu\%[nction]', '', 'endf\%[unction]'], + augroup: ['aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+', '', 'aug\%[roup]\s\+[eE][nN][dD]'], +}->map((_, kwds: list<string>) => + kwds->map((_, kwd: string) => kwd == '' + ? '' + : $'\%(^\|{BAR_SEPARATION}\|\<sil\%[ent]\|{HIGHER_ORDER_COMMAND}\)\s*' + .. $'\%({printf('\C\<\%%(%s\)\>:\@!\%%(\s*%s\)\@!', kwd, OPERATOR)}\)')) +# }}}2 +# EOL {{{2 +# OPENING_BRACKET_AT_EOL {{{3 + +const OPENING_BRACKET_AT_EOL: string = $'{OPENING_BRACKET}{END_OF_VIM9_LINE}' + +# COMMA_AT_EOL {{{3 + +const COMMA_AT_EOL: string = $',{END_OF_VIM9_LINE}' + +# COMMA_OR_DICT_KEY_AT_EOL {{{3 + +const COMMA_OR_DICT_KEY_AT_EOL: string = $'\%(,\|{DICT_KEY}\){END_OF_VIM9_LINE}' + +# LAMBDA_ARROW_AT_EOL {{{3 + +const LAMBDA_ARROW_AT_EOL: string = $'\s=>{END_OF_VIM9_LINE}' + +# LINE_CONTINUATION_AT_EOL {{{3 + +const LINE_CONTINUATION_AT_EOL: string = '\%(' + .. ',' + .. '\|' .. OPERATOR + .. '\|' .. '\s=>' + .. '\|' .. '[^=]\zs[[(]' + .. '\|' .. DICT_KEY + # `{` is ambiguous. + # It can be the start of a dictionary or a block. + # We only want to match the former. + .. '\|' .. $'^\%({STARTS_CURLY_BLOCK}\)\@!.*\zs{{' + .. '\)\s*\%(\s#.*\)\=$' +# }}}2 +# SOL {{{2 +# BACKSLASH_AT_SOL {{{3 + +const BACKSLASH_AT_SOL: string = '^\s*\%(\\\|[#"]\\ \)' + +# CLOSING_BRACKET_AT_SOL {{{3 + +const CLOSING_BRACKET_AT_SOL: string = $'^\s*{CLOSING_BRACKET}' + +# LINE_CONTINUATION_AT_SOL {{{3 + +const LINE_CONTINUATION_AT_SOL: string = '^\s*\%(' + .. '\\' + .. '\|' .. '[#"]\\ ' + .. '\|' .. OPERATOR + .. '\|' .. '->\s*\h' + .. '\|' .. '\.\h' # dict member + .. '\|' .. '|' + # TODO: `}` at the start of a line is not necessarily a line continuation. + # Could be the end of a block. + .. '\|' .. CLOSING_BRACKET + .. '\)' + +# RANGE_AT_SOL {{{3 + +const RANGE_AT_SOL: string = '^\s*:\S' +# }}}1 +# Interface {{{1 +export def Expr(lnum: number): number # {{{2 + # line which is indented + var line_A: dict<any> = {text: getline(lnum), lnum: lnum} + # line above, on which we'll base the indent of line A + var line_B: dict<any> + + if line_A->AtStartOf('HereDoc') + line_A->CacheHeredoc() + elseif line_A.lnum->IsInside('HereDoc') + return line_A.text->HereDocIndent() + elseif line_A.lnum->IsRightBelow('HereDoc') + var ind: number = b:vimindent.startindent + unlet! b:vimindent + return ind + endif + + # Don't move this block after the function header one. + # Otherwise, we might clear the cache too early if the line following the + # header is a comment. + if line_A.text =~ COMMENT + return CommentIndent() + endif + + line_B = PrevCodeLine(line_A.lnum) + if line_A.text =~ BACKSLASH_AT_SOL + if line_B.text =~ BACKSLASH_AT_SOL + return Indent(line_B.lnum) + else + return Indent(line_B.lnum) + IndentMoreLineContinuation() + endif + endif + + if line_A->AtStartOf('FuncHeader') + line_A.lnum->CacheFuncHeader() + elseif line_A.lnum->IsInside('FuncHeader') + return b:vimindent.startindent + 2 * shiftwidth() + elseif line_A.lnum->IsRightBelow('FuncHeader') + var startindent: number = b:vimindent.startindent + unlet! b:vimindent + if line_A.text =~ ENDS_FUNCTION + return startindent + else + return startindent + shiftwidth() + endif + endif + + var past_bracket_block: dict<any> + if exists('b:vimindent') + && b:vimindent->has_key('is_BracketBlock') + past_bracket_block = RemovePastBracketBlock(line_A) + endif + if line_A->AtStartOf('BracketBlock') + line_A->CacheBracketBlock() + endif + if line_A.lnum->IsInside('BracketBlock') + && !b:vimindent.block_stack[0].is_curly_block + for block: dict<any> in b:vimindent.block_stack + # Can't call `BracketBlockIndent()` before we're indenting a line *after* the start of the block.{{{ + # + # That's because it might need the correct indentation of the start + # of the block. But if we're still *on* the start, we haven't yet + # computed that indentation. + #}}} + if line_A.lnum > block.startlnum + && !block.is_curly_block + return BracketBlockIndent(line_A, block) + endif + endfor + endif + if line_A.text->ContinuesBelowBracketBlock(line_B, past_bracket_block) + && line_A.text !~ CLOSING_BRACKET_AT_SOL + return past_bracket_block.startindent + endif + + # Problem: If we press `==` on the line right below the start of a multiline + # lambda (split after its arrow `=>`), the indent is not correct. + # Solution: Indent relative to the line above. + if line_B->EndsWithLambdaArrow() + return Indent(line_B.lnum) + shiftwidth() + IndentMoreInBracketBlock() + endif + + # Don't move this block before the heredoc one.{{{ + # + # A heredoc might be assigned on the very first line. + # And if it is, we need to cache some info. + #}}} + # Don't move it before the function header and bracket block ones either.{{{ + # + # You could, because these blocks of code deal with construct which can only + # appear in a Vim9 script. And in a Vim9 script, the first line is + # `vim9script`. Or maybe some legacy code/comment (see `:help vim9-mix`). + # But you can't find a Vim9 function header or Vim9 bracket block on the + # first line. + # + # Anyway, even if you could, don't. First, it would be inconsistent. + # Second, it could give unexpected results while we're trying to fix some + # failing test. + #}}} + if line_A.lnum == 1 + return 0 + endif + + # Don't do that: + # if line_A.text !~ '\S' + # return -1 + # endif + # It would prevent a line from being automatically indented when using the + # normal command `o`. + # TODO: Can we write a test for this? + + if line_B.text =~ STARTS_CURLY_BLOCK + return Indent(line_B.lnum) + shiftwidth() + IndentMoreInBracketBlock() + + elseif line_A.text =~ CLOSING_BRACKET_AT_SOL + var start: number = MatchingOpenBracket(line_A) + if start <= 0 + return -1 + endif + return Indent(start) + IndentMoreInBracketBlock() + + elseif line_A.text =~ ENDS_BLOCK_OR_CLAUSE + && !line_B->EndsWithLineContinuation() + var kwd: string = BlockStartKeyword(line_A.text) + if !START_MIDDLE_END->has_key(kwd) + return -1 + endif + + # If the cursor is after the match for the end pattern, we won't find + # the start of the block. Let's make sure that doesn't happen. + cursor(line_A.lnum, 1) + + var [start: string, middle: string, end: string] = START_MIDDLE_END[kwd] + var block_start = SearchPairStart(start, middle, end) + if block_start > 0 + return Indent(block_start) + else + return -1 + endif + endif + + var base_ind: number + if line_A->IsFirstLineOfCommand(line_B) + line_A.isfirst = true + line_B = line_B->FirstLinePreviousCommand() + base_ind = Indent(line_B.lnum) + + if line_B->EndsWithCurlyBlock() + && !line_A->IsInThisBlock(line_B.lnum) + return base_ind + endif + + else + line_A.isfirst = false + base_ind = Indent(line_B.lnum) + + var line_C: dict<any> = PrevCodeLine(line_B.lnum) + if !line_B->IsFirstLineOfCommand(line_C) || line_C.lnum <= 0 + return base_ind + endif + endif + + var ind: number = base_ind + Offset(line_A, line_B) + return [ind, 0]->max() +enddef + +def g:GetVimIndent(): number # {{{2 + # for backward compatibility + return Expr(v:lnum) +enddef +# }}}1 +# Core {{{1 +def Offset( # {{{2 + # we indent this line ... + line_A: dict<any>, + # ... relatively to this line + line_B: dict<any>, + ): number + + # increase indentation inside a block + if line_B.text =~ STARTS_NAMED_BLOCK || line_B->EndsWithCurlyBlock() + # But don't indent if the line starting the block also closes it. + if line_B->AlsoClosesBlock() + return 0 + # Indent twice for a line continuation in the block header itself, so that + # we can easily distinguish the end of the block header from the start of + # the block body. + elseif line_B->EndsWithLineContinuation() + && !line_A.isfirst + || line_A.text =~ LINE_CONTINUATION_AT_SOL + && line_A.text !~ PLUS_MINUS_COMMAND + || line_A.text->Is_IN_KeywordForLoop(line_B.text) + return 2 * shiftwidth() + else + return shiftwidth() + endif + + # increase indentation of a line if it's the continuation of a command which + # started on a previous line + elseif !line_A.isfirst + && (line_B->EndsWithLineContinuation() + || line_A.text =~ LINE_CONTINUATION_AT_SOL) + return shiftwidth() + endif + + return 0 +enddef + +def HereDocIndent(line_A: string): number # {{{2 + # at the end of a heredoc + if line_A =~ $'^\s*{b:vimindent.endmarker}$' + # `END` must be at the very start of the line if the heredoc is not trimmed + if !b:vimindent.is_trimmed + # We can't invalidate the cache just yet. + # The indent of `END` is meaningless; it's always 0. The next line + # will need to be indented relative to the start of the heredoc. It + # must know where it starts; it needs the cache. + return 0 + else + var ind: number = b:vimindent.startindent + # invalidate the cache so that it's not used for the next heredoc + unlet! b:vimindent + return ind + endif + endif + + # In a non-trimmed heredoc, all of leading whitespace is semantic. + # Leave it alone. + if !b:vimindent.is_trimmed + # But do save the indent of the assignment line. + if !b:vimindent->has_key('startindent') + b:vimindent.startindent = b:vimindent.startlnum->Indent() + endif + return -1 + endif + + # In a trimmed heredoc, *some* of the leading whitespace is semantic. + # We want to preserve it, so we can't just indent relative to the assignment + # line. That's because we're dealing with data, not with code. + # Instead, we need to compute by how much the indent of the assignment line + # was increased or decreased. Then, we need to apply that same change to + # every line inside the body. + var offset: number + if !b:vimindent->has_key('offset') + var old_startindent: number = b:vimindent.startindent + var new_startindent: number = b:vimindent.startlnum->Indent() + offset = new_startindent - old_startindent + + # If all the non-empty lines in the body have a higher indentation relative + # to the assignment, there is no need to indent them more. + # But if at least one of them does have the same indentation level (or a + # lower one), then we want to indent it further (and the whole block with it). + # This way, we can clearly distinguish the heredoc block from the rest of + # the code. + var end: number = search($'^\s*{b:vimindent.endmarker}$', 'nW') + var should_indent_more: bool = range(v:lnum, end - 1) + ->indexof((_, lnum: number): bool => Indent(lnum) <= old_startindent && getline(lnum) != '') >= 0 + if should_indent_more + offset += shiftwidth() + endif + + b:vimindent.offset = offset + b:vimindent.startindent = new_startindent + endif + + return [0, Indent(v:lnum) + b:vimindent.offset]->max() +enddef + +def CommentIndent(): number # {{{2 + var line_B: dict<any> + line_B.lnum = prevnonblank(v:lnum - 1) + line_B.text = getline(line_B.lnum) + if line_B.text =~ COMMENT + return Indent(line_B.lnum) + endif + + var next: number = NextCodeLine() + if next == 0 + return 0 + endif + var vimindent_save: dict<any> = get(b:, 'vimindent', {})->deepcopy() + var ind: number = next->Expr() + # The previous `Expr()` might have set or deleted `b:vimindent`. + # This could cause issues (e.g. when indenting 2 commented lines above a + # heredoc). Let's make sure the state of the variable is not altered. + if vimindent_save->empty() + unlet! b:vimindent + else + b:vimindent = vimindent_save + endif + if getline(next) =~ ENDS_BLOCK + return ind + shiftwidth() + else + return ind + endif +enddef + +def BracketBlockIndent(line_A: dict<any>, block: dict<any>): number # {{{2 + if !block->has_key('startindent') + block.startindent = block.startlnum->Indent() + endif + + var ind: number = block.startindent + + if line_A.text =~ CLOSING_BRACKET_AT_SOL + if b:vimindent.is_on_named_block_line + ind += 2 * shiftwidth() + endif + return ind + IndentMoreInBracketBlock() + endif + + var startline: dict<any> = { + text: block.startline, + lnum: block.startlnum + } + if startline->EndsWithComma() + || startline->EndsWithLambdaArrow() + || (startline->EndsWithOpeningBracket() + # TODO: Is that reliable? + && block.startline !~ + $'^\s*{NON_BRACKET}\+{LIST_OR_DICT_CLOSING_BRACKET},\s\+{LIST_OR_DICT_OPENING_BRACKET}') + ind += shiftwidth() + IndentMoreInBracketBlock() + endif + + if b:vimindent.is_on_named_block_line + ind += shiftwidth() + endif + + if block.is_dict + && line_A.text !~ DICT_KEY + ind += shiftwidth() + endif + + return ind +enddef + +def CacheHeredoc(line_A: dict<any>) # {{{2 + var endmarker: string = line_A.text->matchstr(ASSIGNS_HEREDOC) + var endlnum: number = search($'^\s*{endmarker}$', 'nW') + var is_trimmed: bool = line_A.text =~ $'.*\s\%(trim\%(\s\+eval\)\=\)\s\+[A-Z]\+{END_OF_LINE}' + b:vimindent = { + is_HereDoc: true, + startlnum: line_A.lnum, + endlnum: endlnum, + endmarker: endmarker, + is_trimmed: is_trimmed, + } + if is_trimmed + b:vimindent.startindent = Indent(line_A.lnum) + endif + RegisterCacheInvalidation() +enddef + +def CacheFuncHeader(startlnum: number) # {{{2 + var pos: list<number> = getcurpos() + cursor(startlnum, 1) + if search('(', 'W', startlnum) <= 0 + return + endif + var endlnum: number = SearchPair('(', '', ')', 'nW') + setpos('.', pos) + if endlnum == startlnum + re |