summaryrefslogtreecommitdiffstats
path: root/runtime/autoload/dist
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/autoload/dist')
-rw-r--r--runtime/autoload/dist/ft.vim1108
-rw-r--r--runtime/autoload/dist/man.vim196
-rw-r--r--runtime/autoload/dist/script.vim434
-rw-r--r--runtime/autoload/dist/vimindent.vim1257
4 files changed, 2995 insertions, 0 deletions
diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim
new file mode 100644
index 0000000..bfc325d
--- /dev/null
+++ b/runtime/autoload/dist/ft.vim
@@ -0,0 +1,1108 @@
+vim9script
+
+# Vim functions for file type detection
+#
+# Maintainer: Bram Moolenaar <Bram@vim.org>
+# Last Change: 2022 Dec 14
+
+# These functions are moved here from runtime/filetype.vim to make startup
+# faster.
+
+export def Check_inp()
+ if getline(1) =~ '^\*'
+ setf abaqus
+ else
+ var n = 1
+ var nmax = line("$") > 500 ? 500 : line("$")
+ while n <= nmax
+ if getline(n) =~? "^header surface data"
+ setf trasys
+ break
+ endif
+ n += 1
+ endwhile
+ endif
+enddef
+
+# This function checks for the kind of assembly that is wanted by the user, or
+# can be detected from the first five lines of the file.
+export def FTasm()
+ # make sure b:asmsyntax exists
+ if !exists("b:asmsyntax")
+ b:asmsyntax = ""
+ endif
+
+ if b:asmsyntax == ""
+ FTasmsyntax()
+ endif
+
+ # if b:asmsyntax still isn't set, default to asmsyntax or GNU
+ if b:asmsyntax == ""
+ if exists("g:asmsyntax")
+ b:asmsyntax = g:asmsyntax
+ else
+ b:asmsyntax = "asm"
+ endif
+ endif
+
+ exe "setf " .. fnameescape(b:asmsyntax)
+enddef
+
+export def FTasmsyntax()
+ # see if the file contains any asmsyntax=foo overrides. If so, change
+ # b:asmsyntax appropriately
+ var head = " " .. getline(1) .. " " .. getline(2) .. " "
+ .. getline(3) .. " " .. getline(4) .. " " .. getline(5) .. " "
+ var match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s')
+ if match != ''
+ b:asmsyntax = match
+ elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library'))
+ b:asmsyntax = "vmasm"
+ endif
+enddef
+
+var ft_visual_basic_content = '\cVB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)'
+
+# See FTfrm() for Visual Basic form file detection
+export def FTbas()
+ if exists("g:filetype_bas")
+ exe "setf " .. g:filetype_bas
+ return
+ endif
+
+ # most frequent FreeBASIC-specific keywords in distro files
+ var fb_keywords = '\c^\s*\%(extern\|var\|enum\|private\|scope\|union\|byref\|operator\|constructor\|delete\|namespace\|public\|property\|with\|destructor\|using\)\>\%(\s*[:=(]\)\@!'
+ var fb_preproc = '\c^\s*\%(' ..
+ # preprocessor
+ '#\s*\a\+\|' ..
+ # compiler option
+ 'option\s\+\%(byval\|dynamic\|escape\|\%(no\)\=gosub\|nokeyword\|private\|static\)\>\|' ..
+ # metacommand
+ '\%(''\|rem\)\s*\$lang\>\|' ..
+ # default datatype
+ 'def\%(byte\|longint\|short\|ubyte\|uint\|ulongint\|ushort\)\>' ..
+ '\)'
+ var fb_comment = "^\\s*/'"
+
+ # OPTION EXPLICIT, without the leading underscore, is common to many dialects
+ var qb64_preproc = '\c^\s*\%($\a\+\|option\s\+\%(_explicit\|_\=explicitarray\)\>\)'
+
+ for lnum in range(1, min([line("$"), 100]))
+ var line = getline(lnum)
+ if line =~ ft_visual_basic_content
+ setf vb
+ return
+ elseif line =~ fb_preproc || line =~ fb_comment || line =~ fb_keywords
+ setf freebasic
+ return
+ elseif line =~ qb64_preproc
+ setf qb64
+ return
+ endif
+ endfor
+ setf basic
+enddef
+
+export def FTbtm()
+ if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm
+ setf dosbatch
+ else
+ setf btm
+ endif
+enddef
+
+export def BindzoneCheck(default = '')
+ if getline(1) .. getline(2) .. getline(3) .. getline(4)
+ =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA'
+ setf bindzone
+ elseif default != ''
+ exe 'setf ' .. default
+ endif
+enddef
+
+# Returns true if file content looks like RAPID
+def IsRapid(sChkExt: string = ""): bool
+ if sChkExt == "cfg"
+ return getline(1) =~? '\v^%(EIO|MMC|MOC|PROC|SIO|SYS):CFG'
+ endif
+ # called from FTmod, FTprg or FTsys
+ return getline(nextnonblank(1)) =~? '\v^\s*%(\%{3}|module\s+\k+\s*%(\(|$))'
+enddef
+
+export def FTcfg()
+ if exists("g:filetype_cfg")
+ exe "setf " .. g:filetype_cfg
+ elseif IsRapid("cfg")
+ setf rapid
+ else
+ setf cfg
+ endif
+enddef
+
+export def FTcls()
+ if exists("g:filetype_cls")
+ exe "setf " .. g:filetype_cls
+ return
+ endif
+
+ if getline(1) =~ '^\v%(\%|\\)'
+ setf tex
+ elseif getline(1)[0] == '#' && getline(1) =~ 'rexx'
+ setf rexx
+ elseif getline(1) == 'VERSION 1.0 CLASS'
+ setf vb
+ else
+ setf st
+ endif
+enddef
+
+export def FTlpc()
+ if exists("g:lpc_syntax_for_c")
+ var lnum = 1
+ while lnum <= 12
+ if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)'
+ setf lpc
+ return
+ endif
+ lnum += 1
+ endwhile
+ endif
+ setf c
+enddef
+
+export def FTheader()
+ if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1
+ if exists("g:c_syntax_for_h")
+ setf objc
+ else
+ setf objcpp
+ endif
+ elseif exists("g:c_syntax_for_h")
+ setf c
+ elseif exists("g:ch_syntax_for_h")
+ setf ch
+ else
+ setf cpp
+ endif
+enddef
+
+# This function checks if one of the first ten lines start with a '@'. In
+# that case it is probably a change file.
+# If the first line starts with # or ! it's probably a ch file.
+# If a line has "main", "include", "//" or "/*" it's probably ch.
+# Otherwise CHILL is assumed.
+export def FTchange()
+ var lnum = 1
+ while lnum <= 10
+ if getline(lnum)[0] == '@'
+ setf change
+ return
+ endif
+ if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!')
+ setf ch
+ return
+ endif
+ if getline(lnum) =~ "MODULE"
+ setf chill
+ return
+ endif
+ if getline(lnum) =~ 'main\s*(\|#\s*include\|//'
+ setf ch
+ return
+ endif
+ lnum += 1
+ endwhile
+ setf chill
+enddef
+
+export def FTent()
+ # This function checks for valid cl syntax in the first five lines.
+ # Look for either an opening comment, '#', or a block start, '{'.
+ # If not found, assume SGML.
+ var lnum = 1
+ while lnum < 6
+ var line = getline(lnum)
+ if line =~ '^\s*[#{]'
+ setf cl
+ return
+ elseif line !~ '^\s*$'
+ # Not a blank line, not a comment, and not a block start,
+ # so doesn't look like valid cl code.
+ break
+ endif
+ lnum += 1
+ endwhile
+ setf dtd
+enddef
+
+export def ExCheck()
+ var lines = getline(1, min([line("$"), 100]))
+ if exists('g:filetype_euphoria')
+ exe 'setf ' .. g:filetype_euphoria
+ elseif match(lines, '^--\|^ifdef\>\|^include\>') > -1
+ setf euphoria3
+ else
+ setf elixir
+ endif
+enddef
+
+export def EuphoriaCheck()
+ if exists('g:filetype_euphoria')
+ exe 'setf ' .. g:filetype_euphoria
+ else
+ setf euphoria3
+ endif
+enddef
+
+export def DtraceCheck()
+ if did_filetype()
+ # Filetype was already detected
+ return
+ endif
+ var lines = getline(1, min([line("$"), 100]))
+ if match(lines, '^module\>\|^import\>') > -1
+ # D files often start with a module and/or import statement.
+ setf d
+ elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1
+ setf dtrace
+ else
+ setf d
+ endif
+enddef
+
+export def FTe()
+ if exists('g:filetype_euphoria')
+ exe 'setf ' .. g:filetype_euphoria
+ else
+ var n = 1
+ while n < 100 && n <= line("$")
+ if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$"
+ setf specman
+ return
+ endif
+ n += 1
+ endwhile
+ setf eiffel
+ endif
+enddef
+
+export def FTfrm()
+ if exists("g:filetype_frm")
+ exe "setf " .. g:filetype_frm
+ return
+ endif
+
+ var lines = getline(1, min([line("$"), 5]))
+
+ if match(lines, ft_visual_basic_content) > -1
+ setf vb
+ else
+ setf form
+ endif
+enddef
+
+# Distinguish between Forth and F#.
+# Provided by Doug Kearns.
+export def FTfs()
+ if exists("g:filetype_fs")
+ exe "setf " .. g:filetype_fs
+ else
+ var line = getline(nextnonblank(1))
+ # comments and colon definitions
+ if line =~ '^\s*\.\=( ' || line =~ '^\s*\\G\= ' || line =~ '^\\$'
+ \ || line =~ '^\s*: \S'
+ setf forth
+ else
+ setf fsharp
+ endif
+ endif
+enddef
+
+# Distinguish between HTML, XHTML and Django
+export def FThtml()
+ var n = 1
+ while n < 10 && n <= line("$")
+ if getline(n) =~ '\<DTD\s\+XHTML\s'
+ setf xhtml
+ return
+ endif
+ if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+'
+ setf htmldjango
+ return
+ endif
+ n += 1
+ endwhile
+ setf FALLBACK html
+enddef
+
+# Distinguish between standard IDL and MS-IDL
+export def FTidl()
+ var n = 1
+ while n < 50 && n <= line("$")
+ if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"'
+ setf msidl
+ return
+ endif
+ n += 1
+ endwhile
+ setf idl
+enddef
+
+# Distinguish between "default", Prolog and Cproto prototype file.
+export def ProtoCheck(default: string)
+ # Cproto files have a comment in the first line and a function prototype in
+ # the second line, it always ends in ";". Indent files may also have
+ # comments, thus we can't match comments to see the difference.
+ # IDL files can have a single ';' in the second line, require at least one
+ # chacter before the ';'.
+ if getline(2) =~ '.;$'
+ setf cpp
+ else
+ # recognize Prolog by specific text in the first non-empty line
+ # require a blank after the '%' because Perl uses "%list" and "%translate"
+ var l = getline(nextnonblank(1))
+ if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-'
+ setf prolog
+ else
+ exe 'setf ' .. default
+ endif
+ endif
+enddef
+
+export def FTm()
+ if exists("g:filetype_m")
+ exe "setf " .. g:filetype_m
+ return
+ endif
+
+ # excluding end(for|function|if|switch|while) common to Murphi
+ var octave_block_terminators = '\<end\%(_try_catch\|classdef\|enumeration\|events\|methods\|parfor\|properties\)\>'
+
+ var objc_preprocessor = '^\s*#\s*\%(import\|include\|define\|if\|ifn\=def\|undef\|line\|error\|pragma\)\>'
+
+ var n = 1
+ var saw_comment = 0 # Whether we've seen a multiline comment leader.
+ while n < 100
+ var line = getline(n)
+ if line =~ '^\s*/\*'
+ # /* ... */ is a comment in Objective C and Murphi, so we can't conclude
+ # it's either of them yet, but track this as a hint in case we don't see
+ # anything more definitive.
+ saw_comment = 1
+ endif
+ if line =~ '^\s*//' || line =~ '^\s*@import\>' || line =~ objc_preprocessor
+ setf objc
+ return
+ endif
+ if line =~ '^\s*\%(#\|%!\)' || line =~ '^\s*unwind_protect\>' ||
+ \ line =~ '\%(^\|;\)\s*' .. octave_block_terminators
+ setf octave
+ return
+ endif
+ # TODO: could be Matlab or Octave
+ if line =~ '^\s*%'
+ setf matlab
+ return
+ endif
+ if line =~ '^\s*(\*'
+ setf mma
+ return
+ endif
+ if line =~ '^\c\s*\(\(type\|var\)\>\|--\)'
+ setf murphi
+ return
+ endif
+ n += 1
+ endwhile
+
+ if saw_comment
+ # We didn't see anything definitive, but this looks like either Objective C
+ # or Murphi based on the comment leader. Assume the former as it is more
+ # common.
+ setf objc
+ else
+ # Default is Matlab
+ setf matlab
+ endif
+enddef
+
+export def FTmms()
+ var n = 1
+ while n < 20
+ var line = getline(n)
+ if line =~ '^\s*\(%\|//\)' || line =~ '^\*'
+ setf mmix
+ return
+ endif
+ if line =~ '^\s*#'
+ setf make
+ return
+ endif
+ n += 1
+ endwhile
+ setf mmix
+enddef
+
+# This function checks if one of the first five lines start with a dot. In
+# that case it is probably an nroff file: 'filetype' is set and 1 is returned.
+export def FTnroff(): number
+ if getline(1)[0] .. getline(2)[0] .. getline(3)[0]
+ .. getline(4)[0] .. getline(5)[0] =~ '\.'
+ setf nroff
+ return 1
+ endif
+ return 0
+enddef
+
+export def FTmm()
+ var n = 1
+ while n < 20
+ if getline(n) =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)'
+ setf objcpp
+ return
+ endif
+ n += 1
+ endwhile
+ setf nroff
+enddef
+
+# Returns true if file content looks like LambdaProlog module
+def IsLProlog(): bool
+ # skip apparent comments and blank lines, what looks like
+ # LambdaProlog comment may be RAPID header
+ var l: number = nextnonblank(1)
+ while l > 0 && l < line('$') && getline(l) =~ '^\s*%' # LambdaProlog comment
+ l = nextnonblank(l + 1)
+ endwhile
+ # this pattern must not catch a go.mod file
+ return getline(l) =~ '\<module\s\+\w\+\s*\.\s*\(%\|$\)'
+enddef
+
+# Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+export def FTmod()
+ if exists("g:filetype_mod")
+ exe "setf " .. g:filetype_mod
+ elseif IsLProlog()
+ setf lprolog
+ elseif getline(nextnonblank(1)) =~ '\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)'
+ setf modula2
+ elseif IsRapid()
+ setf rapid
+ elseif expand("<afile>") =~ '\<go.mod$'
+ setf gomod
+ else
+ # Nothing recognized, assume modsim3
+ setf modsim3
+ endif
+enddef
+
+export def FTpl()
+ if exists("g:filetype_pl")
+ exe "setf " .. g:filetype_pl
+ else
+ # recognize Prolog by specific text in the first non-empty line
+ # require a blank after the '%' because Perl uses "%list" and "%translate"
+ var l = getline(nextnonblank(1))
+ if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-'
+ setf prolog
+ else
+ setf perl
+ endif
+ endif
+enddef
+
+export def FTinc()
+ if exists("g:filetype_inc")
+ exe "setf " .. g:filetype_inc
+ else
+ var lines = getline(1) .. getline(2) .. getline(3)
+ if lines =~? "perlscript"
+ setf aspperl
+ elseif lines =~ "<%"
+ setf aspvbs
+ elseif lines =~ "<?"
+ setf php
+ # Pascal supports // comments but they're vary rarely used for file
+ # headers so assume POV-Ray
+ elseif lines =~ '^\s*\%({\|(\*\)' || lines =~? ft_pascal_keywords
+ setf pascal
+ elseif lines =~# '\<\%(require\|inherit\)\>' || lines =~# '[A-Z][A-Za-z0-9_:${}]*\s\+\%(??\|[?:+]\)\?= '
+ setf bitbake
+ else
+ FTasmsyntax()
+ if exists("b:asmsyntax")
+ exe "setf " .. fnameescape(b:asmsyntax)
+ else
+ setf pov
+ endif
+ endif
+ endif
+enddef
+
+export def FTprogress_cweb()
+ if exists("g:filetype_w")
+ exe "setf " .. g:filetype_w
+ return
+ endif
+ if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE'
+ setf progress
+ else
+ setf cweb
+ endif
+enddef
+
+export def FTprogress_asm()
+ if exists("g:filetype_i")
+ exe "setf " .. g:filetype_i
+ return
+ endif
+ # This function checks for an assembly comment the first ten lines.
+ # If not found, assume Progress.
+ var lnum = 1
+ while lnum <= 10 && lnum < line('$')
+ var line = getline(lnum)
+ if line =~ '^\s*;' || line =~ '^\*'
+ FTasm()
+ return
+ elseif line !~ '^\s*$' || line =~ '^/\*'
+ # Not an empty line: Doesn't look like valid assembly code.
+ # Or it looks like a Progress /* comment
+ break
+ endif
+ lnum += 1
+ endwhile
+ setf progress
+enddef
+
+var ft_pascal_comments = '^\s*\%({\|(\*\|//\)'
+var ft_pascal_keywords = '^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>'
+
+export def FTprogress_pascal()
+ if exists("g:filetype_p")
+ exe "setf " .. g:filetype_p
+ return
+ endif
+ # This function checks for valid Pascal syntax in the first ten lines.
+ # Look for either an opening comment or a program start.
+ # If not found, assume Progress.
+ var lnum = 1
+ while lnum <= 10 && lnum < line('$')
+ var line = getline(lnum)
+ if line =~ ft_pascal_comments || line =~? ft_pascal_keywords
+ setf pascal
+ return
+ elseif line !~ '^\s*$' || line =~ '^/\*'
+ # Not an empty line: Doesn't look like valid Pascal code.
+ # Or it looks like a Progress /* comment
+ break
+ endif
+ lnum += 1
+ endwhile
+ setf progress
+enddef
+
+export def FTpp()
+ if exists("g:filetype_pp")
+ exe "setf " .. g:filetype_pp
+ else
+ var line = getline(nextnonblank(1))
+ if line =~ ft_pascal_comments || line =~? ft_pascal_keywords
+ setf pascal
+ else
+ setf puppet
+ endif
+ endif
+enddef
+
+# Determine if *.prg is ABB RAPID. Can also be Clipper, FoxPro or eviews
+export def FTprg()
+ if exists("g:filetype_prg")
+ exe "setf " .. g:filetype_prg
+ elseif IsRapid()
+ setf rapid
+ else
+ # Nothing recognized, assume Clipper
+ setf clipper
+ endif
+enddef
+
+export def FTr()
+ var max = line("$") > 50 ? 50 : line("$")
+
+ for n in range(1, max)
+ # Rebol is easy to recognize, check for that first
+ if getline(n) =~? '\<REBOL\>'
+ setf rebol
+ return
+ endif
+ endfor
+
+ for n in range(1, max)
+ # R has # comments
+ if getline(n) =~ '^\s*#'
+ setf r
+ return
+ endif
+ # Rexx has /* comments */
+ if getline(n) =~ '^\s*/\*'
+ setf rexx
+ return
+ endif
+ endfor
+
+ # Nothing recognized, use user default or assume Rexx
+ if exists("g:filetype_r")
+ exe "setf " .. g:filetype_r
+ else
+ # Rexx used to be the default, but R appears to be much more popular.
+ setf r
+ endif
+enddef
+
+export def McSetf()
+ # Rely on the file to start with a comment.
+ # MS message text files use ';', Sendmail files use '#' or 'dnl'
+ for lnum in range(1, min([line("$"), 20]))
+ var line = getline(lnum)
+ if line =~ '^\s*\(#\|dnl\)'
+ setf m4 # Sendmail .mc file
+ return
+ elseif line =~ '^\s*;'
+ setf msmessages # MS Message text file
+ return
+ endif
+ endfor
+ setf m4 # Default: Sendmail .mc file
+enddef
+
+# Called from filetype.vim and scripts.vim.
+export def SetFileTypeSH(name: string)
+ if did_filetype()
+ # Filetype was already detected
+ return
+ endif
+ if expand("<amatch>") =~ g:ft_ignore_pat
+ return
+ endif
+ if name =~ '\<csh\>'
+ # Some .sh scripts contain #!/bin/csh.
+ SetFileTypeShell("csh")
+ return
+ elseif name =~ '\<tcsh\>'
+ # Some .sh scripts contain #!/bin/tcsh.
+ SetFileTypeShell("tcsh")
+ return
+ elseif name =~ '\<zsh\>'
+ # Some .sh scripts contain #!/bin/zsh.
+ SetFileTypeShell("zsh")
+ return
+ elseif name =~ '\<ksh\>'
+ b:is_kornshell = 1
+ if exists("b:is_bash")
+ unlet b:is_bash
+ endif
+ if exists("b:is_sh")
+ unlet b:is_sh
+ endif
+ elseif exists("g:bash_is_sh") || name =~ '\<bash\>' || name =~ '\<bash2\>'
+ b:is_bash = 1
+ if exists("b:is_kornshell")
+ unlet b:is_kornshell
+ endif
+ if exists("b:is_sh")
+ unlet b:is_sh
+ endif
+ elseif name =~ '\<sh\>' || name =~ '\<dash\>'
+ # Ubuntu links "sh" to "dash", thus it is expected to work the same way
+ b:is_sh = 1
+ if exists("b:is_kornshell")
+ unlet b:is_kornshell
+ endif
+ if exists("b:is_bash")
+ unlet b:is_bash
+ endif
+ endif
+ SetFileTypeShell("sh")
+enddef
+
+# For shell-like file types, check for an "exec" command hidden in a comment,
+# as used for Tcl.
+# Also called from scripts.vim, thus can't be local to this script.
+export def SetFileTypeShell(name: string)
+ if did_filetype()
+ # Filetype was already detected
+ return
+ endif
+ if expand("<amatch>") =~ g:ft_ignore_pat
+ return
+ endif
+ var l = 2
+ while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)'
+ # Skip empty and comment lines.
+ l += 1
+ endwhile
+ if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$'
+ # Found an "exec" line after a comment with continuation
+ var n = substitute(getline(l), '\s*exec\s\+\([^ ]*/\)\=', '', '')
+ if n =~ '\<tclsh\|\<wish'
+ setf tcl
+ return
+ endif
+ endif
+ exe "setf " .. name
+enddef
+
+export def CSH()
+ if did_filetype()
+ # Filetype was already detected
+ return
+ endif
+ if exists("g:filetype_csh")
+ SetFileTypeShell(g:filetype_csh)
+ elseif &shell =~ "tcsh"
+ SetFileTypeShell("tcsh")
+ else
+ SetFileTypeShell("csh")
+ endif
+enddef
+
+var ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*'
+export def FTRules()
+ var path = expand('<amatch>:p')
+ if path =~ '/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|\%(usr/\)\=lib/udev/\%(rules\.d/\)\=.*\.rules\)$'
+ setf udevrules
+ return
+ endif
+ if path =~ '^/etc/ufw/'
+ setf conf # Better than hog
+ return
+ endif
+ if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d'
+ setf javascript
+ return
+ endif
+ var config_lines: list<string>
+ try
+ config_lines = readfile('/etc/udev/udev.conf')
+ catch /^Vim\%((\a\+)\)\=:E484/
+ setf hog
+ return
+ endtry
+ var dir = expand('<amatch>:p:h')
+ for line in config_lines
+ if line =~ ft_rules_udev_rules_pattern
+ var udev_rules = substitute(line, ft_rules_udev_rules_pattern, '\1', "")
+ if dir == udev_rules
+ setf udevrules
+ endif
+ break
+ endif
+ endfor
+ setf hog
+enddef
+
+export def SQL()
+ if exists("g:filetype_sql")
+ exe "setf " .. g:filetype_sql
+ else
+ setf sql
+ endif
+enddef
+
+# This function checks the first 25 lines of file extension "sc" to resolve
+# detection between scala and SuperCollider.
+# NOTE: We don't check for 'Class : Method', as this can easily be confused
+# with valid Scala like `val x : Int = 3`. So we instead only rely on
+# checks that can't be confused.
+export def FTsc()
+ for lnum in range(1, min([line("$"), 25]))
+ if getline(lnum) =~# 'var\s<\|classvar\s<\|\^this.*\||\w\+|\|+\s\w*\s{\|\*ar\s'
+ setf supercollider
+ return
+ endif
+ endfor
+ setf scala
+enddef
+
+# This function checks the first line of file extension "scd" to resolve
+# detection between scdoc and SuperCollider
+export def FTscd()
+ if getline(1) =~# '\%^\S\+(\d[0-9A-Za-z]*)\%(\s\+\"[^"]*\"\%(\s\+\"[^"]*\"\)\=\)\=$'
+ setf scdoc
+ else
+ setf supercollider
+ endif
+enddef
+
+# If the file has an extension of 't' and is in a directory 't' or 'xt' then
+# it is almost certainly a Perl test file.
+# If the first line starts with '#' and contains 'perl' it's probably a Perl
+# file.
+# (Slow test) If a file contains a 'use' statement then it is almost certainly
+# a Perl file.
+export def FTperl(): number
+ var dirname = expand("%:p:h:t")
+ if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt')
+ setf perl
+ return 1
+ endif
+ if getline(1)[0] == '#' && getline(1) =~ 'perl'
+ setf perl
+ return 1
+ endif
+ var save_cursor = getpos('.')
+ call cursor(1, 1)
+ var has_use = search('^use\s\s*\k', 'c', 30) > 0
+ call setpos('.', save_cursor)
+ if has_use
+ setf perl
+ return 1
+ endif
+ return 0
+enddef
+
+# LambdaProlog and Standard ML signature files
+export def FTsig()
+ if exists("g:filetype_sig")
+ exe "setf " .. g:filetype_sig
+ return
+ endif
+
+ var lprolog_comment = '^\s*\%(/\*\|%\)'
+ var lprolog_keyword = '^\s*sig\s\+\a'
+ var sml_comment = '^\s*(\*'
+ var sml_keyword = '^\s*\%(signature\|structure\)\s\+\a'
+
+ var line = getline(nextnonblank(1))
+
+ if line =~ lprolog_comment || line =~# lprolog_keyword
+ setf lprolog
+ elseif line =~ sml_comment || line =~# sml_keyword
+ setf sml
+ endif
+enddef
+
+# This function checks the first 100 lines of files matching "*.sil" to
+# resolve detection between Swift Intermediate Language and SILE.
+export def FTsil()
+ for lnum in range(1, [line('$'), 100]->min())
+ var line: string = getline(lnum)
+ if line =~ '^\s*[\\%]'
+ setf sile
+ return
+ elseif line =~ '^\s*\S'
+ setf sil
+ return
+ endif
+ endfor
+ # no clue, default to "sil"
+ setf sil
+enddef
+
+export def FTsys()
+ if exists("g:filetype_sys")
+ exe "setf " .. g:filetype_sys
+ elseif IsRapid()
+ setf rapid
+ else
+ setf bat
+ endif
+enddef
+
+# Choose context, plaintex, or tex (LaTeX) based on these rules:
+# 1. Check the first line of the file for "%&<format>".
+# 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords.
+# 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc.
+export def FTtex()
+ var firstline = getline(1)
+ var format: string
+ if firstline =~ '^%&\s*\a\+'
+ format = tolower(matchstr(firstline, '\a\+'))
+ format = substitute(format, 'pdf', '', '')
+ if format == 'tex'
+ format = 'latex'
+ elseif format == 'plaintex'
+ format = 'plain'
+ endif
+ elseif expand('%') =~ 'tex/context/.*/.*.tex'
+ format = 'context'
+ else
+ # Default value, may be changed later:
+ format = exists("g:tex_flavor") ? g:tex_flavor : 'plain'
+ # Save position, go to the top of the file, find first non-comment line.
+ var save_cursor = getpos('.')
+ call cursor(1, 1)
+ var firstNC = search('^\s*[^[:space:]%]', 'c', 1000)
+ if firstNC > 0
+ # Check the next thousand lines for a LaTeX or ConTeXt keyword.
+ var lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>'
+ var cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>'
+ var kwline = search('^\s*\\\%(' .. lpat .. '\)\|^\s*\\\(' .. cpat .. '\)',
+ 'cnp', firstNC + 1000)
+ if kwline == 1 # lpat matched
+ format = 'latex'
+ elseif kwline == 2 # cpat matched
+ format = 'context'
+ endif # If neither matched, keep default set above.
+ # let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000)
+ # let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000)
+ # if cline > 0
+ # let format = 'context'
+ # endif
+ # if lline > 0 && (cline == 0 || cline > lline)
+ # let format = 'tex'
+ # endif
+ endif # firstNC
+ call setpos('.', save_cursor)
+ endif # firstline =~ '^%&\s*\a\+'
+
+ # Translation from formats to file types. TODO: add AMSTeX, RevTex, others?
+ if format == 'plain'
+ setf plaintex
+ elseif format == 'context'
+ setf context
+ else # probably LaTeX
+ setf tex
+ endif
+ return
+enddef
+
+export def FTxml()
+ var n = 1
+ while n < 100 && n <= line("$")
+ var line = getline(n)
+ # DocBook 4 or DocBook 5.
+ var is_docbook4 = line =~ '<!DOCTYPE.*DocBook'
+ var is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"'
+ if is_docbook4 || is_docbook5
+ b:docbk_type = "xml"
+ if is_docbook5
+ b:docbk_ver = 5
+ else
+ b:docbk_ver = 4
+ endif
+ setf docbk
+ return
+ endif
+ if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"'
+ setf xbl
+ return
+ endif
+ n += 1
+ endwhile
+ setf xml
+enddef
+
+export def FTy()
+ var n = 1
+ while n < 100 && n <= line("$")
+ var line = getline(n)
+ if line =~ '^\s*%'
+ setf yacc
+ return
+ endif
+ if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include'
+ setf racc
+ return
+ endif
+ n += 1
+ endwhile
+ setf yacc
+enddef
+
+export def Redif()
+ var lnum = 1
+ while lnum <= 5 && lnum < line('$')
+ if getline(lnum) =~ "^\ctemplate-type:"
+ setf redif
+ return
+ endif
+ lnum += 1
+ endwhile
+enddef
+
+# This function is called for all files under */debian/patches/*, make sure not
+# to non-dep3patch files, such as README and other text files.
+export def Dep3patch()
+ if expand('%:t') ==# 'series'
+ return
+ endif
+
+ for ln in getline(1, 100)
+ if ln =~# '^\%(Description\|Subject\|Origin\|Bug\|Forwarded\|Author\|From\|Reviewed-by\|Acked-by\|Last-Updated\|Applied-Upstream\):'
+ setf dep3patch
+ return
+ elseif ln =~# '^---'
+ # end of headers found. stop processing
+ return
+ endif
+ endfor
+enddef
+
+# This function checks the first 15 lines for appearance of 'FoamFile'
+# and then 'object' in a following line.
+# In that case, it's probably an OpenFOAM file
+export def FTfoam()
+ var ffile = 0
+ var lnum = 1
+ while lnum <= 15
+ if getline(lnum) =~# '^FoamFile'
+ ffile = 1
+ elseif ffile == 1 && getline(lnum) =~# '^\s*object'
+ setf foam
+ return
+ endif
+ lnum += 1
+ endwhile
+enddef
+
+# Determine if a *.tf file is TF mud client or terraform
+export def FTtf()
+ var numberOfLines = line('$')
+ for i in range(1, numberOfLines)
+ var currentLine = trim(getline(i))
+ var firstCharacter = currentLine[0]
+ if firstCharacter !=? ";" && firstCharacter !=? "/" && firstCharacter !=? ""
+ setf terraform
+ return
+ endif
+ endfor
+ setf tf
+enddef
+
+var ft_krl_header = '\&\w+'
+# Determine if a *.src file is Kuka Robot Language
+export def FTsrc()
+ var ft_krl_def_or_deffct = '%(global\s+)?def%(fct)?>'
+ if exists("g:filetype_src")
+ exe "setf " .. g:filetype_src
+ elseif getline(nextnonblank(1)) =~? '\v^\s*%(' .. ft_krl_header .. '|' .. ft_krl_def_or_deffct .. ')'
+ setf krl
+ endif
+enddef
+
+# Determine if a *.dat file is Kuka Robot Language
+export def FTdat()
+ var ft_krl_defdat = 'defdat>'
+ if exists("g:filetype_dat")
+ exe "setf " .. g:filetype_dat
+ elseif getline(nextnonblank(1)) =~? '\v^\s*%(' .. ft_krl_header .. '|' .. ft_krl_defdat .. ')'
+ setf krl
+ endif
+enddef
+
+export def FTlsl()
+ if exists("g:filetype_lsl")
+ exe "setf " .. g:filetype_lsl
+ endif
+
+ var line = getline(nextnonblank(1))
+ if line =~ '^\s*%' || line =~# ':\s*trait\s*$'
+ setf larch
+ else
+ setf lsl
+ endif
+enddef
+
+# Uncomment this line to check for compilation errors early
+# defcompile
diff --git a/runtime/autoload/dist/man.vim b/runtime/autoload/dist/man.vim
new file mode 100644
index 0000000..cd584aa
--- /dev/null
+++ b/runtime/autoload/dist/man.vim
@@ -0,0 +1,196 @@
+" Vim filetype plugin autoload file
+" Language: man
+" Maintainer: Jason Franklin <vim@justemail.net>
+" Maintainer: SungHyun Nam <goweol@gmail.com>
+" Autoload Split: Bram Moolenaar
+" Last Change: 2022 Jun 18
+
+let s:cpo_save = &cpo
+set cpo-=C
+
+let s:man_tag_depth = 0
+
+let s:man_sect_arg = ""
+let s:man_find_arg = "-w"
+try
+ if !has("win32") && $OSTYPE !~ 'cygwin\|linux' && system('uname -s') =~ "SunOS" && system('uname -r') =~ "^5"
+ let s:man_sect_arg = "-s"
+ let s:man_find_arg = "-l"
+ endif
+catch /E145:/
+ " Ignore the error in restricted mode
+endtry
+
+func dist#man#PreGetPage(cnt)
+ if a:cnt == 0
+ let old_isk = &iskeyword
+ if &ft == 'man'
+ setl iskeyword+=(,)
+ endif
+ let str = expand("<cword>")
+ let &l:iskeyword = old_isk
+ let page = substitute(str, '(*\(\k\+\).*', '\1', '')
+ let sect = substitute(str, '\(\k\+\)(\([^()]*\)).*', '\2', '')
+ if match(sect, '^[0-9 ]\+$') == -1
+ let sect = ""
+ endif
+ if sect == page
+ let sect = ""
+ endif
+ else
+ let sect = a:cnt
+ let page = expand("<cword>")
+ endif
+ call dist#man#GetPage('', sect, page)
+endfunc
+
+func s:GetCmdArg(sect, page)
+
+ if empty(a:sect)
+ return shellescape(a:page)
+ endif
+
+ return s:man_sect_arg . ' ' . shellescape(a:sect) . ' ' . shellescape(a:page)
+endfunc
+
+func s:FindPage(sect, page)
+ let l:cmd = printf('man %s %s', s:man_find_arg, s:GetCmdArg(a:sect, a:page))
+ call system(l:cmd)
+
+ if v:shell_error
+ return 0
+ endif
+
+ return 1
+endfunc
+
+func dist#man#GetPage(cmdmods, ...)
+ if a:0 >= 2
+ let sect = a:1
+ let page = a:2
+ elseif a:0 >= 1
+ let sect = ""
+ let page = a:1
+ else
+ return
+ endif
+
+ " To support: nmap K :Man <cword>
+ if page == '<cword>'
+ let page = expand('<cword>')
+ endif
+
+ if !exists('g:ft_man_no_sect_fallback') || (g:ft_man_no_sect_fallback == 0)
+ if sect != "" && s:FindPage(sect, page) == 0
+ let sect = ""
+ endif
+ endif
+ if s:FindPage(sect, page) == 0
+ let msg = 'man.vim: no manual entry for "' . page . '"'
+ if !empty(sect)
+ let msg .= ' in section ' . sect
+ endif
+ echomsg msg
+ return
+ endif
+ exec "let s:man_tag_buf_".s:man_tag_depth." = ".bufnr("%")
+ exec "let s:man_tag_lin_".s:man_tag_depth." = ".line(".")
+ exec "let s:man_tag_col_".s:man_tag_depth." = ".col(".")
+ let s:man_tag_depth = s:man_tag_depth + 1
+
+ let open_cmd = 'edit'
+
+ " Use an existing "man" window if it exists, otherwise open a new one.
+ if &filetype != "man"
+ let thiswin = winnr()
+ exe "norm! \<C-W>b"
+ if winnr() > 1
+ exe "norm! " . thiswin . "\<C-W>w"
+ while 1
+ if &filetype == "man"
+ break
+ endif
+ exe "norm! \<C-W>w"
+ if thiswin == winnr()
+ break
+ endif
+ endwhile
+ endif
+ if &filetype != "man"
+ if exists("g:ft_man_open_mode")
+ if g:ft_man_open_mode == 'vert'
+ let open_cmd = 'vsplit'
+ elseif g:ft_man_open_mode == 'tab'
+ let open_cmd = 'tabedit'
+ else
+ let open_cmd = 'split'
+ endif
+ else
+ let open_cmd = a:cmdmods . ' split'
+ endif
+ endif
+ endif
+
+ silent execute open_cmd . " $HOME/" . page . '.' . sect . '~'
+
+ " Avoid warning for editing the dummy file twice
+ setl buftype=nofile noswapfile
+
+ setl fdc=0 ma nofen nonu nornu
+ %delete _
+ let unsetwidth = 0
+ if empty($MANWIDTH)
+ let $MANWIDTH = winwidth(0)
+ let unsetwidth = 1
+ endif
+
+ " Ensure Vim is not recursively invoked (man-db does this) when doing ctrl-[
+ " on a man page reference by unsetting MANPAGER.
+ " Some versions of env(1) do not support the '-u' option, and in such case
+ " we set MANPAGER=cat.
+ if !exists('s:env_has_u')
+ call system('env -u x true')
+ let s:env_has_u = (v:shell_error == 0)
+ endif
+ let env_cmd = s:env_has_u ? 'env -u MANPAGER' : 'env MANPAGER=cat'
+ let env_cmd .= ' GROFF_NO_SGR=1'
+ let man_cmd = env_cmd . ' man ' . s:GetCmdArg(sect, page) . ' | col -b'
+ silent exec "r !" . man_cmd
+
+ if unsetwidth
+ let $MANWIDTH = ''
+ endif
+ " Remove blank lines from top and bottom.
+ while line('$') > 1 && getline(1) =~ '^\s*$'
+ 1delete _
+ endwhile
+ while line('$') > 1 && getline('$') =~ '^\s*$'
+ $delete _
+ endwhile
+ 1
+ setl ft=man nomod
+ setl bufhidden=hide
+ setl nobuflisted
+ setl noma
+endfunc
+
+func dist#man#PopPage()
+ if s:man_tag_depth > 0
+ let s:man_tag_depth = s:man_tag_depth - 1
+ exec "let s:man_tag_buf=s:man_tag_buf_".s:man_tag_depth
+ exec "let s:man_tag_lin=s:man_tag_lin_".s:man_tag_depth
+ exec "let s:man_tag_col=s:man_tag_col_".s:man_tag_depth
+ exec s:man_tag_buf."b"
+ exec s:man_tag_lin
+ exec "norm! ".s:man_tag_col."|"
+ exec "unlet s:man_tag_buf_".s:man_tag_depth
+ exec "unlet s:man_tag_lin_".s:man_tag_depth
+ exec "unlet s:man_tag_col_".s:man_tag_depth
+ unlet s:man_tag_buf s:man_tag_lin s:man_tag_col
+ endif
+endfunc
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: set sw=2 ts=8 noet:
diff --git a/runtime/autoload/dist/script.vim b/runtime/autoload/dist/script.vim
new file mode 100644
index 0000000..f86c428
--- /dev/null
+++ b/runtime/autoload/dist/script.vim
@@ -0,0 +1,434 @@
+vim9script
+
+# Vim function for detecting a filetype from the file contents.
+# Invoked from "scripts.vim" in 'runtimepath'
+#
+# Maintainer: Bram Moolenaar <Bram@vim.org>
+# Last Change: 2022 Nov 24
+
+export def DetectFiletype()
+ var line1 = getline(1)
+ if line1[0] == '#' && line1[1] == '!'
+ # File that starts with "#!".
+ DetectFromHashBang(line1)
+ else
+ # File does not start with "#!".
+ DetectFromText(line1)
+ endif
+enddef
+
+# Called for a script that has "#!" in the first line.
+def DetectFromHashBang(firstline: string)
+ var line1 = firstline
+
+ # Check for a line like "#!/usr/bin/env {options} bash". Turn it into
+ # "#!/usr/bin/bash" to make matching easier.
+ # Recognize only a few {options} that are commonly used.
+ if line1 =~ '^#!\s*\S*\<env\s'
+ line1 = substitute(line1, '\S\+=\S\+', '', 'g')
+ line1 = substitute(line1, '\(-[iS]\|--ignore-environment\|--split-string\)', '', '')
+ line1 = substitute(line1, '\<env\s\+', '', '')
+ endif
+
+ # Get the program name.
+ # Only accept spaces in PC style paths: "#!c:/program files/perl [args]".
+ # If the word env is used, use the first word after the space:
+ # "#!/usr/bin/env perl [path/args]"
+ # If there is no path use the first word: "#!perl [path/args]".
+ # Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]".
+ var name: string
+ if line1 =~ '^#!\s*\a:[/\\]'
+ name = substitute(line1, '^#!.*[/\\]\(\i\+\).*', '\1', '')
+ elseif line1 =~ '^#!.*\<env\>'
+ name = substitute(line1, '^#!.*\<env\>\s\+\(\i\+\).*', '\1', '')
+ elseif line1 =~ '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)'
+ name = substitute(line1, '^#!\s*\([^/\\ ]*\>\).*', '\1', '')
+ else
+ name = substitute(line1, '^#!\s*\S*[/\\]\(\i\+\).*', '\1', '')
+ endif
+
+ # tcl scripts may have #!/bin/sh in the first line and "exec wish" in the
+ # third line. Suggested by Steven Atkinson.
+ if getline(3) =~ '^exec wish'
+ name = 'wish'
+ endif
+
+ # Bourne-like shell scripts: bash bash2 dash ksh ksh93 sh
+ if name =~ '^\(bash\d*\|dash\|ksh\d*\|sh\)\>'
+ call dist#ft#SetFileTypeSH(line1)
+
+ # csh scripts
+ elseif name =~ '^csh\>'
+ if exists("g:filetype_csh")
+ call dist#ft#SetFileTypeShell(g:filetype_csh)
+ else
+ call dist#ft#SetFileTypeShell("csh")
+ endif
+
+ # tcsh scripts
+ elseif name =~ '^tcsh\>'
+ call dist#ft#SetFileTypeShell("tcsh")
+
+ # Z shell scripts
+ elseif name =~ '^zsh\>'
+ set ft=zsh
+
+ # TCL scripts
+ elseif name =~ '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>'
+ set ft=tcl
+
+ # Expect scripts
+ elseif name =~ '^expect\>'
+ set ft=expect
+
+ # Gnuplot scripts
+ elseif name =~ '^gnuplot\>'
+ set ft=gnuplot
+
+ # Makefiles
+ elseif name =~ 'make\>'
+ set ft=make
+
+ # Pike
+ elseif name =~ '^pike\%(\>\|[0-9]\)'
+ set ft=pike
+
+ # Lua
+ elseif name =~ 'lua'
+ set ft=lua
+
+ # Perl
+ elseif name =~ 'perl'
+ set ft=perl
+
+ # PHP
+ elseif name =~ 'php'
+ set ft=php
+
+ # Python
+ elseif name =~ 'python'
+ set ft=python
+
+ # Groovy
+ elseif name =~ '^groovy\>'
+ set ft=groovy
+
+ # Raku
+ elseif name =~ 'raku'
+ set ft=raku
+
+ # Ruby
+ elseif name =~ 'ruby'
+ set ft=ruby
+
+ # JavaScript
+ elseif name =~ 'node\(js\)\=\>\|js\>' || name =~ 'rhino\>'
+ set ft=javascript
+
+ # BC calculator
+ elseif name =~ '^bc\>'
+ set ft=bc
+
+ # sed
+ elseif name =~ 'sed\>'
+ set ft=sed
+
+ # OCaml-scripts
+ elseif name =~ 'ocaml'
+ set ft=ocaml
+
+ # Awk scripts; also finds "gawk"
+ elseif name =~ 'awk\>'
+ set ft=awk
+
+ # Website MetaLanguage
+ elseif name =~ 'wml'
+ set ft=wml
+
+ # Scheme scripts
+ elseif name =~ 'scheme'
+ set ft=scheme
+
+ # CFEngine scripts
+ elseif name =~ 'cfengine'
+ set ft=cfengine
+
+ # Erlang scripts
+ elseif name =~ 'escript'
+ set ft=erlang
+
+ # Haskell
+ elseif name =~ 'haskell'
+ set ft=haskell
+
+ # Scala
+ elseif name =~ 'scala\>'
+ set ft=scala
+
+ # Clojure
+ elseif name =~ 'clojure'
+ set ft=clojure
+
+ # Free Pascal
+ elseif name =~ 'instantfpc\>'
+ set ft=pascal
+
+ # Fennel
+ elseif name =~ 'fennel\>'
+ set ft=fennel
+
+ # MikroTik RouterOS script
+ elseif name =~ 'rsc\>'
+ set ft=routeros
+
+ # Fish shell
+ elseif name =~ 'fish\>'
+ set ft=fish
+
+ # Gforth
+ elseif name =~ 'gforth\>'
+ set ft=forth
+
+ # Icon
+ elseif name =~ 'icon\>'
+ set ft=icon
+
+ # Guile
+ elseif name =~ 'guile'
+ set ft=scheme
+
+ endif
+enddef
+
+
+# Called for a script that does not have "#!" in the first line.
+def DetectFromText(line1: string)
+ var line2 = getline(2)
+ var line3 = getline(3)
+ var line4 = getline(4)
+ var line5 = getline(5)
+
+ # Bourne-like shell scripts: sh ksh bash bash2
+ if line1 =~ '^:$'
+ call dist#ft#SetFileTypeSH(line1)
+
+ # Z shell scripts
+ elseif line1 =~ '^#compdef\>'
+ || line1 =~ '^#autoload\>'
+ || "\n" .. line1 .. "\n" .. line2 .. "\n" .. line3 ..
+ "\n" .. line4 .. "\n" .. line5
+ =~ '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>'
+ set ft=zsh
+
+ # ELM Mail files
+ elseif line1 =~ '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$'
+ set ft=mail
+
+ # Mason
+ elseif line1 =~ '^<[%&].*>'
+ set ft=mason
+
+ # Vim scripts (must have '" vim' as the first line to trigger this)
+ elseif line1 =~ '^" *[vV]im$'
+ set ft=vim
+
+ # libcxx and libstdc++ standard library headers like "iostream" do not have
+ # an extension, recognize the Emacs file mode.
+ elseif line1 =~? '-\*-.*C++.*-\*-'
+ set ft=cpp
+
+ # MOO
+ elseif line1 =~ '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$'
+ set ft=moo
+
+ # Diff file:
+ # - "diff" in first line (context diff)
+ # - "Only in " in first line
+ # - "--- " in first line and "+++ " in second line (unified diff).
+ # - "*** " in first line and "--- " in second line (context diff).
+ # - "# It was generated by makepatch " in the second line (makepatch diff).
+ # - "Index: <filename>" in the first line (CVS file)
+ # - "=== ", line of "=", "---", "+++ " (SVK diff)
+ # - "=== ", "--- ", "+++ " (bzr diff, common case)
+ # - "=== (removed|added|renamed|modified)" (bzr diff, alternative)
+ # - "# HG changeset patch" in first line (Mercurial export format)
+ elseif line1 =~ '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)'
+ || (line1 =~ '^--- ' && line2 =~ '^+++ ')
+ || (line1 =~ '^\* looking for ' && line2 =~ '^\* comparing to ')
+ || (line1 =~ '^\*\*\* ' && line2 =~ '^--- ')
+ || (line1 =~ '^=== ' && ((line2 =~ '^=\{66\}' && line3 =~ '^--- ' && line4 =~ '^+++') || (line2 =~ '^--- ' && line3 =~ '^+++ ')))
+ || (line1 =~ '^=== \(removed\|added\|renamed\|modified\)')
+ set ft=diff
+
+ # PostScript Files (must have %!PS as the first line, like a2ps output)
+ elseif line1 =~ '^%![ \t]*PS'
+ set ft=postscr
+
+ # M4 scripts: Guess there is a line that starts with "dnl".
+ elseif line1 =~ '^\s*dnl\>'
+ || line2 =~ '^\s*dnl\>'
+ || line3 =~ '^\s*dnl\>'
+ || line4 =~ '^\s*dnl\>'
+ || line5 =~ '^\s*dnl\>'
+ set ft=m4
+
+ # AmigaDos scripts
+ elseif $TERM == "amiga" && (line1 =~ "^;" || line1 =~? '^\.bra')
+ set ft=amiga
+
+ # SiCAD scripts (must have procn or procd as the first line to trigger this)
+ elseif line1 =~? '^ *proc[nd] *$'
+ set ft=sicad
+
+ # Purify log files start with "**** Purify"
+ elseif line1 =~ '^\*\*\*\* Purify'
+ set ft=purifylog
+
+ # XML
+ elseif line1 =~ '<?\s*xml.*?>'
+ set ft=xml
+
+ # XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN")
+ elseif line1 =~ '\<DTD\s\+XHTML\s'
+ set ft=xhtml
+
+ # HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN")
+ # Avoid "doctype html", used by slim.
+ elseif line1 =~? '<!DOCTYPE\s\+html\>'
+ set ft=html
+
+ # PDF
+ elseif line1 =~ '^%PDF-'
+ set ft=pdf
+
+ # XXD output
+ elseif line1 =~ '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} '
+ set ft=xxd
+
+ # RCS/CVS log output
+ elseif line1 =~ '^RCS file:' || line2 =~ '^RCS file:'
+ set ft=rcslog
+
+ # CVS commit
+ elseif line2 =~ '^CVS:' || getline("$") =~ '^CVS: '
+ set ft=cvs
+
+ # Prescribe
+ elseif line1 =~ '^!R!'
+ set ft=prescribe
+
+ # Send-pr
+ elseif line1 =~ '^SEND-PR:'
+ set ft=sendpr
+
+ # SNNS files
+ elseif line1 =~ '^SNNS network definition file'
+ set ft=snnsnet
+ elseif line1 =~ '^SNNS pattern definition file'
+ set ft=snnspat
+ elseif line1 =~ '^SNNS result file'
+ set ft=snnsres
+
+ # Virata
+ elseif line1 =~ '^%.\{-}[Vv]irata'
+ || line2 =~ '^%.\{-}[Vv]irata'
+ || line3 =~ '^%.\{-}[Vv]irata'
+ || line4 =~ '^%.\{-}[Vv]irata'
+ || line5 =~ '^%.\{-}[Vv]irata'
+ set ft=virata
+
+ # Strace
+ elseif line1 =~ '[0-9:.]* *execve(' || line1 =~ '^__libc_start_main'
+ set ft=strace
+
+ # VSE JCL
+ elseif line1 =~ '^\* $$ JOB\>' || line1 =~ '^// *JOB\>'
+ set ft=vsejcl
+
+ # TAK and SINDA
+ elseif line4 =~ 'K & K Associates' || line2 =~ 'TAK 2000'
+ set ft=takout
+ elseif line3 =~ 'S Y S T E M S I M P R O V E D '
+ set ft=sindaout
+ elseif getline(6) =~ 'Run Date: '
+ set ft=takcmp
+ elseif getline(9) =~ 'Node File 1'
+ set ft=sindacmp
+
+ # DNS zone files
+ elseif line1 .. line2 .. line3 .. line4 =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA'
+ set ft=bindzone
+
+ # BAAN
+ elseif line1 =~ '|\*\{1,80}' && line2 =~ 'VRC '
+ || line2 =~ '|\*\{1,80}' && line3 =~ 'VRC '
+ set ft=baan
+
+ # Valgrind
+ elseif line1 =~ '^==\d\+== valgrind' || line3 =~ '^==\d\+== Using valgrind'
+ set ft=valgrind
+
+ # Go docs
+ elseif line1 =~ '^PACKAGE DOCUMENTATION$'
+ set ft=godoc
+
+ # Renderman Interface Bytestream
+ elseif line1 =~ '^##RenderMan'
+ set ft=rib
+
+ # Scheme scripts
+ elseif line1 =~ 'exec\s\+\S*scheme' || line2 =~ 'exec\s\+\S*scheme'
+ set ft=scheme
+
+ # Git output
+ elseif line1 =~ '^\(commit\|tree\|object\) \x\{40,\}\>\|^tag \S\+$'
+ set ft=git
+
+ # Gprof (gnu profiler)
+ elseif line1 == 'Flat profile:'
+ && line2 == ''
+ && line3 =~ '^Each sample counts as .* seconds.$'
+ set ft=gprof
+
+ # Erlang terms
+ # (See also: http://www.gnu.org/software/emacs/manual/html_node/emacs/Choosing-Modes.html#Choosing-Modes)
+ elseif line1 =~? '-\*-.*erlang.*-\*-'
+ set ft=erlang
+
+ # YAML
+ elseif line1 =~ '^%YAML'
+ set ft=yaml
+
+ # MikroTik RouterOS script
+ elseif line1 =~ '^#.*by RouterOS.*$'
+ set ft=routeros
+
+ # Sed scripts
+ # #ncomment is allowed but most likely a false positive so require a space
+ # before any trailing comment text
+ elseif line1 =~ '^#n\%($\|\s\)'
+ set ft=sed
+
+ else
+ var lnum = 1
+ while getline(lnum) =~ "^? " && lnum < line("$")
+ lnum += 1
+ endwhile
+ if getline(lnum) =~ '^Index:\s\+\f\+$'
+ # CVS diff
+ set ft=diff
+
+ # locale input files: Formal Definitions of Cultural Conventions
+ # filename must be like en_US, fr_FR@euro or en_US.UTF-8
+ elseif expand("%") =~ '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_'
+ lnum = 1
+ while lnum < 100 && lnum < line("$")
+ if getline(lnum) =~ '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$'
+ setf fdcc
+ break
+ endif
+ lnum += 1
+ endwhile
+ endif
+ endif
+enddef
diff --git a/runtime/autoload/dist/vimindent.vim b/runtime/autoload/dist/vimindent.vim
new file mode 100644
index 0000000..1306d1e
--- /dev/null
+++ b/runtime/autoload/dist/vimindent.vim
@@ -0,0 +1,1257 @@
+vim9script
+
+# Language: Vim script
+# Maintainer: github user lacygoill
+# Last Change: 2023 Feb 01
+
+# NOTE: Whenever you change the code, make sure the tests are still passing:
+#
+# $ cd runtime/indent/
+# $ make clean; make test || vimdiff testdir/vim.{ok,fail}
+
+# 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\|$\)'
+
+# NOT_A_DICT_KEY {{{3
+
+const NOT_A_DICT_KEY: string = ':\@!'
+
+# 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\)\@!'
+# }}}2
+# Syntaxes {{{2
+# BLOCKS {{{3
+
+const BLOCKS: list<list<string>> = [
+ ['if', 'el\%[se]', 'elseif\=', 'en\%[dif]'],
+ ['for', 'endfor\='],
+ ['wh\%[ile]', 'endw\%[hile]'],
+ ['try', 'cat\%[ch]', 'fina\|finally\=', 'endt\%[ry]'],
+ ['def', 'enddef'],
+ ['fu\%[nction](\@!', 'endf\%[unction]'],
+ ['class', 'endclass'],
+ ['interface', 'endinterface'],
+ ['enum', 'endenum'],
+ ['aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+', 'aug\%[roup]\s\+[eE][nN][dD]'],
+]
+
+# MODIFIERS {{{3
+
+# some keywords can be prefixed by modifiers (e.g. `def` can be prefixed by `export`)
+const MODIFIERS: dict<string> = {
+ def: ['export', 'static'],
+ class: ['export', 'abstract', 'export abstract'],
+ interface: ['export'],
+}
+# ...
+# class: ['export', 'abstract', 'export abstract'],
+# ...
+# →
+# ...
+# class: '\%(export\|abstract\|export\s\+abstract\)\s\+',
+# ...
+->map((_, mods: list<string>): string =>
+ '\%(' .. mods
+ ->join('\|')
+ ->substitute('\s\+', '\\s\\+', 'g')
+ .. '\)' .. '\s\+')
+
+# 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('\|')}\){NOT_A_DICT_KEY}'
+
+# START_MIDDLE_END {{{3
+
+# Let's derive this constant from `BLOCKS`:
+#
+# [['if', 'el\%[se]', 'elseif\=', 'en\%[dif]'],
+# ['for', 'endfor\='],
+# ...,
+# [...]]
+# →
+# {
+# 'for': ['for', '', 'endfor\='],
+# 'endfor': ['for', '', 'endfor\='],
+# '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]'],
+# ...
+# }
+var START_MIDDLE_END: dict<list<string>>
+
+def Unshorten(kwd: string): string
+ return BlockStartKeyword(kwd)
+enddef
+
+def BlockStartKeyword(line: string): string
+ var kwd: string = line->matchstr('\l\+')
+ return fullcommand(kwd, false)
+enddef
+
+{
+ for kwds: list<string> in BLOCKS
+ var [start: string, middle: string, end: string] = [kwds[0], '', kwds[-1]]
+ if MODIFIERS->has_key(start->Unshorten())
+ start = $'\%({MODIFIERS[start]}\)\={start}'
+ endif
+ if kwds->len() > 2
+ middle = kwds[1 : -2]->join('\|')
+ endif
+ for kwd: string in kwds
+ START_MIDDLE_END->extend({[kwd->Unshorten()]: [start, middle, end]})
+ endfor
+ endfor
+}
+
+START_MIDDLE_END = START_MIDDLE_END
+ ->map((_, kwds: list<string>) =>
+ kwds->map((_, kwd: string) => kwd == ''
+ ? ''
+ : $'\%(^\|{BAR_SEPARATION}\|\<sil\%[ent]\|{HIGHER_ORDER_COMMAND}\)\s*'
+ .. $'\<\%({kwd}\)\>\%(\s*{OPERATOR}\)\@!'))
+
+lockvar! START_MIDDLE_END
+
+# ENDS_BLOCK {{{3
+
+const ENDS_BLOCK: string = '^\s*\%('
+ .. BLOCKS
+ ->copy()
+ ->map((_, kwds: list<string>): string => kwds[-1])
+ ->join('\|')
+ .. '\|' .. CLOSING_BRACKET
+ .. $'\){END_OF_COMMAND}'
+
+# ENDS_BLOCK_OR_CLAUSE {{{3
+
+patterns = BLOCKS
+ ->copy()
+ ->map((_, kwds: list<string>) => kwds[1 :])
+ ->flattennew()
+ # `catch` and `elseif` need to be handled as special cases
+ ->filter((_, pat: string): bool => pat->Unshorten() !~ '^\%(catch\|elseif\)\>')
+
+const ENDS_BLOCK_OR_CLAUSE: string = '^\s*\%(' .. patterns->join('\|') .. $'\){END_OF_COMMAND}'
+ .. $'\|^\s*cat\%[ch]\%(\s\+\({PATTERN_DELIMITER}\).*\1\)\={END_OF_COMMAND}'
+ .. $'\|^\s*elseif\=\>\%({OPERATOR}\)\@!'
+
+# STARTS_NAMED_BLOCK {{{3
+
+patterns = []
+{
+ for kwds: list<string> in BLOCKS
+ for kwd: string in kwds[0 : -2]
+ if MODIFIERS->has_key(kwd->Unshorten())
+ patterns += [$'\%({MODIFIERS[kwd]}\)\={kwd}']
+ else
+ patterns += [kwd]
+ endif
+ endfor
+ endfor
+}
+
+const STARTS_NAMED_BLOCK: string = $'^\s*\%(sil\%[ent]\s\+\)\=\%({patterns->join('\|')}\)\>{NOT_A_DICT_KEY}'
+
+# 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_FUNCTION {{{3
+
+const STARTS_FUNCTION: string = $'^\s*\%({MODIFIERS.def}\)\=def\>{NOT_A_DICT_KEY}'
+
+# ENDS_FUNCTION {{{3
+
+const ENDS_FUNCTION: string = $'^\s*enddef\>{END_OF_COMMAND}'
+
+# ASSIGNS_HEREDOC {{{3
+
+const ASSIGNS_HEREDOC: string = $'^\%({COMMENT}\)\@!.*\%({HEREDOC_OPERATOR}\)\s\+\zs[A-Z]\+{END_OF_LINE}'
+
+# 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*$'
+
+# TRICKY_COMMANDS {{{3
+
+# Some commands are tricky because they accept an argument which can be
+# conflated with an operator. Examples:
+#
+# argdelete *
+# cd -
+# normal! ==
+# nunmap <buffer> (
+#
+# TODO: Other commands might accept operators as argument. Handle them too.
+patterns =<< trim eval END
+ {'\'}<argd\%[elete]\s\+\*\s*$
+ \<[lt]\=cd!\=\s\+-\s*$
+ \<norm\%[al]!\=\s*\S\+$
+ \%(\<sil\%[ent]!\=\s\+\)\=\<[nvxsoilct]\=\%(nore\|un\)map!\=\s
+ {PLUS_MINUS_COMMAND}
+END
+
+const TRICKY_COMMANDS: string = patterns->join('\|')
+# }}}2
+# EOL {{{2
+# OPENING_BRACKET_AT_EOL {{{3
+
+const OPENING_BRACKET_AT_EOL: string = OPENING_BRACKET .. END_OF_VIM9_LINE
+
+# CLOSING_BRACKET_AT_EOL {{{3
+
+const CLOSING_BRACKET_AT_EOL: string = CLOSING_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 = v:lnum): 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')
+ && !IsInInterface()
+ 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')
+ var is_in_curly_block: bool = IsInCurlyBlock()
+ for block: dict<any> in b:vimindent.block_stack
+ if line_A.lnum <= block.startlnum
+ continue
+ endif
+ if !block->has_key('startindent')
+ block.startindent = Indent(block.startlnum)
+ endif
+ if !is_in_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
+ + (past_bracket_block.startline =~ STARTS_NAMED_BLOCK ? 2 * shiftwidth() : 0)
+ 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
+ # FIXME: Similar issue here:
+ #
+ # var x = []
+ # ->filter((_, _) =>
+ # true)
+ # ->items()
+ #
+ # Press `==` on last line.
+ # Expected: The `->items()` line is indented like `->filter(...)`.
+ # Actual: It's indented like `true)`.
+ # Is it worth fixing? `=ip` gives the correct indentation, because then the
+ # cache is used.
+
+ # 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: number = 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()
+enddef
+# }}}1
+# Core {{{1
+def Offset( # {{{2
+ # we indent this line ...
+ line_A: dict<any>,
+ # ... relatively to this line
+ line_B: dict<any>,
+ ): number
+
+ if line_B->AtStartOf('FuncHeader')
+ && IsInInterface()
+ return 0
+
+ # increase indentation inside a block
+ elseif 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
+ 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
+ return
+ endif
+
+ b:vimindent = {
+ is_FuncHeader: true,
+ startindent: startlnum->Indent(),
+ endlnum: endlnum,
+ }
+ RegisterCacheInvalidation()
+enddef
+
+def CacheBracketBlock(line_A: dict<any>) # {{{2
+ var pos: list<number> = getcurpos()
+ var opening: string = line_A.text->matchstr(CHARACTER_UNDER_CURSOR)
+ var closing: string = {'[': ']', '{': '}', '(': ')'}[opening]
+ var endlnum: number = SearchPair(opening, '', closing, 'nW')
+ setpos('.', pos)
+ if endlnum <= line_A.lnum
+ return
+ endif
+
+ if !exists('b:vimindent')
+ b:vimindent = {
+ is_BracketBlock: true,
+ is_on_named_block_line: line_A.text =~ STARTS_NAMED_BLOCK,
+ block_stack: [],
+ }
+ endif
+
+ var is_dict: bool
+ var is_curly_block: bool
+ if opening == '{'
+ if line_A.text =~ STARTS_CURLY_BLOCK
+ [is_dict, is_curly_block] = [false, true]
+ else
+ [is_dict, is_curly_block] = [true, false]
+ endif
+ endif
+ b:vimindent.block_stack->insert({
+ is_dict: is_dict,
+ is_curly_block: is_curly_block,
+ startline: line_A.text,
+ startlnum: line_A.lnum,
+ endlnum: endlnum,
+ })
+
+ RegisterCacheInvalidation()
+enddef
+
+def RegisterCacheInvalidation() # {{{2
+ # invalidate the cache so that it's not used for the next `=` normal command
+ autocmd_add([{
+ cmd: 'unlet! b:vimindent',
+ event: 'ModeChanged',
+ group: '__VimIndent__',
+ once: true,
+ pattern: '*:n',
+ replace: true,
+ }])
+enddef
+
+def RemovePastBracketBlock(line_A: dict<any>): dict<any> # {{{2
+ var stack: list<dict<any>> = b:vimindent.block_stack
+
+ var removed: dict<any>
+ if line_A.lnum > stack[0].endlnum
+ removed = stack[0]
+ endif
+
+ stack->filter((_, block: dict<any>): bool => line_A.lnum <= block.endlnum)
+ if stack->empty()
+ unlet! b:vimindent
+ endif
+ return removed
+enddef
+# }}}1
+# Util {{{1
+# Get {{{2
+def Indent(lnum: number): number # {{{3
+ if lnum <= 0
+ # Don't return `-1`. It could cause `Expr()` to return a non-multiple of `'shiftwidth'`.{{{
+ #
+ # It would be OK if we were always returning `Indent()` directly. But
+ # we don't. Most of the time, we include it in some computation
+ # like `Indent(...) + shiftwidth()`. If `'shiftwidth'` is `4`, and
+ # `Indent()` returns `-1`, `Expr()` will end up returning `3`.
+ #}}}
+ return 0
+ endif
+ return indent(lnum)
+enddef
+
+def MatchingOpenBracket(line: dict<any>): number # {{{3
+ var end: string = line.text->matchstr(CLOSING_BRACKET)
+ var start: string = {']': '[', '}': '{', ')': '('}[end]
+ cursor(line.lnum, 1)
+ return SearchPairStart(start, '', end)
+enddef
+
+def FirstLinePreviousCommand(line: dict<any>): dict<any> # {{{3
+ var line_B: dict<any> = line
+
+ while line_B.lnum > 1
+ var code_line_above: dict<any> = PrevCodeLine(line_B.lnum)
+
+ if line_B.text =~ CLOSING_BRACKET_AT_SOL
+ var n: number = MatchingOpenBracket(line_B)
+
+ if n <= 0
+ break
+ endif
+
+ line_B.lnum = n
+ line_B.text = getline(line_B.lnum)
+ continue
+
+ elseif line_B->IsFirstLineOfCommand(code_line_above)
+ break
+ endif
+
+ line_B = code_line_above
+ endwhile
+
+ return line_B
+enddef
+
+def PrevCodeLine(lnum: number): dict<any> # {{{3
+ var line: string = getline(lnum)
+ if line =~ '^\s*[A-Z]\+$'
+ var endmarker: string = line->matchstr('[A-Z]\+')
+ var pos: list<number> = getcurpos()
+ cursor(lnum, 1)
+ var n: number = search(ASSIGNS_HEREDOC, 'bnW')
+ setpos('.', pos)
+ if n > 0
+ line = getline(n)
+ if line =~ $'{HEREDOC_OPERATOR}\s\+{endmarker}'
+ return {lnum: n, text: line}
+ endif
+ endif
+ endif
+
+ var n: number = prevnonblank(lnum - 1)
+ line = getline(n)
+ while line =~ COMMENT && n > 1
+ n = prevnonblank(n - 1)
+ line = getline(n)
+ endwhile
+ # If we get back to the first line, we return 1 no matter what; even if it's a
+ # commented line. That should not cause an issue though. We just want to
+ # avoid a commented line above which there is a line of code which is more
+ # relevant. There is nothing above the first line.
+ return {lnum: n, text: line}
+enddef
+
+def NextCodeLine(): number # {{{3
+ var last: number = line('$')
+ if v:lnum == last
+ return 0
+ endif
+
+ var lnum: number = v:lnum + 1
+ while lnum <= last
+ var line: string = getline(lnum)
+ if line != '' && line !~ COMMENT
+ return lnum
+ endif
+ ++lnum
+ endwhile
+ return 0
+enddef
+
+def SearchPair( # {{{3
+ start: string,
+ middle: string,
+ end: string,
+ flags: string,
+ stopline = 0,
+ ): number
+
+ var s: string = start
+ var e: string = end
+ if start == '[' || start == ']'
+ s = s->escape('[]')
+ endif
+ if end == '[' || end == ']'
+ e = e->escape('[]')
+ endif
+ return searchpair('\C' .. s, (middle == '' ? '' : '\C' .. middle), '\C' .. e,
+ flags, (): bool => InCommentOrString(), stopline, TIMEOUT)
+enddef
+
+def SearchPairStart( # {{{3
+ start: string,
+ middle: string,
+ end: string,
+ ): number
+ return SearchPair(start, middle, end, 'bnW')
+enddef
+
+def SearchPairEnd( # {{{3
+ start: string,
+ middle: string,
+ end: string,
+ stopline = 0,
+ ): number
+ return SearchPair(start, middle, end, 'nW', stopline)
+enddef
+# }}}2
+# Test {{{2
+def AtStartOf(line_A: dict<any>, syntax: string): bool # {{{3
+ if syntax == 'BracketBlock'
+ return AtStartOfBracketBlock(line_A)
+ endif
+
+ var pat: string = {
+ HereDoc: ASSIGNS_HEREDOC,
+ FuncHeader: STARTS_FUNCTION
+ }[syntax]
+ return line_A.text =~ pat
+ && (!exists('b:vimindent') || !b:vimindent->has_key('is_HereDoc'))
+enddef
+
+def AtStartOfBracketBlock(line_A: dict<any>): bool # {{{3
+ # We ignore bracket blocks while we're indenting a function header
+ # because it makes the logic simpler. It might mean that we don't
+ # indent correctly a multiline bracket block inside a function header,
+ # but that's a corner case for which it doesn't seem worth making the
+ # code more complex.
+ if exists('b:vimindent')
+ && !b:vimindent->has_key('is_BracketBlock')
+ return false
+ endif
+
+ var pos: list<number> = getcurpos()
+ cursor(line_A.lnum, [line_A.lnum, '$']->col())
+
+ if SearchPair(OPENING_BRACKET, '', CLOSING_BRACKET, 'bcW', line_A.lnum) <= 0
+ setpos('.', pos)
+ return false
+ endif
+ # Don't restore the cursor position.
+ # It needs to be on a bracket for `CacheBracketBlock()` to work as intended.
+
+ return line_A->EndsWithOpeningBracket()
+ || line_A->EndsWithCommaOrDictKey()
+ || line_A->EndsWithLambdaArrow()
+enddef
+
+def ContinuesBelowBracketBlock( # {{{3
+ line_A: string,
+ line_B: dict<any>,
+ block: dict<any>
+ ): bool
+
+ return !block->empty()
+ && (line_A =~ LINE_CONTINUATION_AT_SOL
+ || line_B->EndsWithLineContinuation())
+enddef
+
+def IsInside(lnum: number, syntax: string): bool # {{{3
+ if !exists('b:vimindent')
+ || !b:vimindent->has_key($'is_{syntax}')
+ return false
+ endif
+
+ if syntax == 'BracketBlock'
+ if !b:vimindent->has_key('block_stack')
+ || b:vimindent.block_stack->empty()
+ return false
+ endif
+ return lnum <= b:vimindent.block_stack[0].endlnum
+ endif
+
+ return lnum <= b:vimindent.endlnum
+enddef
+
+def IsRightBelow(lnum: number, syntax: string): bool # {{{3
+ return exists('b:vimindent')
+ && b:vimindent->has_key($'is_{syntax}')
+ && lnum > b:vimindent.endlnum
+enddef
+
+def IsInCurlyBlock(): bool # {{{3
+ return b:vimindent.block_stack
+ ->indexof((_, block: dict<any>): bool => block.is_curly_block) >= 0
+enddef
+
+def IsInThisBlock(line_A: dict<any>, lnum: number): bool # {{{3
+ var pos: list<number> = getcurpos()
+ cursor(lnum, [lnum, '$']->col())
+ var end: number = SearchPairEnd('{', '', '}')
+ setpos('.', pos)
+
+ return line_A.lnum <= end
+enddef
+
+def IsInInterface(): bool # {{{3
+ return SearchPair('interface', '', 'endinterface', 'nW') > 0
+enddef
+
+def IsFirstLineOfCommand(line_1: dict<any>, line_2: dict<any>): bool # {{{3
+ if line_1.text->Is_IN_KeywordForLoop(line_2.text)
+ return false
+ endif
+
+ if line_1.text =~ RANGE_AT_SOL
+ || line_1.text =~ PLUS_MINUS_COMMAND
+ return true
+ endif
+
+ if line_2.text =~ DICT_KEY
+ && !line_1->IsInThisBlock(line_2.lnum)
+ return true
+ endif
+
+ var line_1_is_good: bool = line_1.text !~ COMMENT
+ && line_1.text !~ DICT_KEY
+ && line_1.text !~ LINE_CONTINUATION_AT_SOL
+
+ var line_2_is_good: bool = !line_2->EndsWithLineContinuation()
+
+ return line_1_is_good && line_2_is_good
+enddef
+
+def Is_IN_KeywordForLoop(line_1: string, line_2: string): bool # {{{3
+ return line_2 =~ '^\s*for\s'
+ && line_1 =~ '^\s*in\s'
+enddef
+
+def InCommentOrString(): bool # {{{3
+ return synstack('.', col('.'))
+ ->indexof((_, id: number): bool => synIDattr(id, 'name') =~ '\ccomment\|string\|heredoc') >= 0
+enddef
+
+def AlsoClosesBlock(line_B: dict<any>): bool # {{{3
+ # We know that `line_B` opens a block.
+ # Let's see if it also closes that block.
+ var kwd: string = BlockStartKeyword(line_B.text)
+ if !START_MIDDLE_END->has_key(kwd)
+ return false
+ endif
+
+ var [start: string, middle: string, end: string] = START_MIDDLE_END[kwd]
+ var pos: list<number> = getcurpos()
+ cursor(line_B.lnum, 1)
+ var block_end: number = SearchPairEnd(start, middle, end, line_B.lnum)
+ setpos('.', pos)
+
+ return block_end > 0
+enddef
+
+def EndsWithComma(line: dict<any>): bool # {{{3
+ return NonCommentedMatch(line, COMMA_AT_EOL)
+enddef
+
+def EndsWithCommaOrDictKey(line_A: dict<any>): bool # {{{3
+ return NonCommentedMatch(line_A, COMMA_OR_DICT_KEY_AT_EOL)
+enddef
+
+def EndsWithCurlyBlock(line_B: dict<any>): bool # {{{3
+ return NonCommentedMatch(line_B, STARTS_CURLY_BLOCK)
+enddef
+
+def EndsWithLambdaArrow(line_A: dict<any>): bool # {{{3
+ return NonCommentedMatch(line_A, LAMBDA_ARROW_AT_EOL)
+enddef
+
+def EndsWithLineContinuation(line_B: dict<any>): bool # {{{3
+ return NonCommentedMatch(line_B, LINE_CONTINUATION_AT_EOL)
+enddef
+
+def EndsWithOpeningBracket(line: dict<any>): bool # {{{3
+ return NonCommentedMatch(line, OPENING_BRACKET_AT_EOL)
+enddef
+
+def EndsWithClosingBracket(line: dict<any>): bool # {{{3
+ return NonCommentedMatch(line, CLOSING_BRACKET_AT_EOL)
+enddef
+
+def NonCommentedMatch(line: dict<any>, pat: string): bool # {{{3
+ # Could happen if there is no code above us, and we're not on the 1st line.
+ # In that case, `PrevCodeLine()` returns `{lnum: 0, line: ''}`.
+ if line.lnum == 0
+ return false
+ endif
+
+ # Technically, that's wrong. A line might start with a range and end with a
+ # line continuation symbol. But it's unlikely. And it's useful to assume the
+ # opposite because it prevents us from conflating a mark with an operator or
+ # the start of a list:
+ #
+ # not a comparison operator
+ # v
+ # :'< mark <
+ # :'< mark [
+ # ^
+ # not the start of a list
+ if line.text =~ RANGE_AT_SOL
+ return false
+ endif
+
+ # that's not an arithmetic operator
+ # v
+ # catch /pattern /
+ #
+ # When `/` is used as a pattern delimiter, it's always present twice.
+ # And usually, the first occurrence is in the middle of a sequence of
+ # non-whitespace characters. If we can find such a `/`, we assume that the
+ # trailing `/` is not an operator.
+ # Warning: Here, don't use a too complex pattern.{{{
+ #
+ # In particular, avoid backreferences.
+ # For example, this would be too costly:
+ #
+ # if line.text =~ $'\%(\S*\({PATTERN_DELIMITER}\)\S\+\|\S\+\({PATTERN_DELIMITER}\)\S*\)'
+ # .. $'\s\+\1{END_OF_COMMAND}'
+ #
+ # Sometimes, it could even give `E363`.
+ #}}}
+ var delim: string = line.text
+ ->matchstr($'\s\+\zs{PATTERN_DELIMITER}\ze{END_OF_COMMAND}')
+ if !delim->empty()
+ delim = $'\V{delim}\m'
+ if line.text =~ $'\%(\S*{delim}\S\+\|\S\+{delim}\S*\)\s\+{delim}{END_OF_COMMAND}'
+ return false
+ endif
+ endif
+ # TODO: We might still miss some corner cases:{{{
+ #
+ # conflated with arithmetic division
+ # v
+ # substitute/pat / rep /
+ # echo
+ # ^--^
+ # ✘
+ #
+ # A better way to handle all these corner cases, would be to inspect the top
+ # of the syntax stack:
+ #
+ # :echo synID('.', col('.'), v:false)->synIDattr('name')
+ #
+ # Unfortunately, the legacy syntax plugin is not accurate enough.
+ # For example, it doesn't highlight a slash as an operator.
+ # }}}
+
+ # `%` at the end of a line is tricky.
+ # It might be the modulo operator or the current file (e.g. `edit %`).
+ # Let's assume it's the latter.
+ if line.text =~ $'%{END_OF_COMMAND}'
+ return false
+ endif
+
+ if line.text =~ TRICKY_COMMANDS
+ return false
+ endif
+
+ var pos: list<number> = getcurpos()
+ cursor(line.lnum, 1)
+ var match_lnum: number = search(pat, 'cnW', line.lnum, TIMEOUT, (): bool => InCommentOrString())
+ setpos('.', pos)
+ return match_lnum > 0
+enddef
+# }}}1
+# vim:sw=4