Update runtime files
diff --git a/runtime/indent/chatito.vim b/runtime/indent/chatito.vim
new file mode 100644
index 0000000..1ff5e9e
--- /dev/null
+++ b/runtime/indent/chatito.vim
@@ -0,0 +1,32 @@
+" Vim indent file
+" Language:	Chatito
+" Maintainer:	ObserverOfTime <chronobserver@disroot.org>
+" Last Change:	2022 Sep 20
+
+if exists('b:did_indent')
+    finish
+endif
+let b:did_indent = 1
+
+setlocal indentexpr=GetChatitoIndent()
+setlocal indentkeys=o,O,*<Return>,0#,!^F
+
+let b:undo_indent = 'setl inde< indk<'
+
+if exists('*GetChatitoIndent')
+    finish
+endif
+
+function GetChatitoIndent()
+    let l:prev = v:lnum - 1
+    if getline(prevnonblank(l:prev)) =~# '^[~%@]\['
+        " shift indent after definitions
+        return shiftwidth()
+    elseif getline(l:prev) !~# '^\s*$'
+        " maintain indent in sentences
+        return indent(l:prev)
+    else
+        " reset indent after a blank line
+        return 0
+    end
+endfunction
diff --git a/runtime/indent/gyp.vim b/runtime/indent/gyp.vim
new file mode 100644
index 0000000..c3980ac
--- /dev/null
+++ b/runtime/indent/gyp.vim
@@ -0,0 +1,7 @@
+" Vim indent file
+" Language:	GYP
+" Maintainer:	ObserverOfTime <chronobserver@disroot.org>
+" Last Change:	2022 Sep 27
+
+" JSON indent works well
+runtime! indent/json.vim
diff --git a/runtime/indent/hare.vim b/runtime/indent/hare.vim
new file mode 100644
index 0000000..bc4fea4
--- /dev/null
+++ b/runtime/indent/hare.vim
@@ -0,0 +1,138 @@
+" Vim indent file
+" Language: Hare
+" Maintainer: Amelia Clarke <me@rsaihe.dev>
+" Last Change: 2022 Sep 22
+
+if exists("b:did_indent")
+  finish
+endif
+let b:did_indent = 1
+
+if !has("cindent") || !has("eval")
+  finish
+endif
+
+setlocal cindent
+
+" L0 -> don't deindent labels
+" (s -> use one indent after a trailing (
+" m1 -> if ) starts a line, indent it the same as its matching (
+" ks -> add an extra indent to extra lines in an if expression or for expression
+" j1 -> indent code inside {} one level when in parentheses
+" J1 -> see j1
+" *0 -> don't search for unclosed block comments
+" #1 -> don't deindent lines that begin with #
+setlocal cinoptions=L0,(s,m1,ks,j1,J1,*0,#1
+
+" Controls which keys reindent the current line.
+" 0{     -> { at beginning of line
+" 0}     -> } at beginning of line
+" 0)     -> ) at beginning of line
+" 0]     -> ] at beginning of line
+" !^F    -> <C-f> (not inserted)
+" o      -> <CR> or `o` command
+" O      -> `O` command
+" e      -> else
+" 0=case -> case
+setlocal indentkeys=0{,0},0),0],!^F,o,O,e,0=case
+
+setlocal cinwords=if,else,for,switch,match
+
+setlocal indentexpr=GetHareIndent()
+
+function! FloorCindent(lnum)
+  return cindent(a:lnum) / shiftwidth() * shiftwidth()
+endfunction
+
+function! GetHareIndent()
+  let line = getline(v:lnum)
+  let prevlnum = prevnonblank(v:lnum - 1)
+  let prevline = getline(prevlnum)
+  let prevprevline = getline(prevnonblank(prevlnum - 1))
+
+  " This is all very hacky and imperfect, but it's tough to do much better when
+  " working with regex-based indenting rules.
+
+  " If the previous line ended with =, indent by one shiftwidth.
+  if prevline =~# '\v\=\s*(//.*)?$'
+    return indent(prevlnum) + shiftwidth()
+  endif
+
+  " If the previous line ended in a semicolon and the line before that ended
+  " with =, deindent by one shiftwidth.
+  if prevline =~# '\v;\s*(//.*)?$' && prevprevline =~# '\v\=\s*(//.*)?$'
+    return indent(prevlnum) - shiftwidth()
+  endif
+
+  " TODO: The following edge-case is still indented incorrectly:
+  " case =>
+  "         if (foo) {
+  "                 bar;
+  "         };
+  " | // cursor is incorrectly deindented by one shiftwidth.
+  "
+  " This only happens if the {} block is the first statement in the case body.
+  " If `case` is typed, the case will also be incorrectly deindented by one
+  " shiftwidth. Are you having fun yet?
+
+  " Deindent cases.
+  if line =~# '\v^\s*case'
+    " If the previous line was also a case, don't do any special indenting.
+    if prevline =~# '\v^\s*case'
+      return indent(prevlnum)
+    end
+
+    " If the previous line was a multiline case, deindent by one shiftwidth.
+    if prevline =~# '\v\=\>\s*(//.*)?$'
+      return indent(prevlnum) - shiftwidth()
+    endif
+
+    " If the previous line started a block, deindent by one shiftwidth.
+    " This handles the first case in a switch/match block.
+    if prevline =~# '\v\{\s*(//.*)?$'
+      return FloorCindent(v:lnum) - shiftwidth()
+    end
+
+    " If the previous line ended in a semicolon and the line before that wasn't
+    " a case, deindent by one shiftwidth.
+    if prevline =~# '\v;\s*(//.*)?$' && prevprevline !~# '\v\=\>\s*(//.*)?$'
+      return FloorCindent(v:lnum) - shiftwidth()
+    end
+
+    let l:indent = FloorCindent(v:lnum)
+
+    " If a normal cindent would indent the same amount as the previous line,
+    " deindent by one shiftwidth. This fixes some issues with `case let` blocks.
+    if l:indent == indent(prevlnum)
+      return l:indent - shiftwidth()
+    endif
+
+    " Otherwise, do a normal cindent.
+    return l:indent
+  endif
+
+  " Don't indent an extra shiftwidth for cases which span multiple lines.
+  if prevline =~# '\v\=\>\s*(//.*)?$' && prevline !~# '\v^\s*case\W'
+    return indent(prevlnum)
+  endif
+
+  " Indent the body of a case.
+  " If the previous line ended in a semicolon and the line before that was a
+  " case, don't do any special indenting.
+  if prevline =~# '\v;\s*(//.*)?$' && prevprevline =~# '\v\=\>\s*(//.*)?$' && line !~# '\v^\s*}'
+    return indent(prevlnum)
+  endif
+
+  let l:indent = FloorCindent(v:lnum)
+
+  " If the previous line was a case and a normal cindent wouldn't indent, indent
+  " an extra shiftwidth.
+  if prevline =~# '\v\=\>\s*(//.*)?$' && l:indent == indent(prevlnum)
+    return l:indent + shiftwidth()
+  endif
+
+  " If everything above is false, do a normal cindent.
+  return l:indent
+endfunction
+
+" vim: tabstop=2 shiftwidth=2 expandtab
diff --git a/runtime/indent/solidity.vim b/runtime/indent/solidity.vim
new file mode 100644
index 0000000..caed726
--- /dev/null
+++ b/runtime/indent/solidity.vim
@@ -0,0 +1,442 @@
+" Vim indent file
+" Language: 		Solidity
+" Acknowledgement: 	Based off of vim-javascript
+" Maintainer: 		Cothi (jiungdev@gmail.com)
+" Original Author: 	tomlion (https://github.com/tomlion/vim-solidity)
+" Last Changed: 	2022 Sep 27
+"
+" 0. Initialization {{{1
+" =================
+
+" Only load this indent file when no other was loaded.
+if exists("b:did_indent")
+  finish
+endif
+let b:did_indent = 1
+
+setlocal nosmartindent
+
+" Now, set up our indentation expression and keys that trigger it.
+setlocal indentexpr=GetSolidityIndent()
+setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e
+
+" Only define the function once.
+if exists("*GetSolidityIndent")
+  finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" 1. Variables {{{1
+" ============
+
+let s:js_keywords = '^\s*\(break\|case\|catch\|continue\|debugger\|default\|delete\|do\|else\|finally\|for\|function\|if\|in\|instanceof\|new\|return\|switch\|this\|throw\|try\|typeof\|var\|void\|while\|with\)'
+
+" Regex of syntax group names that are or delimit string or are comments.
+let s:syng_strcom = 'string\|regex\|comment\c'
+
+" Regex of syntax group names that are strings.
+let s:syng_string = 'regex\c'
+
+" Regex of syntax group names that are strings or documentation.
+let s:syng_multiline = 'comment\c'
+
+" Regex of syntax group names that are line comment.
+let s:syng_linecom = 'linecomment\c'
+
+" Expression used to check whether we should skip a match with searchpair().
+let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'"
+
+let s:line_term = '\s*\%(\%(\/\/\).*\)\=$'
+
+" Regex that defines continuation lines, not including (, {, or [.
+let s:continuation_regex = '\%([\\*+/.:]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)' . s:line_term
+
+" Regex that defines continuation lines.
+" TODO: this needs to deal with if ...: and so on
+let s:msl_regex = '\%([\\*+/.:([]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)' . s:line_term
+
+let s:one_line_scope_regex = '\<\%(if\|else\|for\|while\)\>[^{;]*' . s:line_term
+
+" Regex that defines blocks.
+let s:block_regex = '\%([{[]\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term
+
+let s:var_stmt = '^\s*var'
+
+let s:comma_first = '^\s*,'
+let s:comma_last = ',\s*$'
+
+let s:ternary = '^\s\+[?|:]'
+let s:ternary_q = '^\s\+?'
+
+" 2. Auxiliary Functions {{{1
+" ======================
+
+" Check if the character at lnum:col is inside a string, comment, or is ascii.
+function s:IsInStringOrComment(lnum, col)
+  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
+endfunction
+
+" Check if the character at lnum:col is inside a string.
+function s:IsInString(lnum, col)
+  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
+endfunction
+
+" Check if the character at lnum:col is inside a multi-line comment.
+function s:IsInMultilineComment(lnum, col)
+  return !s:IsLineComment(a:lnum, a:col) && synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_multiline
+endfunction
+
+" Check if the character at lnum:col is a line comment.
+function s:IsLineComment(lnum, col)
+  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_linecom
+endfunction
+
+" Find line above 'lnum' that isn't empty, in a comment, or in a string.
+function s:PrevNonBlankNonString(lnum)
+  let in_block = 0
+  let lnum = prevnonblank(a:lnum)
+  while lnum > 0
+    " Go in and out of blocks comments as necessary.
+    " If the line isn't empty (with opt. comment) or in a string, end search.
+    let line = getline(lnum)
+    if line =~ '/\*'
+      if in_block
+        let in_block = 0
+      else
+        break
+      endif
+    elseif !in_block && line =~ '\*/'
+      let in_block = 1
+    elseif !in_block && line !~ '^\s*\%(//\).*$' && !(s:IsInStringOrComment(lnum, 1) && s:IsInStringOrComment(lnum, strlen(line)))
+      break
+    endif
+    let lnum = prevnonblank(lnum - 1)
+  endwhile
+  return lnum
+endfunction
+
+" Find line above 'lnum' that started the continuation 'lnum' may be part of.
+function s:GetMSL(lnum, in_one_line_scope)
+  " Start on the line we're at and use its indent.
+  let msl = a:lnum
+  let lnum = s:PrevNonBlankNonString(a:lnum - 1)
+  while lnum > 0
+    " If we have a continuation line, or we're in a string, use line as MSL.
+    " Otherwise, terminate search as we have found our MSL already.
+    let line = getline(lnum)
+    let col = match(line, s:msl_regex) + 1
+    if (col > 0 && !s:IsInStringOrComment(lnum, col)) || s:IsInString(lnum, strlen(line))
+      let msl = lnum
+    else
+      " Don't use lines that are part of a one line scope as msl unless the
+      " flag in_one_line_scope is set to 1
+      "
+      if a:in_one_line_scope
+        break
+      end
+      let msl_one_line = s:Match(lnum, s:one_line_scope_regex)
+      if msl_one_line == 0
+        break
+      endif
+    endif
+    let lnum = s:PrevNonBlankNonString(lnum - 1)
+  endwhile
+  return msl
+endfunction
+
+function s:RemoveTrailingComments(content)
+  let single = '\/\/\(.*\)\s*$'
+  let multi = '\/\*\(.*\)\*\/\s*$'
+  return substitute(substitute(a:content, single, '', ''), multi, '', '')
+endfunction
+
+" Find if the string is inside var statement (but not the first string)
+function s:InMultiVarStatement(lnum)
+  let lnum = s:PrevNonBlankNonString(a:lnum - 1)
+
+"  let type = synIDattr(synID(lnum, indent(lnum) + 1, 0), 'name')
+
+  " loop through previous expressions to find a var statement
+  while lnum > 0
+    let line = getline(lnum)
+
+    " if the line is a js keyword
+    if (line =~ s:js_keywords)
+      " check if the line is a var stmt
+      " if the line has a comma first or comma last then we can assume that we
+      " are in a multiple var statement
+      if (line =~ s:var_stmt)
+        return lnum
+      endif
+
+      " other js keywords, not a var
+      return 0
+    endif
+
+    let lnum = s:PrevNonBlankNonString(lnum - 1)
+  endwhile
+
+  " beginning of program, not a var
+  return 0
+endfunction
+
+" Find line above with beginning of the var statement or returns 0 if it's not
+" this statement
+function s:GetVarIndent(lnum)
+  let lvar = s:InMultiVarStatement(a:lnum)
+  let prev_lnum = s:PrevNonBlankNonString(a:lnum - 1)
+
+  if lvar
+    let line = s:RemoveTrailingComments(getline(prev_lnum))
+
+    " if the previous line doesn't end in a comma, return to regular indent
+    if (line !~ s:comma_last)
+      return indent(prev_lnum) - &sw
+    else
+      return indent(lvar) + &sw
+    endif
+  endif
+
+  return -1
+endfunction
+
+
+" Check if line 'lnum' has more opening brackets than closing ones.
+function s:LineHasOpeningBrackets(lnum)
+  let open_0 = 0
+  let open_2 = 0
+  let open_4 = 0
+  let line = getline(a:lnum)
+  let pos = match(line, '[][(){}]', 0)
+  while pos != -1
+    if !s:IsInStringOrComment(a:lnum, pos + 1)
+      let idx = stridx('(){}[]', line[pos])
+      if idx % 2 == 0
+        let open_{idx} = open_{idx} + 1
+      else
+        let open_{idx - 1} = open_{idx - 1} - 1
+      endif
+    endif
+    let pos = match(line, '[][(){}]', pos + 1)
+  endwhile
+  return (open_0 > 0) . (open_2 > 0) . (open_4 > 0)
+endfunction
+
+function s:Match(lnum, regex)
+  let col = match(getline(a:lnum), a:regex) + 1
+  return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
+endfunction
+
+function s:IndentWithContinuation(lnum, ind, width)
+  " Set up variables to use and search for MSL to the previous line.
+  let p_lnum = a:lnum
+  let lnum = s:GetMSL(a:lnum, 1)
+  let line = getline(lnum)
+
+  " If the previous line wasn't a MSL and is continuation return its indent.
+  " TODO: the || s:IsInString() thing worries me a bit.
+  if p_lnum != lnum
+    if s:Match(p_lnum,s:continuation_regex)||s:IsInString(p_lnum,strlen(line))
+      return a:ind
+    endif
+  endif
+
+  " Set up more variables now that we know we aren't continuation bound.
+  let msl_ind = indent(lnum)
+
+  " If the previous line ended with [*+/.-=], start a continuation that
+  " indents an extra level.
+  if s:Match(lnum, s:continuation_regex)
+    if lnum == p_lnum
+      return msl_ind + a:width
+    else
+      return msl_ind
+    endif
+  endif
+
+  return a:ind
+endfunction
+
+function s:InOneLineScope(lnum)
+  let msl = s:GetMSL(a:lnum, 1)
+  if msl > 0 && s:Match(msl, s:one_line_scope_regex)
+    return msl
+  endif
+  return 0
+endfunction
+
+function s:ExitingOneLineScope(lnum)
+  let msl = s:GetMSL(a:lnum, 1)
+  if msl > 0
+    " if the current line is in a one line scope ..
+    if s:Match(msl, s:one_line_scope_regex)
+      return 0
+    else
+      let prev_msl = s:GetMSL(msl - 1, 1)
+      if s:Match(prev_msl, s:one_line_scope_regex)
+        return prev_msl
+      endif
+    endif
+  endif
+  return 0
+endfunction
+
+" 3. GetSolidityIndent Function {{{1
+" =========================
+
+function GetSolidityIndent()
+  " 3.1. Setup {{{2
+  " ----------
+
+  " Set up variables for restoring position in file.  Could use v:lnum here.
+  let vcol = col('.')
+
+  " 3.2. Work on the current line {{{2
+  " -----------------------------
+
+  let ind = -1
+  " Get the current line.
+  let line = getline(v:lnum)
+  " previous nonblank line number
+  let prevline = prevnonblank(v:lnum - 1)
+
+  " If we got a closing bracket on an empty line, find its match and indent
+  " according to it.  For parentheses we indent to its column - 1, for the
+  " others we indent to the containing line's MSL's level.  Return -1 if fail.
+  let col = matchend(line, '^\s*[],})]')
+  if col > 0 && !s:IsInStringOrComment(v:lnum, col)
+    call cursor(v:lnum, col)
+
+    let lvar = s:InMultiVarStatement(v:lnum)
+    if lvar
+      let prevline_contents = s:RemoveTrailingComments(getline(prevline))
+
+      " check for comma first
+      if (line[col - 1] =~ ',')
+        " if the previous line ends in comma or semicolon don't indent
+        if (prevline_contents =~ '[;,]\s*$')
+          return indent(s:GetMSL(line('.'), 0))
+        " get previous line indent, if it's comma first return prevline indent
+        elseif (prevline_contents =~ s:comma_first)
+          return indent(prevline)
+        " otherwise we indent 1 level
+        else
+          return indent(lvar) + &sw
+        endif
+      endif
+    endif
+
+
+    let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
+    if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
+      if line[col-1]==')' && col('.') != col('$') - 1
+        let ind = virtcol('.')-1
+      else
+        let ind = indent(s:GetMSL(line('.'), 0))
+      endif
+    endif
+    return ind
+  endif
+
+  " If the line is comma first, dedent 1 level
+  if (getline(prevline) =~ s:comma_first)
+    return indent(prevline) - &sw
+  endif
+
+  if (line =~ s:ternary)
+    if (getline(prevline) =~ s:ternary_q)
+      return indent(prevline)
+    else
+      return indent(prevline) + &sw
+    endif
+  endif
+
+  " If we are in a multi-line comment, cindent does the right thing.
+  if s:IsInMultilineComment(v:lnum, 1) && !s:IsLineComment(v:lnum, 1)
+    return cindent(v:lnum)
+  endif
+
+  " Check for multiple var assignments
+"  let var_indent = s:GetVarIndent(v:lnum)
+"  if var_indent >= 0
+"    return var_indent
+"  endif
+
+  " 3.3. Work on the previous line. {{{2
+  " -------------------------------
+
+  " If the line is empty and the previous nonblank line was a multi-line
+  " comment, use that comment's indent. Deduct one char to account for the
+  " space in ' */'.
+  if line =~ '^\s*$' && s:IsInMultilineComment(prevline, 1)
+    return indent(prevline) - 1
+  endif
+
+  " Find a non-blank, non-multi-line string line above the current line.
+  let lnum = s:PrevNonBlankNonString(v:lnum - 1)
+
+  " If the line is empty and inside a string, use the previous line.
+  if line =~ '^\s*$' && lnum != prevline
+    return indent(prevnonblank(v:lnum))
+  endif
+
+  " At the start of the file use zero indent.
+  if lnum == 0
+    return 0
+  endif
+
+  " Set up variables for current line.
+  let line = getline(lnum)
+  let ind = indent(lnum)
+
+  " If the previous line ended with a block opening, add a level of indent.
+  if s:Match(lnum, s:block_regex)
+    return indent(s:GetMSL(lnum, 0)) + &sw
+  endif
+
+  " If the previous line contained an opening bracket, and we are still in it,
+  " add indent depending on the bracket type.
+  if line =~ '[[({]'
+    let counts = s:LineHasOpeningBrackets(lnum)
+    if counts[0] == '1' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
+      if col('.') + 1 == col('$')
+        return ind + &sw
+      else
+        return virtcol('.')
+      endif
+    elseif counts[1] == '1' || counts[2] == '1'
+      return ind + &sw
+    else
+      call cursor(v:lnum, vcol)
+    end
+  endif
+
+  " 3.4. Work on the MSL line. {{{2
+  " --------------------------
+
+  let ind_con = ind
+  let ind = s:IndentWithContinuation(lnum, ind_con, &sw)
+
+  " }}}2
+  "
+  "
+  let ols = s:InOneLineScope(lnum)
+  if ols > 0
+    let ind = ind + &sw
+  else
+    let ols = s:ExitingOneLineScope(lnum)
+    while ols > 0 && ind > 0
+      let ind = ind - &sw
+      let ols = s:InOneLineScope(ols - 1)
+    endwhile
+  endif
+
+  return ind
+endfunction
+
+" }}}1
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/indent/testdir/vim.in b/runtime/indent/testdir/vim.in
index 5eb262f..0582a93 100644
--- a/runtime/indent/testdir/vim.in
+++ b/runtime/indent/testdir/vim.in
@@ -1,7 +1,6 @@
 " vim: set ft=vim sw=4 :
 
 " START_INDENT
-
 func Some()
 let x = 1
 endfunc
@@ -15,15 +14,6 @@
 \ ]
 endif
 
-" TODO: add searchpair() to find matching {
-"for x in [
-"{
-"key: 'value'
-"},
-"]
-"eval 0
-"endfor
-
 for x in [
 {key: 'value'},
 ]
@@ -37,12 +27,11 @@
 \  ]
 
 def Func()
-  var d = dd
-	->extend({
-	})
-  eval 0
+var d = dd
+->extend({
+})
+eval 0
 enddef
-
 " END_INDENT
 
 " START_INDENT
@@ -84,7 +73,7 @@
 " START_INDENT
 " INDENT_NEXT  next-line
 func Some()
-     " next-line
+    " next-line
 let f = x
 endfunc
 " END_INDENT
@@ -104,6 +93,12 @@
 " END_INDENT
 
 " START_INDENT
+let a =<< trim END
+nothing
+END
+" END_INDENT
+
+" START_INDENT
 " INDENT_AT  this-line
 let a=<< trim END
    blah
@@ -111,3 +106,754 @@
       blah this-line
 END
 " END_INDENT
+
+" START_INDENT
+if v:true
+echo 0
+end
+" END_INDENT
+
+" START_INDENT
+var result = Func(
+arg1,
+arg2
+)
+" END_INDENT
+
+" START_INDENT
+var result = Func(arg1,
+arg2)
+" END_INDENT
+
+" START_INDENT
+filter(list, (k, v) =>
+v > 0)
+" END_INDENT
+
+" START_INDENT
+filter(list, (k, v) => {
+const x = get(list, k, 0)
+return x > 0
+})
+" END_INDENT
+
+" START_INDENT
+if x > 0
+filter(list, (k, v) => {
+const x = get(list, k, 1)
+return x > 0
+})
+endif
+" END_INDENT
+
+" START_INDENT
+{
+var temp = 'temp'
+}
+" END_INDENT
+
+" START_INDENT
+var text = lead
+.. middle
+.. end
+" END_INDENT
+
+" START_INDENT
+var text = lead ..
+middle ..
+end
+" END_INDENT
+
+" START_INDENT
+var total = start +
+end -
+correction
+" END_INDENT
+
+" START_INDENT
+var result = start
+:+ print
+" END_INDENT
+
+" START_INDENT
+var result = positive
+? PosFunc(arg)
+: NegFunc(arg)
+" END_INDENT
+
+" START_INDENT
+var result = GetBuilder()
+->BuilderSetWidth(333)
+->BuilderSetHeight(777)
+->BuilderBuild()
+" END_INDENT
+
+" START_INDENT
+var result = MyDict
+.member
+" END_INDENT
+
+" START_INDENT
+autocmd BufNewFile *.match if condition
+|   echo 'match'
+| endif
+" END_INDENT
+
+" START_INDENT
+set cpo+=C
+var lines =<< trim END
+| this works
+END
+set cpo-=C
+" END_INDENT
+
+" START_INDENT
+syn region Text
+\ start='foo'
+#\ comment
+\ end='bar'
+" END_INDENT
+
+" START_INDENT
+au CursorHold * echom 'BEFORE bar'
+#\ some comment
+| echom 'AFTER bar'
+" END_INDENT
+
+" START_INDENT
+def MyFunc(text: string,
+separator = '-'
+): string
+enddef
+" END_INDENT
+
+" START_INDENT
+def MyFunc(
+text: string,
+separator = '-'
+): string
+enddef
+" END_INDENT
+
+" START_INDENT
+[var1, var2] =
+Func()
+" END_INDENT
+
+" START_INDENT
+const list = ['one',
+'two']
+" END_INDENT
+
+" START_INDENT
+const list = [
+'one',
+'two',
+]
+" END_INDENT
+
+" START_INDENT
+const dict = {one: 1,
+two: 2
+}
+" END_INDENT
+
+" START_INDENT
+const dict = {
+one: 1,
+two: 2
+}
+" END_INDENT
+
+" START_INDENT
+if true
+const dict =
+{
+one: 1,
+two: 2
+}
+endif
+" END_INDENT
+
+" START_INDENT
+def Func()
+return {
+one: 1
+}
+enddef
+" END_INDENT
+
+" START_INDENT
+echo {
+a: 0,
+# b
+# c
+}
+" END_INDENT
+
+" START_INDENT
+echo search(
+# comment
+'1'
+.. '2'
+)
+" END_INDENT
+
+" START_INDENT
+if true
+var v = (      # trailing "(" starts line continuation
+3 + 4      # nothing special
+)              # end of expression indicates continued line
+var x: number  # needs to align with previous "var"
+endif
+" END_INDENT
+
+" START_INDENT
+def Func() # {{{
+# comment
+if true
+return
+endif
+enddef
+" END_INDENT
+
+" START_INDENT
+echo {
+key:
+'value',
+}
+" END_INDENT
+
+" START_INDENT
+var id = time
+->timer_start((_) => {
+n = 0
+})
+" END_INDENT
+
+" START_INDENT
+augroup Name
+autocmd!
+augroup END
+" END_INDENT
+
+" START_INDENT
+var n =
+# comment
+1
++ 2
+
+var s = ''
+" END_INDENT
+
+" START_INDENT
+var keys = {
+J: 'j',
+"\<Home>": '1G',
+"\<End>": 'G',
+z: 'zz'
+}
+" END_INDENT
+
+" START_INDENT
+export def Func(
+n: number,
+s: string,
+...l: list<bool>
+)
+enddef
+" END_INDENT
+
+" START_INDENT
+var heredoc =<< trim ENDD
+var nested_heredoc =<< trim END
+END
+ENDD
+" END_INDENT
+
+" START_INDENT
+if true
+else  " comment
+endif
+" END_INDENT
+
+" START_INDENT
+if true | echo 'one' | endif
+if true | echo 'two' | endif
+if true | echo 'three' | endif
+" END_INDENT
+
+" START_INDENT
+if true
+:'<-1 mark <
+else
+echo ''
+endif
+" END_INDENT
+
+" START_INDENT
+substitute/pat /rep /
+echo
+" END_INDENT
+
+" START_INDENT
+try
+echo 1
+catch /pat /  # comment
+echo 2
+endtry
+" END_INDENT
+
+" START_INDENT
+def Func()
+Cmd %
+enddef
+" END_INDENT
+
+" START_INDENT
+if end == 'xxx' || end == 'yyy'
+echo
+endif
+" END_INDENT
+
+" START_INDENT
+if true
+popup_move(id, {col: 1,
+line: 2})
+endif
+setwinvar(id, 'name', 3)
+" END_INDENT
+
+" START_INDENT
+var d = [
+{a: 'x',
+b: 'y'},
+FuncA(),
+FuncB(),
+]
+" END_INDENT
+
+" START_INDENT
+var ll = [[
+1,
+2,
+3], [
+4,
+5,
+6], [
+7,
+8,
+9]]
+" END_INDENT
+
+" START_INDENT
+var ld = [{
+a: 'xxx',
+b: 'yyy'}, {
+c: 'xxx',
+d: 'yyy'}, {
+e: 'xxx',
+f: 'yyy'}, {
+}]
+" END_INDENT
+
+" START_INDENT
+var d = {
+a: {
+b: {
+c: [{
+d: 'e',
+f: 'g',
+h: 'i'
+}],
+j: 'k',
+},
+},
+}
+" END_INDENT
+
+" START_INDENT
+if true
+var end: any
+if true
+end = 0
+elseif true
+echo
+endif
+endif
+" END_INDENT
+
+" START_INDENT
+nunmap <buffer> (
+nunmap <buffer> )
+inoremap [ {
+inoremap ] }
+silent! xunmap i{
+silent! xunmap a{
+" END_INDENT
+
+" START_INDENT
+def Func(
+s: string,
+n = 1,
+m = 2
+)
+enddef
+" END_INDENT
+
+" START_INDENT
+var h =<< END
+text
+END
+
+def Func()
+echo
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func()
+var h =<< END
+text
+END
+echo 'test'
+enddef
+" END_INDENT
+
+" START_INDENT
+def Foo()
+lcd -
+enddef
+def Bar()
+echo
+enddef
+" END_INDENT
+
+" START_INDENT
+if true
+n = Func(1, 2,
+3)
+endif
+" END_INDENT
+
+" START_INDENT
+def Func(s: string,
+n: number): bool
+if true
+return false
+endif
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func(
+n: number)
+#
+echo
+enddef
+" END_INDENT
+
+" START_INDENT
+" INDENT_AT  this-line
+def Func(
+	n: number)
+    #
+echo  # this-line
+enddef
+" END_INDENT
+
+" START_INDENT
+if true
+if true
+normal! ==
+endif
+endif
+" END_INDENT
+
+" START_INDENT
+var d = {
+a: () => true,
+b: () => true
+&& true
+&& Foo(),
+c: () => Bar(),
+e: () => Baz(),
+}
+" END_INDENT
+
+" START_INDENT
+def Select(Cont: func(func(any)), Pred: func(any): bool): func(func(any))
+return (Emit: func(any)) => {
+Cont((t: any) => {
+if Pred(t)
+Emit(t)
+endif
+})
+}
+enddef
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE let g:vim_indent = {'more_in_bracket_block': v:true}
+def Select(Cont: func(func(any)), Pred: func(any): bool): func(func(any))
+return (Emit: func(any)) => {
+Cont((t: any) => {
+if Pred(t)
+Emit(t)
+endif
+})
+}
+enddef
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE unlet! g:vim_indent
+" END_INDENT
+
+" START_INDENT
+g:lightline = {
+'active': {
+'left': [ [ 'mode', 'paste' ], [ 'readonly', 'relativepath', 'modified' ] ],
+},
+'inactive': {
+'left': [ [ 'readonly', 'relativepath', 'modified' ] ],
+}
+}
+" END_INDENT
+
+" START_INDENT
+if getline(1, 10)
+->map((_, v: string): number => strcharlen(v))
+->max() > 1'000
+&l:breakindent = false
+&l:linebreak = false
+else
+&l:breakindent = true
+&l:linebreak = true
+endif
+" END_INDENT
+
+" START_INDENT
+var ext2cmd: dict<string> = {
+doc: $'antiword {fname}',
+docx: $'pandoc --from=docx --to=markdown {fname}',
+epub: $'pandoc --from=epub --to=markdown {fname}',
+odp: $'odt2txt {fname}',
+odt: $'odt2txt {fname}',
+pdf: $'pdftotext -nopgbrk -layout -q -eol unix {fname} -',
+rtf: 'unrtf --text',
+}
+" END_INDENT
+
+" START_INDENT
+const ptybuf: number = term_start(&shell, {
+hidden: true,
+exit_cb: (_, _) => {
+if true
+close
+else
+help
+endif
+}
+})
+" END_INDENT
+
+" START_INDENT
+var d = {
+a: 0,
+# a ' quote {{{
+#}}}
+b: 0,
+}
+" END_INDENT
+
+" START_INDENT
+echo printf('%s () %s',
+1,
+2
+)
+" END_INDENT
+
+" START_INDENT
+prop_add(1, col('.'), {
+length: 2,
+type: 'test'
+})
+" END_INDENT
+
+" START_INDENT
+echo (() => " string starting with space")()
+echo
+" END_INDENT
+
+" START_INDENT
+var variables = deepcopy(g:)
+->filter((k: string, _): bool =>
+k =~ '\c\V' .. keyword->escape('\')
+&& k !~ '\%(loaded\|did_plugin_\)')
+->items()
+->map((_, v): string => v[0] .. ' = ' .. string(v[1]))
+new
+" END_INDENT
+
+" START_INDENT
+var d = freq
+->map((_, v) =>
+v * (
+1
++ 2
+))
+for item in d
+->items()
+->sort((a, b) => b[1] - a[1])
+echo
+endfor
+" END_INDENT
+
+" START_INDENT
+make_job = job_start([&shell, &shellcmdflag, make_cmd], {
+callback: function(MakeProcessOutput, [qfid]),
+close_cb: function(MakeCloseCb, [qfid]),
+exit_cb: MakeCompleted,
+in_io: 'null'
+})
+" END_INDENT
+
+" START_INDENT
+var matching_abbrev: list<dict<string>> = copy(ABBREV)
+->filter((_, v: dict<string>): bool =>
+stridx(v.lhs, word_to_complete) == 0)
+->map((_, v: dict<string>) => ({
+word: v.lhs,
+menu: AbbrevRhs(v.rhs)->stridx('expand_') >= 0
+?    AbbrevRhs(v.rhs)->matchstr('.*,\s*''\zs.*\ze'')')
+:    AbbrevRhs(v.rhs)
+}))
+" END_INDENT
+
+" START_INDENT
+def Func()
+if true
+vimgrep /^\C\s*\%(fu\%[nction]\|def\)\s\+/ file
+endif
+enddef
+" END_INDENT
+
+" START_INDENT
+setlocal iskeyword+=[
+cword = expand('<cword>')
+" END_INDENT
+
+" START_INDENT
+silent if true
+echo
+endif
+" END_INDENT
+
+" START_INDENT
+def Func()
+sort :^.*[\/]:
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func()
+d = {
+}
+hd =<< trim END
+['
+]'
+END
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func()
+if true
+var hd =<< trim END
+if get(b:, 'current_syntax', '')
+endif
+END
+elseif true
+echo
+endif
+enddef
+" END_INDENT
+
+" START_INDENT
+# test for control-flow keyword followed by commented fold marker {{{
+if true
+echo
+endif #}}}
+" END_INDENT
+
+" START_INDENT
+if winsz == 0|let winsz= ""|endif
+exe "noswapfile ".winsz."wincmd s"
+" END_INDENT
+
+" START_INDENT
+if true
+if true
+windo if true | echo | endif
+augroup Name
+autocmd WinLeave * if true | eval 1 + 2 | endif
+augroup END
+endif
+endif
+" END_INDENT
+
+" START_INDENT
+if true
+echo ' =<< trim END'
+->len()
+endif
+" END_INDENT
+
+" START_INDENT
+function Func()
+if true
+if true
+if true | echo com | endif
+if true | echo com | endif
+endif
+else
+endif
+endfunction
+" END_INDENT
+
+" START_INDENT
+function Func()
+if v:true
++
+echo
+-
+endif
+endfunction
+" END_INDENT
+
+" START_INDENT
+var matchpairs: string = &matchpairs
+var pairs: dict<list<string>>
+for [opening: string, closing: string]
+in matchpairs
+->split(',')
+->map((_, v: string): list<string> => split(v, ':'))
+pairs[opening] = [escape(opening, '[]'), escape(closing, '[]'),  'nW', 'w$']
+pairs[closing] = [escape(opening, '[]'), escape(closing, '[]'), 'bnW', 'w0']
+endfor
+" END_INDENT
+
+" START_INDENT
+{
+echo []
++ []
++ [{a: 1,
+b: 2}]
+}
+" END_INDENT
+
+" START_INDENT
+silent! argdel *
+edit file
+" END_INDENT
diff --git a/runtime/indent/testdir/vim.ok b/runtime/indent/testdir/vim.ok
index 932eebe..39efdba 100644
--- a/runtime/indent/testdir/vim.ok
+++ b/runtime/indent/testdir/vim.ok
@@ -1,7 +1,6 @@
 " vim: set ft=vim sw=4 :
 
 " START_INDENT
-
 func Some()
     let x = 1
 endfunc
@@ -15,15 +14,6 @@
 		\ ]
 endif
 
-" TODO: add searchpair() to find matching {
-"for x in [
-"{
-"key: 'value'
-"},
-"]
-"eval 0
-"endfor
-
 for x in [
 	{key: 'value'},
 	]
@@ -38,11 +28,10 @@
 
 def Func()
     var d = dd
-		->extend({
-    })
+	->extend({
+	})
     eval 0
 enddef
-
 " END_INDENT
 
 " START_INDENT
@@ -69,7 +58,7 @@
 let list = [
     'one',
     'two',
-    ]
+]
 echo
 
 " END_INDENT
@@ -84,8 +73,8 @@
 " START_INDENT
 " INDENT_NEXT  next-line
 func Some()
-     " next-line
-     let f = x
+    " next-line
+    let f = x
 endfunc
 " END_INDENT
 
@@ -99,6 +88,12 @@
 
 " START_INDENT
 let a =<< END
+nothing
+END
+" END_INDENT
+
+" START_INDENT
+let a =<< trim END
     nothing
 END
 " END_INDENT
@@ -111,3 +106,754 @@
       blah this-line
 END
 " END_INDENT
+
+" START_INDENT
+if v:true
+    echo 0
+end
+" END_INDENT
+
+" START_INDENT
+var result = Func(
+    arg1,
+    arg2
+)
+" END_INDENT
+
+" START_INDENT
+var result = Func(arg1,
+    arg2)
+" END_INDENT
+
+" START_INDENT
+filter(list, (k, v) =>
+    v > 0)
+" END_INDENT
+
+" START_INDENT
+filter(list, (k, v) => {
+    const x = get(list, k, 0)
+    return x > 0
+})
+" END_INDENT
+
+" START_INDENT
+if x > 0
+    filter(list, (k, v) => {
+	const x = get(list, k, 1)
+	return x > 0
+    })
+endif
+" END_INDENT
+
+" START_INDENT
+{
+    var temp = 'temp'
+}
+" END_INDENT
+
+" START_INDENT
+var text = lead
+    .. middle
+    .. end
+" END_INDENT
+
+" START_INDENT
+var text = lead ..
+    middle ..
+    end
+" END_INDENT
+
+" START_INDENT
+var total = start +
+    end -
+    correction
+" END_INDENT
+
+" START_INDENT
+var result = start
+:+ print
+" END_INDENT
+
+" START_INDENT
+var result = positive
+    ? PosFunc(arg)
+    : NegFunc(arg)
+" END_INDENT
+
+" START_INDENT
+var result = GetBuilder()
+    ->BuilderSetWidth(333)
+    ->BuilderSetHeight(777)
+    ->BuilderBuild()
+" END_INDENT
+
+" START_INDENT
+var result = MyDict
+    .member
+" END_INDENT
+
+" START_INDENT
+autocmd BufNewFile *.match if condition
+    |   echo 'match'
+    | endif
+" END_INDENT
+
+" START_INDENT
+set cpo+=C
+var lines =<< trim END
+    | this works
+END
+set cpo-=C
+" END_INDENT
+
+" START_INDENT
+syn region Text
+	    \ start='foo'
+	    #\ comment
+	    \ end='bar'
+" END_INDENT
+
+" START_INDENT
+au CursorHold * echom 'BEFORE bar'
+    #\ some comment
+    | echom 'AFTER bar'
+" END_INDENT
+
+" START_INDENT
+def MyFunc(text: string,
+	separator = '-'
+	): string
+enddef
+" END_INDENT
+
+" START_INDENT
+def MyFunc(
+	text: string,
+	separator = '-'
+	): string
+enddef
+" END_INDENT
+
+" START_INDENT
+[var1, var2] =
+    Func()
+" END_INDENT
+
+" START_INDENT
+const list = ['one',
+    'two']
+" END_INDENT
+
+" START_INDENT
+const list = [
+    'one',
+    'two',
+]
+" END_INDENT
+
+" START_INDENT
+const dict = {one: 1,
+    two: 2
+}
+" END_INDENT
+
+" START_INDENT
+const dict = {
+    one: 1,
+    two: 2
+}
+" END_INDENT
+
+" START_INDENT
+if true
+    const dict =
+	{
+	    one: 1,
+	    two: 2
+	}
+endif
+" END_INDENT
+
+" START_INDENT
+def Func()
+    return {
+	one: 1
+    }
+enddef
+" END_INDENT
+
+" START_INDENT
+echo {
+    a: 0,
+    # b
+    # c
+}
+" END_INDENT
+
+" START_INDENT
+echo search(
+    # comment
+    '1'
+    .. '2'
+)
+" END_INDENT
+
+" START_INDENT
+if true
+    var v = (      # trailing "(" starts line continuation
+	3 + 4      # nothing special
+    )              # end of expression indicates continued line
+    var x: number  # needs to align with previous "var"
+endif
+" END_INDENT
+
+" START_INDENT
+def Func() # {{{
+    # comment
+    if true
+	return
+    endif
+enddef
+" END_INDENT
+
+" START_INDENT
+echo {
+    key:
+	'value',
+}
+" END_INDENT
+
+" START_INDENT
+var id = time
+    ->timer_start((_) => {
+	n = 0
+    })
+" END_INDENT
+
+" START_INDENT
+augroup Name
+    autocmd!
+augroup END
+" END_INDENT
+
+" START_INDENT
+var n =
+    # comment
+    1
+    + 2
+
+var s = ''
+" END_INDENT
+
+" START_INDENT
+var keys = {
+    J: 'j',
+    "\<Home>": '1G',
+    "\<End>": 'G',
+    z: 'zz'
+}
+" END_INDENT
+
+" START_INDENT
+export def Func(
+	n: number,
+	s: string,
+	...l: list<bool>
+	)
+enddef
+" END_INDENT
+
+" START_INDENT
+var heredoc =<< trim ENDD
+    var nested_heredoc =<< trim END
+    END
+ENDD
+" END_INDENT
+
+" START_INDENT
+if true
+else  " comment
+endif
+" END_INDENT
+
+" START_INDENT
+if true | echo 'one' | endif
+if true | echo 'two' | endif
+if true | echo 'three' | endif
+" END_INDENT
+
+" START_INDENT
+if true
+    :'<-1 mark <
+else
+    echo ''
+endif
+" END_INDENT
+
+" START_INDENT
+substitute/pat /rep /
+echo
+" END_INDENT
+
+" START_INDENT
+try
+    echo 1
+catch /pat /  # comment
+    echo 2
+endtry
+" END_INDENT
+
+" START_INDENT
+def Func()
+    Cmd %
+enddef
+" END_INDENT
+
+" START_INDENT
+if end == 'xxx' || end == 'yyy'
+    echo
+endif
+" END_INDENT
+
+" START_INDENT
+if true
+    popup_move(id, {col: 1,
+	line: 2})
+endif
+setwinvar(id, 'name', 3)
+" END_INDENT
+
+" START_INDENT
+var d = [
+    {a: 'x',
+	b: 'y'},
+    FuncA(),
+    FuncB(),
+]
+" END_INDENT
+
+" START_INDENT
+var ll = [[
+    1,
+    2,
+    3], [
+    4,
+    5,
+    6], [
+    7,
+    8,
+    9]]
+" END_INDENT
+
+" START_INDENT
+var ld = [{
+    a: 'xxx',
+    b: 'yyy'}, {
+    c: 'xxx',
+    d: 'yyy'}, {
+    e: 'xxx',
+    f: 'yyy'}, {
+    }]
+" END_INDENT
+
+" START_INDENT
+var d = {
+    a: {
+	b: {
+	    c: [{
+		d: 'e',
+		f: 'g',
+		h: 'i'
+	    }],
+	    j: 'k',
+	},
+    },
+}
+" END_INDENT
+
+" START_INDENT
+if true
+    var end: any
+    if true
+	end = 0
+    elseif true
+	echo
+    endif
+endif
+" END_INDENT
+
+" START_INDENT
+nunmap <buffer> (
+nunmap <buffer> )
+inoremap [ {
+inoremap ] }
+silent! xunmap i{
+silent! xunmap a{
+" END_INDENT
+
+" START_INDENT
+def Func(
+	s: string,
+	n = 1,
+	m = 2
+	)
+enddef
+" END_INDENT
+
+" START_INDENT
+var h =<< END
+text
+END
+
+def Func()
+    echo
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func()
+    var h =<< END
+text
+END
+    echo 'test'
+enddef
+" END_INDENT
+
+" START_INDENT
+def Foo()
+    lcd -
+enddef
+def Bar()
+    echo
+enddef
+" END_INDENT
+
+" START_INDENT
+if true
+    n = Func(1, 2,
+	3)
+endif
+" END_INDENT
+
+" START_INDENT
+def Func(s: string,
+	n: number): bool
+    if true
+	return false
+    endif
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func(
+	n: number)
+    #
+    echo
+enddef
+" END_INDENT
+
+" START_INDENT
+" INDENT_AT  this-line
+def Func(
+	n: number)
+    #
+    echo  # this-line
+enddef
+" END_INDENT
+
+" START_INDENT
+if true
+    if true
+	normal! ==
+    endif
+endif
+" END_INDENT
+
+" START_INDENT
+var d = {
+    a: () => true,
+    b: () => true
+	&& true
+	&& Foo(),
+    c: () => Bar(),
+    e: () => Baz(),
+}
+" END_INDENT
+
+" START_INDENT
+def Select(Cont: func(func(any)), Pred: func(any): bool): func(func(any))
+    return (Emit: func(any)) => {
+	Cont((t: any) => {
+	    if Pred(t)
+		Emit(t)
+	    endif
+	})
+    }
+enddef
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE let g:vim_indent = {'more_in_bracket_block': v:true}
+def Select(Cont: func(func(any)), Pred: func(any): bool): func(func(any))
+    return (Emit: func(any)) => {
+	    Cont((t: any) => {
+		    if Pred(t)
+			Emit(t)
+		    endif
+		})
+	}
+enddef
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE unlet! g:vim_indent
+" END_INDENT
+
+" START_INDENT
+g:lightline = {
+    'active': {
+	'left': [ [ 'mode', 'paste' ], [ 'readonly', 'relativepath', 'modified' ] ],
+    },
+    'inactive': {
+	'left': [ [ 'readonly', 'relativepath', 'modified' ] ],
+    }
+}
+" END_INDENT
+
+" START_INDENT
+if getline(1, 10)
+	->map((_, v: string): number => strcharlen(v))
+	->max() > 1'000
+    &l:breakindent = false
+    &l:linebreak = false
+else
+    &l:breakindent = true
+    &l:linebreak = true
+endif
+" END_INDENT
+
+" START_INDENT
+var ext2cmd: dict<string> = {
+    doc: $'antiword {fname}',
+    docx: $'pandoc --from=docx --to=markdown {fname}',
+    epub: $'pandoc --from=epub --to=markdown {fname}',
+    odp: $'odt2txt {fname}',
+    odt: $'odt2txt {fname}',
+    pdf: $'pdftotext -nopgbrk -layout -q -eol unix {fname} -',
+    rtf: 'unrtf --text',
+}
+" END_INDENT
+
+" START_INDENT
+const ptybuf: number = term_start(&shell, {
+    hidden: true,
+    exit_cb: (_, _) => {
+	if true
+	    close
+	else
+	    help
+	endif
+    }
+})
+" END_INDENT
+
+" START_INDENT
+var d = {
+    a: 0,
+    # a ' quote {{{
+    #}}}
+    b: 0,
+}
+" END_INDENT
+
+" START_INDENT
+echo printf('%s () %s',
+    1,
+    2
+)
+" END_INDENT
+
+" START_INDENT
+prop_add(1, col('.'), {
+    length: 2,
+    type: 'test'
+})
+" END_INDENT
+
+" START_INDENT
+echo (() => " string starting with space")()
+echo
+" END_INDENT
+
+" START_INDENT
+var variables = deepcopy(g:)
+    ->filter((k: string, _): bool =>
+	k =~ '\c\V' .. keyword->escape('\')
+	&& k !~ '\%(loaded\|did_plugin_\)')
+    ->items()
+    ->map((_, v): string => v[0] .. ' = ' .. string(v[1]))
+new
+" END_INDENT
+
+" START_INDENT
+var d = freq
+    ->map((_, v) =>
+	v * (
+	    1
+	    + 2
+	))
+for item in d
+	->items()
+	->sort((a, b) => b[1] - a[1])
+    echo
+endfor
+" END_INDENT
+
+" START_INDENT
+make_job = job_start([&shell, &shellcmdflag, make_cmd], {
+    callback: function(MakeProcessOutput, [qfid]),
+    close_cb: function(MakeCloseCb, [qfid]),
+    exit_cb: MakeCompleted,
+    in_io: 'null'
+})
+" END_INDENT
+
+" START_INDENT
+var matching_abbrev: list<dict<string>> = copy(ABBREV)
+    ->filter((_, v: dict<string>): bool =>
+	stridx(v.lhs, word_to_complete) == 0)
+    ->map((_, v: dict<string>) => ({
+	word: v.lhs,
+	menu: AbbrevRhs(v.rhs)->stridx('expand_') >= 0
+	    ?    AbbrevRhs(v.rhs)->matchstr('.*,\s*''\zs.*\ze'')')
+	    :    AbbrevRhs(v.rhs)
+    }))
+" END_INDENT
+
+" START_INDENT
+def Func()
+    if true
+	vimgrep /^\C\s*\%(fu\%[nction]\|def\)\s\+/ file
+    endif
+enddef
+" END_INDENT
+
+" START_INDENT
+setlocal iskeyword+=[
+cword = expand('<cword>')
+" END_INDENT
+
+" START_INDENT
+silent if true
+    echo
+endif
+" END_INDENT
+
+" START_INDENT
+def Func()
+    sort :^.*[\/]:
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func()
+    d = {
+    }
+    hd =<< trim END
+	['
+	]'
+    END
+enddef
+" END_INDENT
+
+" START_INDENT
+def Func()
+    if true
+	var hd =<< trim END
+	    if get(b:, 'current_syntax', '')
+	    endif
+	END
+    elseif true
+	echo
+    endif
+enddef
+" END_INDENT
+
+" START_INDENT
+# test for control-flow keyword followed by commented fold marker {{{
+if true
+    echo
+endif #}}}
+" END_INDENT
+
+" START_INDENT
+if winsz == 0|let winsz= ""|endif
+exe "noswapfile ".winsz."wincmd s"
+" END_INDENT
+
+" START_INDENT
+if true
+    if true
+	windo if true | echo | endif
+	augroup Name
+	    autocmd WinLeave * if true | eval 1 + 2 | endif
+	augroup END
+    endif
+endif
+" END_INDENT
+
+" START_INDENT
+if true
+    echo ' =<< trim END'
+	->len()
+endif
+" END_INDENT
+
+" START_INDENT
+function Func()
+    if true
+	if true
+	    if true | echo com | endif
+	    if true | echo com | endif
+	endif
+    else
+    endif
+endfunction
+" END_INDENT
+
+" START_INDENT
+function Func()
+    if v:true
+	+
+	echo
+	-
+    endif
+endfunction
+" END_INDENT
+
+" START_INDENT
+var matchpairs: string = &matchpairs
+var pairs: dict<list<string>>
+for [opening: string, closing: string]
+	in matchpairs
+	->split(',')
+	->map((_, v: string): list<string> => split(v, ':'))
+    pairs[opening] = [escape(opening, '[]'), escape(closing, '[]'),  'nW', 'w$']
+    pairs[closing] = [escape(opening, '[]'), escape(closing, '[]'), 'bnW', 'w0']
+endfor
+" END_INDENT
+
+" START_INDENT
+{
+    echo []
+	+ []
+	+ [{a: 1,
+	    b: 2}]
+}
+" END_INDENT
+
+" START_INDENT
+silent! argdel *
+edit file
+" END_INDENT
diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim
index 2ae3658..2f59dfc 100644
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -1,214 +1,22 @@
-" Vim indent file
-" Language:	Vim script
-" Maintainer:	Bram Moolenaar <Bram@vim.org>
-" Last Change:	2022 Jun 24
+vim9script
 
-" Only load this indent file when no other was loaded.
-if exists("b:did_indent")
-  finish
+# Vim indent file
+# Language:	Vim script
+# Maintainer:	Bram Moolenaar <Bram@vim.org>
+# Last Change:	2022 Sep 27
+
+# Only load this indent file when no other was loaded.
+if exists('b:did_indent')
+    finish
 endif
-let b:did_indent = 1
 
-setlocal indentexpr=GetVimIndent()
-setlocal indentkeys+==endif,=enddef,=endfu,=endfor,=endwh,=endtry,=},=else,=cat,=finall,=END,0\\,0=\"\\\ 
+b:did_indent = true
+b:undo_indent = 'setlocal indentkeys< indentexpr<'
+
+import autoload '../autoload/dist/vimindent.vim'
+
+setlocal indentexpr=vimindent.Expr(v:lnum)
+setlocal indentkeys+==endif,=enddef,=endfu,=endfor,=endwh,=endtry,=},=else,=cat,=finall,=END,0\\
+execute('setlocal indentkeys+=0=\"\\\ ,0=#\\\ ')
 setlocal indentkeys-=0#
 setlocal indentkeys-=:
-
-let b:undo_indent = "setl indentkeys< indentexpr<"
-
-" Only define the function once.
-if exists("*GetVimIndent")
-  finish
-endif
-let s:keepcpo= &cpo
-set cpo&vim
-
-function GetVimIndent()
-  let ignorecase_save = &ignorecase
-  try
-    let &ignorecase = 0
-    return GetVimIndentIntern()
-  finally
-    let &ignorecase = ignorecase_save
-  endtry
-endfunc
-
-" Legacy script line continuation and Vim9 script operators that must mean an
-" expression that continues from the previous line.
-let s:lineContPat = '^\s*\(\\\|"\\ \|->\)'
-
-function GetVimIndentIntern()
-  " If the current line has line continuation and the previous one too, use
-  " the same indent.  This does not skip empty lines.
-  let cur_text = getline(v:lnum)
-  let cur_has_linecont = cur_text =~ s:lineContPat
-  if cur_has_linecont && v:lnum > 1 && getline(v:lnum - 1) =~ s:lineContPat
-    return indent(v:lnum - 1)
-  endif
-
-  " Find a non-blank line above the current line.
-  let lnum = prevnonblank(v:lnum - 1)
-
-  " The previous line, ignoring line continuation
-  let prev_text_end = lnum > 0 ? getline(lnum) : ''
-
-  " If the current line doesn't start with '\' or '"\ ' and below a line that
-  " starts with '\' or '"\ ', use the indent of the line above it.
-  if !cur_has_linecont
-    while lnum > 0 && getline(lnum) =~ s:lineContPat
-      let lnum = lnum - 1
-    endwhile
-  endif
-
-  " At the start of the file use zero indent.
-  if lnum == 0
-    return 0
-  endif
-
-  " the start of the previous line, skipping over line continuation
-  let prev_text = getline(lnum)
-  let found_cont = 0
-
-  " Add a 'shiftwidth' after :if, :while, :try, :catch, :finally, :function
-  " and :else.  Add it three times for a line that starts with '\' or '"\ '
-  " after a line that doesn't (or g:vim_indent_cont if it exists).
-  let ind = indent(lnum)
-
-  " In heredoc indenting works completely differently.
-  if has('syntax_items') 
-    let syn_here = synIDattr(synID(v:lnum, 1, 1), "name")
-    if syn_here =~ 'vimLetHereDocStop'
-      " End of heredoc: use indent of matching start line
-      let lnum = v:lnum - 1
-      while lnum > 0
-	let attr = synIDattr(synID(lnum, 1, 1), "name")
-	if attr != '' && attr !~ 'vimLetHereDoc'
-	  return indent(lnum)
-	endif
-	let lnum -= 1
-      endwhile
-      return 0
-    endif
-    if syn_here =~ 'vimLetHereDoc'
-      if synIDattr(synID(lnum, 1, 1), "name") !~ 'vimLetHereDoc'
-	" First line in heredoc: increase indent
-	return ind + shiftwidth()
-      endif
-      " Heredoc continues: no change in indent
-      return ind
-    endif
-  endif
-
-  if cur_text =~ s:lineContPat && v:lnum > 1 && prev_text !~ s:lineContPat
-    let found_cont = 1
-    if exists("g:vim_indent_cont")
-      let ind = ind + g:vim_indent_cont
-    else
-      let ind = ind + shiftwidth() * 3
-    endif
-  elseif prev_text =~ '^\s*aug\%[roup]\s\+' && prev_text !~ '^\s*aug\%[roup]\s\+[eE][nN][dD]\>'
-    let ind = ind + shiftwidth()
-  else
-    " A line starting with :au does not increment/decrement indent.
-    " A { may start a block or a dict.  Assume that when a } follows it's a
-    " terminated dict.
-    " ":function" starts a block but "function(" doesn't.
-    if prev_text !~ '^\s*au\%[tocmd]' && prev_text !~ '^\s*{.*}'
-      let i = match(prev_text, '\(^\||\)\s*\(export\s\+\)\?\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\|finall\%[y]\|def\|el\%[seif]\)\>\|fu\%[nction][! ]\)')
-      if i >= 0
-	let ind += shiftwidth()
-	if strpart(prev_text, i, 1) == '|' && has('syntax_items')
-	      \ && synIDattr(synID(lnum, i, 1), "name") =~ '\(Comment\|String\|PatSep\)$'
-	  let ind -= shiftwidth()
-	endif
-      endif
-    endif
-  endif
-
-  " If the previous line contains an "end" after a pipe, but not in an ":au"
-  " command.  And not when there is a backslash before the pipe.
-  " And when syntax HL is enabled avoid a match inside a string.
-  let i = match(prev_text, '[^\\]|\s*\(ene\@!\)')
-  if i > 0 && prev_text !~ '^\s*au\%[tocmd]'
-    if !has('syntax_items') || synIDattr(synID(lnum, i + 2, 1), "name") !~ '\(Comment\|String\)$'
-      let ind = ind - shiftwidth()
-    endif
-  endif
-
-  " For a line starting with "}" find the matching "{".  Align with that line,
-  " it is either the matching block start or dictionary start.
-  " Use the mapped "%" from matchit to find the match, otherwise we may match
-  " a { inside a comment or string.
-  if cur_text =~ '^\s*}'
-    if maparg('%') != ''
-      exe v:lnum
-      silent! normal %
-      if line('.') < v:lnum
-	let ind = indent('.')
-      endif
-    else
-      " todo: use searchpair() to find a match
-    endif
-  endif
-
-  " Look back for a line to align with
-  while lnum > 1
-    " Below a line starting with "}" find the matching "{".
-    if prev_text =~ '^\s*}'
-      if maparg('%') != ''
-	exe lnum
-	silent! normal %
-	if line('.') < lnum
-	  let lnum = line('.')
-	  let ind = indent(lnum)
-	  let prev_text = getline(lnum)
-	else
-	  break
-	endif
-      else
-	" todo: use searchpair() to find a match
-	break
-      endif
-    elseif prev_text =~ s:lineContPat
-      " looks like a continuation like, go back one line
-      let lnum = lnum - 1
-      let ind = indent(lnum)
-      let prev_text = getline(lnum)
-    else
-      break
-    endif
-  endwhile
-
-  " Below a line starting with "]" we must be below the end of a list.
-  " Include a "}" and "},} in case a dictionary ends too.
-  if prev_text_end =~ '^\s*\(},\=\s*\)\=]'
-    let ind = ind - shiftwidth()
-  endif
-
-  let ends_in_comment = has('syntax_items')
-	\ && synIDattr(synID(lnum, len(getline(lnum)), 1), "name") =~ '\(Comment\|String\)$'
-
-  " A line ending in "{" or "[" is most likely the start of a dict/list literal,
-  " indent the next line more.  Not for a continuation line or {{{.
-  if !ends_in_comment && prev_text_end =~ '\s[{[]\s*$' && !found_cont
-    let ind = ind + shiftwidth()
-  endif
-
-  " Subtract a 'shiftwidth' on a :endif, :endwhile, :endfor, :catch, :finally,
-  " :endtry, :endfun, :enddef, :else and :augroup END.
-  " Although ":en" would be enough only match short command names as in
-  " 'indentkeys'.
-  if cur_text =~ '^\s*\(endif\|endwh\|endfor\|endtry\|endfu\|enddef\|cat\|finall\|else\|aug\%[roup]\s\+[eE][nN][dD]\)'
-    let ind = ind - shiftwidth()
-    if ind < 0
-      let ind = 0
-    endif
-  endif
-
-  return ind
-endfunction
-
-let &cpo = s:keepcpo
-unlet s:keepcpo
-
-" vim:sw=2
diff --git a/runtime/indent/vue.vim b/runtime/indent/vue.vim
new file mode 100644
index 0000000..7ff623b
--- /dev/null
+++ b/runtime/indent/vue.vim
@@ -0,0 +1,12 @@
+" Vim indent file placeholder
+" Language:	Vue
+" Maintainer:	None, please volunteer if you have a real Vue indent script
+
+" Only load this indent file when no other was loaded.
+if exists("b:did_indent")
+   finish
+endif
+let b:did_indent = 1
+
+" Html comes closest
+runtime! indent/html.vim