summaryrefslogtreecommitdiff
path: root/mnv/runtime/autoload/dist
diff options
context:
space:
mode:
Diffstat (limited to 'mnv/runtime/autoload/dist')
-rw-r--r--mnv/runtime/autoload/dist/ft.mnv3444
-rw-r--r--mnv/runtime/autoload/dist/json.mnv182
-rw-r--r--mnv/runtime/autoload/dist/man.mnv337
-rw-r--r--mnv/runtime/autoload/dist/mnv.mnv35
-rw-r--r--mnv/runtime/autoload/dist/mnv9.mnv154
-rw-r--r--mnv/runtime/autoload/dist/mnvindent.mnv1277
-rw-r--r--mnv/runtime/autoload/dist/script.mnv490
7 files changed, 5919 insertions, 0 deletions
diff --git a/mnv/runtime/autoload/dist/ft.mnv b/mnv/runtime/autoload/dist/ft.mnv
new file mode 100644
index 0000000000..5c91e591f1
--- /dev/null
+++ b/mnv/runtime/autoload/dist/ft.mnv
@@ -0,0 +1,3444 @@
+mnv9script
+
+# MNV functions for file type detection
+#
+# Maintainer: The MNV Project <https://github.com/Project-Tick/Project-Tick>
+# Last Change: 2026 Mar 24
+# Former Maintainer: Bram Moolenaar <Bram@mnv.org>
+
+# These functions are moved here from runtime/filetype.mnv to make startup
+# faster.
+
+var prolog_pattern = '^\s*\(:-\|%\+\(\s\|$\)\|\/\*\)\|\.\s*$'
+
+def IsObjectScriptRoutine(): bool
+ var line1 = getline(1)
+ line1 = substitute(line1, '^\ufeff', '', '')
+ return line1 =~? '^\s*routine\>\s\+[%A-Za-z][%A-Za-z0-9_.]*\%(\s*\[\|\s*;\|$\)'
+enddef
+
+export def Check_inp()
+ if getline(1) =~ '%%'
+ setf tex
+ elseif 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
+
+# Erlang Application Resource Files (*.app.src is matched by extension)
+# See: https://erlang.org/doc/system/applications
+export def FTapp()
+ if exists("g:filetype_app")
+ exe "setf " .. g:filetype_app
+ return
+ endif
+ const pat = '^\s*{\s*application\s*,\s*\(''\=\)' .. expand("%:t:r:r") .. '\1\s*,'
+ var line: string
+ for lnum in range(1, min([line("$"), 100]))
+ line = getline(lnum)
+ # skip Erlang comments, might be something else
+ if line =~ '^\s*%' || line =~ '^\s*$'
+ continue
+ elseif line =~ '^\s*{' &&
+ getline(lnum, lnum + 9)->filter((_, v) => v !~ '^\s*%')->join(' ') =~# pat
+ setf erlang
+ endif
+ return
+ endfor
+enddef
+
+# This function checks for the kind of assembly that is wanted by the user, or
+# can be detected from the beginning 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 FTmac()
+ if exists("g:filetype_mac")
+ exe "setf " .. g:filetype_mac
+ else
+ if IsObjectScriptRoutine()
+ setf objectscript_routine
+ else
+ FTasm()
+ endif
+ endif
+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
+ return
+ endif
+ # Use heuristics
+ var is_slash_star_encountered = false
+ var i = 1
+ const n = min([50, line("$")])
+ while i <= n
+ const line = getline(i)
+ if line =~ '^/\*'
+ is_slash_star_encountered = true
+ endif
+ if line =~# '^; Listing generated by Microsoft' || line =~? '^\%(\%(CONST\|_BSS\|_DATA\|_TEXT\)\s\+SEGMENT\>\)\|\s*\.[2-6]86P\?\>\|\s*\.XMM\>'
+ b:asmsyntax = "masm"
+ return
+ elseif line =~ 'Texas Instruments Incorporated' || (line =~ '^\*' && !is_slash_star_encountered)
+ # tiasm uses `* comment`, but detection is unreliable if '/*' is seen
+ b:asmsyntax = "tiasm"
+ return
+ elseif ((line =~? '\.title\>\|\.ident\>\|\.macro\>\|\.subtitle\>\|\.library\>'))
+ b:asmsyntax = "vmasm"
+ return
+ endif
+ i += 1
+ endwhile
+enddef
+
+var ft_visual_basic_content = '\c^\s*\%(Attribute\s\+VB_Name\|Begin\s\+\%(VB\.\|{\%(\x\+-\)\+\x\+}\)\)'
+
+# 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 FTcl()
+ if join(getline(1, 4), '') =~ '/\*'
+ setf opencl
+ else
+ setf lisp
+ endif
+enddef
+
+# Determines whether a *.cls file is ObjectScript, TeX, Rexx, Visual Basic, or Smalltalk.
+export def FTcls()
+ if exists("g:filetype_cls")
+ exe "setf " .. g:filetype_cls
+ return
+ endif
+
+ var line1 = getline(1)
+ if line1 =~ '^#!.*\<\%(rexx\|regina\)\>'
+ setf rexx
+ return
+ elseif line1 == 'VERSION 1.0 CLASS'
+ setf vb
+ return
+ endif
+
+ var nonblank1 = getline(nextnonblank(1))
+ var lnum = nextnonblank(1)
+ while lnum > 0 && lnum <= line("$")
+ var line = getline(lnum)
+ if line =~? '^\s*\%(import\|include\|includegenerator\)\>'
+ lnum = nextnonblank(lnum + 1)
+ else
+ nonblank1 = line
+ break
+ endif
+ endwhile
+
+ if nonblank1 =~? '^\s*class\>\s\+[%A-Za-z][%A-Za-z0-9_.]*\%(\s\+extends\>\|\s*\[\|\s*{\|$\)'
+ setf objectscript
+ elseif nonblank1 =~ '^\v%(\%|\\)'
+ setf tex
+ elseif nonblank1 =~ '^\s*\%(/\*\|::\w\)'
+ setf rexx
+ else
+ setf st
+ endif
+enddef
+
+export def FTll()
+ if getline(1) =~ ';\|\<source_filename\>\|\<target\>'
+ setf llvm
+ return
+ endif
+ var n = 1
+ while n < 100 && n <= line("$")
+ var line = getline(n)
+ if line =~ '^\s*%'
+ setf lex
+ return
+ endif
+ n += 1
+ endwhile
+ setf lifelines
+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
+
+# Searches within the first `maxlines` lines of the file for distinctive
+# Objective-C or C++ syntax and returns the appropriate filetype. Returns a
+# null_string if the search was inconclusive.
+def CheckObjCOrCpp(maxlines = 100): string
+ var n = 1
+ while n < maxlines && n <= line('$')
+ const line = getline(n)
+ if line =~ '\v^\s*\@%(class|interface|end)>'
+ return 'objcpp'
+ elseif line =~ '\v^\s*%(class|namespace|template|using)>'
+ return 'cpp'
+ endif
+ ++n
+ endwhile
+ return null_string
+enddef
+
+# Determines whether a *.h file is C, C++, Ch, or Objective-C/Objective-C++.
+export def FTheader()
+ if exists('g:filetype_h')
+ execute $'setf {g:filetype_h}'
+ elseif exists('g:c_syntax_for_h')
+ setf c
+ elseif exists('g:ch_syntax_for_h')
+ setf ch
+ else
+ # Search the first 100 lines of the file for distinctive Objective-C or C++
+ # syntax and set the filetype accordingly. Otherwise, use C as the default
+ # filetype.
+ execute $'setf {CheckObjCOrCpp() ?? 'c'}'
+ 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 FTdef()
+ # LaTeX def files are usually generated by docstrip, which will output '%%' in first line
+ if getline(1) =~ '%%'
+ setf tex
+ endif
+ if get(g:, "filetype_def", "") == "modula2" || IsModula2()
+ SetFiletypeModula2()
+ return
+ endif
+
+ if exists("g:filetype_def")
+ exe "setf " .. g:filetype_def
+ else
+ setf def
+ 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
+
+def IsForth(): bool
+ var first_line = nextnonblank(1)
+
+ # SwiftForth block comment (line is usually filled with '-' or '=') or
+ # OPTIONAL (sometimes precedes the header comment)
+ if getline(first_line) =~? '^\%({\%(\s\|$\)\|OPTIONAL\s\)'
+ return true
+ endif
+
+ var n = first_line
+ while n < 100 && n <= line("$")
+ # Forth comments and colon definitions
+ if getline(n) =~ '^[:(\\] '
+ return true
+ endif
+ n += 1
+ endwhile
+ return false
+enddef
+
+# Distinguish between Forth and Fortran
+export def FTf()
+ if exists("g:filetype_f")
+ exe "setf " .. g:filetype_f
+ elseif IsForth()
+ setf forth
+ else
+ setf fortran
+ endif
+enddef
+
+export def FTfrm()
+ if exists("g:filetype_frm")
+ exe "setf " .. g:filetype_frm
+ return
+ endif
+
+ if getline(1) == "VERSION 5.00"
+ setf vb
+ 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#
+export def FTfs()
+ if exists("g:filetype_fs")
+ exe "setf " .. g:filetype_fs
+ elseif IsForth()
+ setf forth
+ else
+ setf fsharp
+ endif
+enddef
+
+# Recursively searches for Hare source files within a directory, up to a given
+# depth.
+def IsHareModule(dir: string, depth: number): bool
+ if depth < 1
+ return false
+ elseif depth == 1
+ return !glob(dir .. '/*.ha')->empty()
+ endif
+
+ # Check all files in the directory before recursing into subdirectories.
+ const items = glob(dir .. '/*', true, true)
+ ->sort((a, b) => isdirectory(a) - isdirectory(b))
+ for n in items
+ if isdirectory(n)
+ if IsHareModule(n, depth - 1)
+ return true
+ endif
+ elseif n =~ '\.ha$'
+ return true
+ endif
+ endfor
+
+ return false
+enddef
+
+# Determines whether a README file is inside a Hare module and should receive
+# the 'haredoc' filetype.
+export def FTharedoc()
+ if IsHareModule('<afile>:h', get(g:, 'filetype_haredoc', 1))
+ setf haredoc
+ endif
+enddef
+
+# Distinguish between HTML, XHTML, Django and Angular
+export def FThtml()
+ var n = 1
+
+ # Test if the filename follows the Angular component template convention
+ # Disabled for the reasons mentioned here: #13594
+ # if expand('%:t') =~ '^.*\.component\.html$'
+ # setf htmlangular
+ # return
+ # endif
+
+ while n < 40 && n <= line("$")
+ # Check for Angular
+ if getline(n) =~ '@\(if\|for\|defer\|switch\)\|\*\(ngIf\|ngFor\|ngSwitch\|ngTemplateOutlet\)\|ng-template\|ng-content'
+ setf htmlangular
+ return
+ endif
+ # Check for XHTML
+ if getline(n) =~ '\<DTD\s\+XHTML\s'
+ setf xhtml
+ return
+ endif
+ # Check for Django
+ if getline(n) =~ '{%\s*\(autoescape\|block\|comment\|csrf_token\|cycle\|debug\|extends\|filter\|firstof\|for\|if\|ifchanged\|include\|load\|lorem\|now\|query_string\|regroup\|resetcycle\|spaceless\|templatetag\|url\|verbatim\|widthratio\|with\)\>\|{#\s\+'
+ setf htmldjango
+ return
+ endif
+ # Check for SuperHTML
+ if getline(n) =~ '<extend\|<super>'
+ setf superhtml
+ 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, zsh module's C and Cproto prototype file.
+export def ProtoCheck(default: string)
+ # zsh modules use '#include "*.pro"'
+ # https://github.com/zsh-users/zsh/blob/63f086d167960a27ecdbcb762179e2c2bf8a29f5/Src/Modules/example.c#L31
+ if getline(1) =~ '/* Generated automatically */'
+ setf c
+ # 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 ';'.
+ elseif 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 lnum = getline(nextnonblank(1))
+ if lnum =~ '\<prolog\>' || lnum =~ prolog_pattern
+ 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
+
+# For files ending in *.m4, distinguish:
+# – *.html.m4 files
+# - *fvwm2rc*.m4 files
+# – files in the Autoconf M4 dialect
+# – files in POSIX M4
+export def FTm4()
+ var fname = expand('%:t')
+ var path = expand('%:p:h')
+
+ if fname =~# 'html\.m4$'
+ setf htmlm4
+ return
+ endif
+
+ if fname =~# 'fvwm2rc'
+ setf fvwm2m4
+ return
+ endif
+
+ # Canonical Autoconf file
+ if fname ==# 'aclocal.m4'
+ setf config
+ return
+ endif
+
+ # Repo heuristic for Autoconf M4 (nearby configure.ac)
+ if filereadable(path .. '/../configure.ac') || filereadable(path .. '/configure.ac')
+ setf config
+ return
+ endif
+
+ # Content heuristic for Autoconf M4 (scan first ~200 lines)
+ # Signals:
+ # - Autoconf macro prefixes: AC_/AM_/AS_/AU_/AT_
+ var n = 1
+ var max = min([200, line('$')])
+ while n <= max
+ var line = getline(n)
+ if line =~# '^\s*A[CMSUT]_'
+ setf config
+ return
+ endif
+ n += 1
+ endwhile
+
+ # Default to POSIX M4
+ setf m4
+enddef
+
+export def FTmake()
+ # Check if it is a BSD, GNU, or Microsoft Makefile
+ unlet! b:make_flavor
+
+ # 1. filename
+ if expand('%:t') == 'BSDmakefile'
+ b:make_flavor = 'bsd'
+ setf make
+ return
+ elseif expand('%:t') == 'GNUmakefile'
+ b:make_flavor = 'gnu'
+ setf make
+ return
+ endif
+
+ # 2. user's setting
+ if exists('g:make_flavor')
+ b:make_flavor = g:make_flavor
+ setf make
+ return
+ elseif get(g:, 'make_microsoft')
+ echom "make_microsoft is deprecated; try g:make_flavor = 'microsoft' instead"
+ b:make_flavor = 'microsoft'
+ setf make
+ return
+ endif
+
+ # 3. try to detect a flavor from file content
+ var n = 1
+ while n < 1000 && n <= line('$')
+ var line = getline(n)
+ if line =~? '^\s*!\s*\(ifn\=\(def\)\=\|include\|message\|error\)\>'
+ b:make_flavor = 'microsoft'
+ break
+ elseif line =~ '^\.\%(export\|error\|for\|if\%(n\=\%(def\|make\)\)\=\|info\|warning\)\>'
+ b:make_flavor = 'bsd'
+ break
+ elseif line =~ '^ *\%(ifn\=\%(eq\|def\)\|define\|override\)\>'
+ b:make_flavor = 'gnu'
+ break
+ elseif line =~ '\$[({][a-z-]\+\s\+\S\+' # a function call, e.g. $(shell pwd)
+ b:make_flavor = 'gnu'
+ break
+ endif
+ n += 1
+ endwhile
+ setf make
+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 typical
+# nroff pattern in man files. In that case it is probably an nroff file:
+# 'filetype' is set and 1 is returned.
+export def FTnroff(): number
+ var n = 1
+ while n <= 5
+ var line = getline(n)
+ if line =~ '^\%([.'']\s*\%(TH\|D[dt]\|S[Hh]\|d[es]1\?\|so\)\s\+\S\|[.'']\s*ig\>\|\%([.'']\s*\)\?\\"\)'
+ setf nroff
+ return 1
+ endif
+ n += 1
+ endwhile
+ 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 lnum: number = nextnonblank(1)
+ while lnum > 0 && lnum < line('$') && getline(lnum) =~ '^\s*%' # LambdaProlog comment
+ lnum = nextnonblank(lnum + 1)
+ endwhile
+ # this pattern must not catch a go.mod file
+ return getline(lnum) =~ '\<module\s\+\w\+\s*\.\s*\(%\|$\)'
+enddef
+
+def IsModula2(): bool
+ return getline(nextnonblank(1)) =~ '\<MODULE\s\+\w\+\s*\%(\[.*]\s*\)\=;\|^\s*(\*'
+enddef
+
+def SetFiletypeModula2()
+ const KNOWN_DIALECTS = ["iso", "pim", "r10"]
+ const KNOWN_EXTENSIONS = ["gm2"]
+ const LINE_COUNT = 200
+ const TAG = '(\*!m2\(\w\+\)\%(+\(\w\+\)\)\=\*)'
+
+ var dialect = get(g:, "modula2_default_dialect", "pim")
+ var extension = get(g:, "modula2_default_extension", "")
+
+ var matches = []
+
+ # ignore unknown dialects or badly formatted tags
+ for lnum in range(1, min([line("$"), LINE_COUNT]))
+ matches = matchlist(getline(lnum), TAG)
+ if !empty(matches)
+ if index(KNOWN_DIALECTS, matches[1]) >= 0
+ dialect = matches[1]
+ endif
+ if index(KNOWN_EXTENSIONS, matches[2]) >= 0
+ extension = matches[2]
+ endif
+ break
+ endif
+ endfor
+
+ modula2#SetDialect(dialect, extension)
+
+ setf modula2
+enddef
+
+# Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+export def FTmod()
+ if get(g:, "filetype_mod", "") == "modula2" || IsModula2()
+ SetFiletypeModula2()
+ return
+ endif
+
+ if exists("g:filetype_mod")
+ exe "setf " .. g:filetype_mod
+ elseif expand("<afile>") =~ '\<go.mod$'
+ setf gomod
+ elseif IsLProlog()
+ setf lprolog
+ elseif IsRapid()
+ setf rapid
+ 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 line = getline(nextnonblank(1))
+ if line =~ '\<prolog\>' || line =~ prolog_pattern
+ setf prolog
+ else
+ setf perl
+ endif
+ endif
+enddef
+
+export def FTinc()
+ if exists("g:filetype_inc")
+ exe "setf " .. g:filetype_inc
+ else
+ if IsObjectScriptRoutine()
+ setf objectscript_routine
+ return
+ endif
+ for lnum in range(1, min([line("$"), 20]))
+ var line = getline(lnum)
+ if line =~? "perlscript"
+ setf aspperl
+ return
+ elseif line =~ "<%"
+ setf aspvbs
+ return
+ elseif line =~ "<?"
+ setf php
+ return
+ # Pascal supports // comments but they're vary rarely used for file
+ # headers so assume POV-Ray
+ elseif line =~ '^\s*\%({\|(\*\)' || line =~? ft_pascal_keywords
+ setf pascal
+ return
+ elseif line =~# '\<\%(require\|inherit\)\>' || line =~# '[A-Z][A-Za-z0-9_:${}/]*\s\+\%(??\|[?:+.]\)\?=.\? '
+ setf bitbake
+ return
+ endif
+ endfor
+ FTasmsyntax()
+ if exists("b:asmsyntax")
+ exe "setf " .. fnameescape(b:asmsyntax)
+ else
+ setf pov
+ 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
+
+# These include the leading '%' sign
+var ft_swig_keywords = '^\s*%\%(addmethods\|apply\|beginfile\|clear\|constant\|define\|echo\|enddef\|endoffile\|extend\|feature\|fragment\|ignore\|import\|importfile\|include\|includefile\|inline\|insert\|keyword\|module\|name\|namewarn\|native\|newobject\|parms\|pragma\|rename\|template\|typedef\|typemap\|types\|varargs\|warn\)'
+# This is the start/end of a block that is copied literally to the processor file (C/C++)
+var ft_swig_verbatim_block_start = '^\s*%{'
+
+export def FTi()
+ if exists("g:filetype_i")
+ exe "setf " .. g:filetype_i
+ return
+ endif
+ # This function checks for an assembly comment or a SWIG keyword or verbatim block in the first 50 lines.
+ # If not found, assume Progress.
+ var lnum = 1
+ while lnum <= 50 && lnum < line('$')
+ var line = getline(lnum)
+ if line =~ '^\s*;' || line =~ '^\*'
+ FTasm()
+ return
+ elseif line =~ ft_swig_keywords || line =~ ft_swig_verbatim_block_start
+ setf swig
+ return
+ endif
+ lnum += 1
+ endwhile
+ setf progress
+enddef
+
+export def FTint()
+ if exists("g:filetype_int")
+ exe "setf " .. g:filetype_int
+ elseif IsObjectScriptRoutine()
+ setf objectscript_routine
+ else
+ setf hex
+ endif
+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.mnv and scripts.mnv.
+# When "setft" is passed and false then the 'filetype' option is not set.
+export def SetFileTypeSH(name: string, setft = true): string
+ if setft && did_filetype()
+ # Filetype was already detected
+ return ''
+ endif
+ if setft && expand("<amatch>") =~ g:ft_ignore_pat
+ return ''
+ endif
+ if name =~ '^csh$' || name =~ '^#!.\{-2,}\<csh\>'
+ # Some .sh scripts contain #!/bin/csh.
+ return SetFileTypeShell("csh", setft)
+ elseif name =~ '^tcsh$' || name =~ '^#!.\{-2,}\<tcsh\>'
+ # Some .sh scripts contain #!/bin/tcsh.
+ return SetFileTypeShell("tcsh", setft)
+ elseif name =~ '^zsh$' || name =~ '^#!.\{-2,}\<zsh\>'
+ # Some .sh scripts contain #!/bin/zsh.
+ return SetFileTypeShell("zsh", setft)
+ elseif name =~ '^ksh$' || name =~ '^#!.\{-2,}\<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 =~ '^bash2\=$' ||
+ \ name =~ '^#!.\{-2,}\<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 =~ '^\%(da\)\=sh$' || name =~ '^#!.\{-2,}\<\%(da\)\=sh\>'
+ # 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
+
+ return SetFileTypeShell("sh", setft)
+enddef
+
+# For shell-like file types, check for an "exec" command hidden in a comment,
+# as used for Tcl.
+# When "setft" is passed and false then the 'filetype' option is not set.
+# Also called from scripts.mnv, thus can't be local to this script.
+export def SetFileTypeShell(name: string, setft = true): string
+ if setft && did_filetype()
+ # Filetype was already detected
+ return ''
+ endif
+ if setft && expand("<amatch>") =~ g:ft_ignore_pat
+ return ''
+ endif
+
+ var lnum = 2
+ while lnum < 20 && lnum < line("$") && getline(lnum) =~ '^\s*\(#\|$\)'
+ # Skip empty and comment lines.
+ lnum += 1
+ endwhile
+ if lnum < line("$") && getline(lnum) =~ '\s*exec\s' && getline(lnum - 1) =~ '^\s*#.*\\$'
+ # Found an "exec" line after a comment with continuation
+ var n = substitute(getline(lnum), '\s*exec\s\+\([^ ]*/\)\=', '', '')
+ if n =~ '\<tclsh\|\<wish'
+ if setft
+ setf tcl
+ endif
+ return 'tcl'
+ endif
+ endif
+
+ if setft
+ exe "setf " .. name
+ endif
+ return 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 /^MNV\%((\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
+
+export def FTsa()
+ if join(getline(1, 4), "\n") =~# '\%(^\|\n\);'
+ setf tiasm
+ return
+ endif
+ setf sather
+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 mnvrc.
+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
+
+export def FTtyp()
+ if exists("g:filetype_typ")
+ exe "setf " .. g:filetype_typ
+ return
+ endif
+
+ # Look for SQL type definition syntax
+ for line in getline(1, 200)
+ # SQL type files may define the casing
+ if line =~ '^CASE\s\==\s\=\(SAME\|LOWER\|UPPER\|OPPOSITE\)$'
+ setf sql
+ return
+ endif
+
+ # SQL type files may define some types as follows
+ if line =~ '^TYPE\s.*$'
+ setf sql
+ return
+ endif
+ endfor
+
+ # Otherwise, affect the typst filetype
+ setf typst
+enddef
+
+# Detect Microsoft Developer Studio Project files (Makefile) or Faust DSP
+# files.
+export def FTdsp()
+ if exists("g:filetype_dsp")
+ exe "setf " .. g:filetype_dsp
+ return
+ endif
+
+ # Test the filename
+ if expand('%:t') =~ '^[mM]akefile.*$'
+ setf make
+ return
+ endif
+
+ # Test the file contents
+ for line in getline(1, 200)
+ # Check for comment style
+ if line =~ '^#.*'
+ setf make
+ return
+ endif
+
+ # Check for common lines
+ if line =~ '^.*Microsoft Developer Studio Project File.*$'
+ setf make
+ return
+ endif
+
+ if line =~ '^!MESSAGE This is not a valid makefile\..+$'
+ setf make
+ return
+ endif
+
+ # Check for keywords
+ if line =~ '^!(IF,ELSEIF,ENDIF).*$'
+ setf make
+ return
+ endif
+
+ # Check for common assignments
+ if line =~ '^SOURCE=.*$'
+ setf make
+ return
+ endif
+ endfor
+
+ # Otherwise, assume we have a Faust file
+ setf faust
+enddef
+
+# Set the filetype of a *.v file to Verilog, V or Cog based on the first 500
+# lines.
+export def FTv()
+ if did_filetype()
+ # ":setf" will do nothing, bail out early
+ return
+ endif
+ if exists("g:filetype_v")
+ exe "setf " .. g:filetype_v
+ return
+ endif
+
+ var in_comment = 0
+ for lnum in range(1, min([line("$"), 500]))
+ var line = getline(lnum)
+ # Skip Verilog and V comments (lines and blocks).
+ if line =~ '^\s*/\*'
+ # start comment block
+ in_comment = 1
+ endif
+ if in_comment == 1
+ if line =~ '\*/'
+ # end comment block
+ in_comment = 0
+ endif
+ # skip comment-block line
+ continue
+ endif
+ if line =~ '^\s*//'
+ # skip comment line
+ continue
+ endif
+
+ # Coq: line ends with a '.' followed by an optional variable number of
+ # spaces or contains the start of a comment, but not inside a Verilog or V
+ # comment.
+ # Example: "Definition x := 10. (*".
+ if (line =~ '\.\s*$' && line !~ '/[/*]') || (line =~ '(\*' && line !~ '/[/*].*(\*')
+ setf coq
+ return
+ endif
+
+ # Verilog: line ends with ';' followed by an optional variable number of
+ # spaces and an optional start of a comment.
+ # Example: " b <= a + 1; // Add 1".
+ # Alternatively: a module is defined: " module MyModule ( input )"
+ if line =~ ';\s*\(/[/*].*\)\?$' || line =~ '\C^\s*module\s\+\w\+\s*('
+ setf verilog
+ return
+ endif
+ endfor
+
+ # No line matched, fall back to "v".
+ setf v
+enddef
+
+export def FTvba()
+ if getline(1) =~ '^["#] MNVball Archiver'
+ setf mnv
+ else
+ setf vb
+ endif
+enddef
+
+export def Detect_UCI_statements(): bool
+ # Match a config or package statement at the start of the line.
+ const config_or_package_statement = '^\s*\(\(c\|config\)\|\(p\|package\)\)\s\+\S'
+ # Match a line that is either all blank or blank followed by a comment
+ const comment_or_blank = '^\s*\(#.*\)\?$'
+
+ # Return true iff the file has a config or package statement near the
+ # top of the file and all preceding lines were comments or blank.
+ return getline(1) =~# config_or_package_statement
+ \ || getline(1) =~# comment_or_blank
+ \ && ( getline(2) =~# config_or_package_statement
+ \ || getline(2) =~# comment_or_blank
+ \ && getline(3) =~# config_or_package_statement
+ \ )
+enddef
+
+export def DetectFromName()
+ const amatch = expand("<amatch>")
+ const name = fnamemodify(amatch, ':t')
+ const ft = get(ft_from_name, name, '')
+ if ft != ''
+ execute "setf " .. ft
+ endif
+enddef
+
+export def DetectFromExt()
+ const amatch = expand("<amatch>")
+ var ext = fnamemodify(amatch, ':e')
+ const name = fnamemodify(amatch, ':t')
+ if ext == '' && name[0] == '.'
+ ext = name[1 : ]
+ endif
+ const ft = get(ft_from_ext, ext, '')
+ if ft != ''
+ execute "setf " .. ft
+ endif
+enddef
+
+# Key: extension of the file name. without `.`
+# Value: filetype
+const ft_from_ext = {
+ # 8th (Firth-derivative)
+ "8th": "8th",
+ # A-A-P recipe
+ "aap": "aap",
+ # ABAB/4
+ "abap": "abap",
+ # ABC music notation
+ "abc": "abc",
+ # ABEL
+ "abl": "abel",
+ # ABNF
+ "abnf": "abnf",
+ # AceDB
+ "wrm": "acedb",
+ # Ada (83, 9X, 95)
+ "adb": "ada",
+ "ads": "ada",
+ "ada": "ada",
+ # AHDL
+ "tdf": "ahdl",
+ # AIDL
+ "aidl": "aidl",
+ # AMPL
+ "run": "ampl",
+ # ANTLR / PCCTS
+ "g": "pccts",
+ # ANTLR 4
+ "g4": "antlr4",
+ # Arduino
+ "ino": "arduino",
+ "pde": "arduino",
+ # Asymptote
+ "asy": "asy",
+ # XA65 MOS6510 cross assembler
+ "a65": "a65",
+ # Applescript
+ "applescript": "applescript",
+ "scpt": "applescript",
+ # Applix ELF
+ "am": "elf",
+ # Arc Macro Language
+ "aml": "aml",
+ # ART*Enterprise (formerly ART-IM)
+ "art": "art",
+ # AsciiDoc
+ "asciidoc": "asciidoc",
+ "adoc": "asciidoc",
+ # ASN.1
+ "asn": "asn",
+ "asn1": "asn",
+ # Assembly - Netwide
+ "nasm": "nasm",
+ # Assembly - Microsoft
+ "masm": "masm",
+ # Assembly - Macro (VAX)
+ "mar": "vmasm",
+ # Astro
+ "astro": "astro",
+ # Atlas
+ "atl": "atlas",
+ "as": "atlas",
+ # Atom is based on XML
+ "atom": "xml",
+ # Authzed
+ "zed": "authzed",
+ # Autoit v3
+ "au3": "autoit",
+ # Autohotkey
+ "ahk": "autohotkey",
+ # Autotest .at files are actually Autoconf M4
+ "at": "config",
+ # Avenue
+ "ave": "ave",
+ # Awk
+ "awk": "awk",
+ "gawk": "awk",
+ # B
+ "mch": "b",
+ "ref": "b",
+ "imp": "b",
+ # Bass
+ "bass": "bass",
+ # IBasic file (similar to QBasic)
+ "iba": "ibasic",
+ "ibi": "ibasic",
+ # FreeBasic file (similar to QBasic)
+ "fb": "freebasic",
+ # Batch file for MSDOS. See dist#ft#FTsys for *.sys
+ "bat": "dosbatch",
+ # BC calculator
+ "bc": "bc",
+ # BDF font
+ "bdf": "bdf",
+ # Beancount
+ "beancount": "beancount",
+ # BibTeX bibliography database file
+ "bib": "bib",
+ # BibTeX Bibliography Style
+ "bst": "bst",
+ # Bicep
+ "bicep": "bicep",
+ "bicepparam": "bicep-params",
+ # BIND zone
+ "zone": "bindzone",
+ # Blank
+ "bl": "blank",
+ # Brighterscript
+ "bs": "brighterscript",
+ # Brightscript
+ "brs": "brightscript",
+ # BSDL
+ "bsd": "bsdl",
+ "bsdl": "bsdl",
+ # Bpftrace
+ "bt": "bpftrace",
+ # C3
+ "c3": "c3",
+ "c3i": "c3",
+ "c3t": "c3",
+ # Cairo
+ "cairo": "cairo",
+ # Cap'n Proto
+ "capnp": "capnp",
+ # Common Package Specification
+ "cps": "json",
+ # C#
+ "cs": "cs",
+ "csx": "cs",
+ "cake": "cs",
+ # CSDL
+ "csdl": "csdl",
+ # Ctags
+ "ctags": "conf",
+ # Cabal
+ "cabal": "cabal",
+ # Cedar
+ "cedar": "cedar",
+ # ChaiScript
+ "chai": "chaiscript",
+ # Chatito
+ "chatito": "chatito",
+ # Chuck
+ "ck": "chuck",
+ # Comshare Dimension Definition Language
+ "cdl": "cdl",
+ # Conary Recipe
+ "recipe": "conaryrecipe",
+ # Corn config file
+ "corn": "corn",
+ # ChainPack Object Notation (CPON)
+ "cpon": "cpon",
+ # Controllable Regex Mutilator
+ "crm": "crm",
+ # Cyn++
+ "cyn": "cynpp",
+ # Cypher query language
+ "cypher": "cypher",
+ # C++
+ "cxx": "cpp",
+ "c++": "cpp",
+ "hh": "cpp",
+ "hxx": "cpp",
+ "hpp": "cpp",
+ "ipp": "cpp",
+ "moc": "cpp",
+ "tcc": "cpp",
+ "inl": "cpp",
+ # MS files (ixx: C++ module interface file, Microsoft Project file)
+ "ixx": "cpp",
+ "mpp": "cpp",
+ # C++ 20 modules (clang)
+ # https://clang.llvm.org/docs/StandardCPlusPlusModules.html#file-name-requirement
+ "cppm": "cpp",
+ "ccm": "cpp",
+ "cxxm": "cpp",
+ "c++m": "cpp",
+ # Ch (CHscript)
+ "chf": "ch",
+ # TLH files are C++ headers generated by Visual C++'s #import from typelibs
+ "tlh": "cpp",
+ # Cascading Style Sheets
+ "css": "css",
+ # Common Expression Language (CEL) - https://cel.dev
+ "cel": "cel",
+ # Century Term Command Scripts (*.cmd too)
+ "con": "cterm",
+ # ChordPro
+ "chopro": "chordpro",
+ "crd": "chordpro",
+ "cho": "chordpro",
+ "crdpro": "chordpro",
+ "chordpro": "chordpro",
+ # Clean
+ "dcl": "clean",
+ "icl": "clean",
+ # Clever
+ "eni": "cl",
+ # Clojure
+ "clj": "clojure",
+ "cljs": "clojure",
+ "cljx": "clojure",
+ "cljc": "clojure",
+ # Cobol
+ "cbl": "cobol",
+ "cob": "cobol",
+ # Coco/R
+ "atg": "coco",
+ # Cold Fusion
+ "cfm": "cf",
+ "cfi": "cf",
+ "cfc": "cf",
+ # Cooklang
+ "cook": "cook",
+ # Clinical Quality Language (CQL)
+ # .cql is also mentioned as the 'XDCC Catcher queue list' file extension.
+ # If support for XDCC Catcher is needed in the future, the contents of the file
+ # needs to be inspected.
+ "cql": "cqlang",
+ # Crystal
+ "cr": "crystal",
+ # CSV Files
+ "csv": "csv",
+ # Concertor
+ "cto": "concerto",
+ # CUDA Compute Unified Device Architecture
+ "cu": "cuda",
+ "cuh": "cuda",
+ # Cue
+ "cue": "cue",
+ # DAX
+ "dax": "dax",
+ # WildPackets EtherPeek Decoder
+ "dcd": "dcd",
+ # Elvish
+ "elv": "elvish",
+ # Faust
+ "lib": "faust",
+ # Fennel
+ "fnl": "fennel",
+ "fnlm": "fennel",
+ # Libreoffice config files
+ "xcu": "xml",
+ "xlb": "xml",
+ "xlc": "xml",
+ "xba": "xml",
+ # Libtool files
+ "lo": "sh",
+ "la": "sh",
+ "lai": "sh",
+ # LyRiCs
+ "lrc": "lyrics",
+ # MLIR
+ "mlir": "mlir",
+ # Quake C
+ "qc": "c",
+ # Cucumber
+ "feature": "cucumber",
+ # Communicating Sequential Processes
+ "csp": "csp",
+ "fdr": "csp",
+ # CUPL logic description and simulation
+ "pld": "cupl",
+ "si": "cuplsim",
+ # Dafny
+ "dfy": "dafny",
+ # Dart
+ "dart": "dart",
+ "drt": "dart",
+ # Dhall
+ "dhall": "dhall",
+ # ROCKLinux package description
+ "desc": "desc",
+ # Desktop files
+ "desktop": "desktop",
+ "directory": "desktop",
+ # Diff files
+ "diff": "diff",
+ "rej": "diff",
+ # Djot
+ "dj": "djot",
+ "djot": "djot",
+ # DOT
+ "dot": "dot",
+ "gv": "dot",
+ # Dylan - lid files
+ "lid": "dylanlid",
+ # Dylan - intr files (melange)
+ "intr": "dylanintr",
+ # Dylan
+ "dylan": "dylan",
+ # Dracula
+ "drac": "dracula",
+ "drc": "dracula",
+ "lvs": "dracula",
+ "lpe": "dracula",
+ # Datascript
+ "ds": "datascript",
+ # DTD (Document Type Definition for XML)
+ "dtd": "dtd",
+ # Devicetree (.its for U-Boot Flattened Image Trees, .keymap for ZMK keymap, and
+ # .overlay for Zephyr overlay)
+ "dts": "dts",
+ "dtsi": "dts",
+ "dtso": "dts",
+ "its": "dts",
+ "keymap": "dts",
+ "overlay": "dts",
+ # Embedix Component Description
+ "ecd": "ecd",
+ # ERicsson LANGuage; Yaws is erlang too
+ "erl": "erlang",
+ "hrl": "erlang",
+ "yaws": "erlang",
+ # Elm
+ "elm": "elm",
+ # Elsa - https://github.com/ucsd-progsys/elsa
+ "lc": "elsa",
+ # EdgeDB Schema Definition Language
+ "esdl": "esdl",
+ # ESQL-C
+ "ec": "esqlc",
+ "EC": "esqlc",
+ # Esterel
+ "strl": "esterel",
+ # Essbase script
+ "csc": "csc",
+ # Expect
+ "exp": "expect",
+ # Falcon
+ "fal": "falcon",
+ # Fantom
+ "fan": "fan",
+ "fwt": "fan",
+ # Factor
+ "factor": "factor",
+ # FGA
+ "fga": "fga",
+ # FIRRTL - Flexible Internal Representation for RTL
+ "fir": "firrtl",
+ # Fish shell
+ "fish": "fish",
+ # Flix
+ "flix": "flix",
+ # Fluent
+ "ftl": "fluent",
+ # Focus Executable
+ "fex": "focexec",
+ "focexec": "focexec",
+ # Focus Master file (but not for auto.master)
+ "mas": "master",
+ "master": "master",
+ # Forth
+ "ft": "forth",
+ "fth": "forth",
+ "4th": "forth",
+ # Reva Forth
+ "frt": "reva",
+ # Framescript
+ "fsl": "framescript",
+ # Func
+ "fc": "func",
+ # Fusion
+ "fusion": "fusion",
+ # FHIR Shorthand (FSH)
+ "fsh": "fsh",
+ # F#
+ "fsi": "fsharp",
+ "fsx": "fsharp",
+ # GDMO
+ "mo": "gdmo",
+ "gdmo": "gdmo",
+ # GDscript
+ "gd": "gdscript",
+ # Godot resource
+ "tscn": "gdresource",
+ "tres": "gdresource",
+ # Godot shader
+ "gdshader": "gdshader",
+ "shader": "gdshader",
+ # Gemtext
+ "gmi": "gemtext",
+ "gemini": "gemtext",
+ # Gift (Moodle)
+ "gift": "gift",
+ # Gleam
+ "gleam": "gleam",
+ # GLSL
+ # Extensions supported by Khronos reference compiler (with one exception, ".glsl")
+ # https://github.com/KhronosGroup/glslang
+ "vert": "glsl",
+ "tesc": "glsl",
+ "tese": "glsl",
+ "glsl": "glsl",
+ "geom": "glsl",
+ "frag": "glsl",
+ "comp": "glsl",
+ "rgen": "glsl",
+ "rmiss": "glsl",
+ "rchit": "glsl",
+ "rahit": "glsl",
+ "rint": "glsl",
+ "rcall": "glsl",
+ # GN (generate ninja) files
+ "gn": "gn",
+ "gni": "gn",
+ # Glimmer-flavored TypeScript and JavaScript
+ "gts": "typescript.glimmer",
+ "gjs": "javascript.glimmer",
+ # Go (Google)
+ "go": "go",
+ # GrADS scripts
+ "gs": "grads",
+ # GraphQL
+ "graphql": "graphql",
+ "graphqls": "graphql",
+ "gql": "graphql",
+ # Gretl
+ "gretl": "gretl",
+ # GNU Server Pages
+ "gsp": "gsp",
+ # GYP
+ "gyp": "gyp",
+ "gypi": "gyp",
+ # Hack
+ "hack": "hack",
+ "hackpartial": "hack",
+ # Haml
+ "haml": "haml",
+ # Hamster Classic | Playground files
+ "hsm": "hamster",
+ # Handlebars
+ "hbs": "handlebars",
+ # Hare
+ "ha": "hare",
+ # Haskell
+ "hs": "haskell",
+ "hsc": "haskell",
+ "hs-boot": "haskell",
+ "hsig": "haskell",
+ "lhs": "lhaskell",
+ "chs": "chaskell",
+ # Haste
+ "ht": "haste",
+ "htpp": "hastepreproc",
+ # Haxe
+ "hx": "haxe",
+ # HCL
+ "hcl": "hcl",
+ # Hercules
+ "vc": "hercules",
+ "ev": "hercules",
+ "sum": "hercules",
+ "errsum": "hercules",
+ # HEEx
+ "heex": "heex",
+ # HEX (Intel)
+ "hex": "hex",
+ "ihex": "hex",
+ "int": "hex",
+ "ihe": "hex",
+ "ihx": "hex",
+ "mcs": "hex",
+ "h32": "hex",
+ "h80": "hex",
+ "h86": "hex",
+ "a43": "hex",
+ "a90": "hex",
+ # Hjson
+ "hjson": "hjson",
+ # HLS Playlist (or another form of playlist)
+ "m3u": "hlsplaylist",
+ "m3u8": "hlsplaylist",
+ # Hollywood
+ "hws": "hollywood",
+ # Hoon
+ "hoon": "hoon",
+ # TI Code Composer Studio General Extension Language
+ "gel": "gel",
+ # HTTP request files
+ "http": "http",
+ # HTML with Ruby - eRuby
+ "erb": "eruby",
+ "rhtml": "eruby",
+ # Some template. Used to be HTML Cheetah.
+ "tmpl": "template",
+ # Hurl
+ "hurl": "hurl",
+ # Hylo
+ "hylo": "hylo",
+ # Hyper Builder
+ "hb": "hb",
+ # Httest
+ "htt": "httest",
+ "htb": "httest",
+ # Icon
+ "icn": "icon",
+ # Microsoft IDL (Interface Description Language) Also *.idl
+ # MOF = WMI (Windows Management Instrumentation) Managed Object Format
+ "odl": "msidl",
+ "mof": "msidl",
+ # Idris2
+ "idr": "idris2",
+ "lidr": "lidris2",
+ # Inform
+ "inf": "inform",
+ "INF": "inform",
+ # Ipkg for Idris 2 language
+ "ipkg": "ipkg",
+ # Informix 4GL (source - canonical, include file, I4GL+M4 preproc.)
+ "4gl": "fgl",
+ "4gh": "fgl",
+ "m4gl": "fgl",
+ # .INI file for MSDOS
+ "ini": "dosini",
+ "INI": "dosini",
+ # Inko
+ "inko": "inko",
+ # Inno Setup
+ "iss": "iss",
+ # J
+ "ijs": "j",
+ # JAL
+ "jal": "jal",
+ "JAL": "jal",
+ # Jam
+ "jpl": "jam",
+ "jpr": "jam",
+ # Janet
+ "janet": "janet",
+ # Java
+ "java": "java",
+ "jav": "java",
+ "jsh": "java",
+ # JavaCC
+ "jj": "javacc",
+ "jjt": "javacc",
+ # JavaScript, ECMAScript, ES module script, CommonJS script
+ "js": "javascript",
+ "jsm": "javascript",
+ "javascript": "javascript",
+ "es": "javascript",
+ "mjs": "javascript",
+ "cjs": "javascript",
+ # JavaScript with React
+ "jsx": "javascriptreact",
+ # Java Server Pages
+ "jsp": "jsp",
+ # Jess
+ "clp": "jess",
+ # Jgraph
+ "jgr": "jgraph",
+ # Jinja
+ "jinja": "jinja",
+ # Jujutsu
+ "jjdescription": "jjdescription",
+ # Jovial
+ "jov": "jovial",
+ "j73": "jovial",
+ "jovial": "jovial",
+ # Jq
+ "jq": "jq",
+ # JSON5
+ "json5": "json5",
+ # JSON Patch (RFC 6902)
+ "json-patch": "json",
+ # Geojson is also json
+ "geojson": "json",
+ # Jupyter Notebook and jupyterlab config is also json
+ "ipynb": "json",
+ "jupyterlab-settings": "json",
+ # Sublime config
+ "sublime-project": "json",
+ "sublime-settings": "json",
+ "sublime-workspace": "json",
+ # JSON
+ "json": "json",
+ "jsonp": "json",
+ "webmanifest": "json",
+ # JSON Lines
+ "jsonl": "jsonl",
+ # Jsonnet
+ "jsonnet": "jsonnet",
+ "libsonnet": "jsonnet",
+ # Julia
+ "jl": "julia",
+ # KAREL
+ "kl": "karel",
+ "KL": "karel",
+ # KDL
+ "kdl": "kdl",
+ # KerML
+ "kerml": "kerml",
+ # Kixtart
+ "kix": "kix",
+ # Kimwitu[++]
+ "k": "kwt",
+ # Kivy
+ "kv": "kivy",
+ # Koka
+ "kk": "koka",
+ # Kos
+ "kos": "kos",
+ # Kotlin
+ "kt": "kotlin",
+ "ktm": "kotlin",
+ "kts": "kotlin",
+ # KDE script
+ "ks": "kscript",
+ # Kyaml
+ "kyaml": "yaml",
+ "kyml": "yaml",
+ # Lace (ISE)
+ "ace": "lace",
+ "ACE": "lace",
+ # Latte
+ "latte": "latte",
+ "lte": "latte",
+ # LDAP LDIF
+ "ldif": "ldif",
+ # Lean
+ "lean": "lean",
+ # Ledger
+ "ldg": "ledger",
+ "ledger": "ledger",
+ "journal": "ledger",
+ # Leex
+ "xrl": "leex",
+ # Leo
+ "leo": "leo",
+ # Less
+ "less": "less",
+ # Lex
+ "lex": "lex",
+ "l": "lex",
+ "lxx": "lex",
+ "l++": "lex",
+ # Lilypond
+ "ly": "lilypond",
+ "ily": "lilypond",
+ # Liquidsoap
+ "liq": "liquidsoap",
+ # Liquid
+ "liquid": "liquid",
+ # Lite
+ "lite": "lite",
+ "lt": "lite",
+ # Livebook
+ "livemd": "livebook",
+ # Logtalk
+ "lgt": "logtalk",
+ # LOTOS
+ "lotos": "lotos",
+ # Lout (also: *.lt)
+ "lou": "lout",
+ "lout": "lout",
+ # Luau
+ "luau": "luau",
+ # Lynx style file (or LotusScript!)
+ "lss": "lss",
+ # MaGic Point
+ "mgp": "mgp",
+ # MakeIndex
+ "ist": "ist",
+ "mst": "ist",
+ # Mallard
+ "page": "mallard",
+ # Manpage
+ "man": "man",
+ # Maple V
+ "mv": "maple",
+ "mpl": "maple",
+ "mws": "maple",
+ # Mason (it used to include *.comp, are those Mason files?)
+ "mason": "mason",
+ "mhtml": "mason",
+ # Mathematica notebook and package files
+ "nb": "mma",
+ "wl": "mma",
+ # Maya Extension Language
+ "mel": "mel",
+ # mcmeta
+ "mcmeta": "json",
+ # MediaWiki
+ "mw": "mediawiki",
+ "wiki": "mediawiki",
+ # Mermaid
+ "mmd": "mermaid",
+ "mmdc": "mermaid",
+ "mermaid": "mermaid",
+ # Meson Build system config
+ "wrap": "dosini",
+ # Metafont
+ "mf": "mf",
+ # MetaPost
+ "mp": "mp",
+ # MGL
+ "mgl": "mgl",
+ # MIX - Knuth assembly
+ "mix": "mix",
+ "mixal": "mix",
+ # Symbian meta-makefile definition (MMP)
+ "mmp": "mmp",
+ # Larch/Modula-3
+ "lm3": "modula3",
+ # Monk
+ "isc": "monk",
+ "monk": "monk",
+ "ssc": "monk",
+ "tsc": "monk",
+ # MOO
+ "moo": "moo",
+ # Moonscript
+ "moon": "moonscript",
+ # Move language
+ "move": "move",
+ # MPD is based on XML
+ "mpd": "xml",
+ # Motorola S record
+ "s19": "srec",
+ "s28": "srec",
+ "s37": "srec",
+ "mot": "srec",
+ "srec": "srec",
+ # Msql
+ "msql": "msql",
+ # MuPAD source
+ "mu": "mupad",
+ # Mush
+ "mush": "mush",
+ # Mustache
+ "mustache": "mustache",
+ # N1QL
+ "n1ql": "n1ql",
+ "nql": "n1ql",
+ # Neon
+ "neon": "neon",
+ # NetLinx
+ "axs": "netlinx",
+ "axi": "netlinx",
+ # Nickel
+ "ncl": "nickel",
+ # Nim file
+ "nim": "nim",
+ "nims": "nim",
+ "nimble": "nim",
+ # Ninja file
+ "ninja": "ninja",
+ # Nix
+ "nix": "nix",
+ # Norg
+ "norg": "norg",
+ # Novell netware batch files
+ "ncf": "ncf",
+ # N-Quads
+ "nq": "nq",
+ # Not Quite C
+ "nqc": "nqc",
+ # NSE - Nmap Script Engine - uses Lua syntax
+ "nse": "lua",
+ # NSIS
+ "nsi": "nsis",
+ "nsh": "nsis",
+ # N-Triples
+ "nt": "ntriples",
+ # Nu
+ "nu": "nu",
+ # Numbat
+ "nbt": "numbat",
+ # Oblivion Language and Oblivion Script Extender
+ "obl": "obse",
+ "obse": "obse",
+ "oblivion": "obse",
+ "obscript": "obse",
+ # Objdump
+ "objdump": "objdump",
+ "cppobjdump": "objdump",
+ # Occam
+ "occ": "occam",
+ # Odin
+ "odin": "odin",
+ # Omnimark
+ "xom": "omnimark",
+ "xin": "omnimark",
+ # OpenROAD
+ "or": "openroad",
+ # OpenSCAD
+ "scad": "openscad",
+ # Oracle config file
+ "ora": "ora",
+ # Org (Emacs' org-mode)
+ "org": "org",
+ "org_archive": "org",
+ # PApp
+ "papp": "papp",
+ "pxml": "papp",
+ "pxsl": "papp",
+ # Pascal (also *.p, *.pp, *.inc)
+ "pas": "pascal",
+ # Delphi
+ "dpr": "pascal",
+ # Free Pascal makefile definition file
+ "fpc": "fpcmake",
+ # Path of Exile item filter
+ "filter": "poefilter",
+ # PDF
+ "pdf": "pdf",
+ # PCMK - HAE - crm configure edit
+ "pcmk": "pcmk",
+ # PEM (Privacy-Enhanced Mail)
+ "pem": "pem",
+ "cer": "pem",
+ "crt": "pem",
+ "csr": "pem",
+ # Perl POD
+ "pod": "pod",
+ # Pike and Cmod
+ "pike": "pike",
+ "pmod": "pike",
+ "cmod": "cmod",
+ # Palm Resource compiler
+ "rcp": "pilrc",
+ # Pip requirements
+ "pip": "requirements",
+ # PL/1, PL/I
+ "pli": "pli",
+ "pl1": "pli",
+ # PL/M (also: *.inp)
+ "plm": "plm",
+ "p36": "plm",
+ "pac": "plm",
+ # PL/SQL
+ "pls": "plsql",
+ "plsql": "plsql",
+ # PLP
+ "plp": "plp",
+ # PO and PO template (GNU gettext)
+ "po": "po",
+ "pot": "po",
+ # Pony
+ "pony": "pony",
+ # PostScript (+ font files, encapsulated PostScript, Adobe Illustrator)
+ "ps": "postscr",
+ "pfa": "postscr",
+ "afm": "postscr",
+ "eps": "postscr",
+ "epsf": "postscr",
+ "epsi": "postscr",
+ "ai": "postscr",
+ # PostScript Printer Description
+ "ppd": "ppd",
+ # Povray
+ "pov": "pov",
+ # Power Query M
+ "pq": "pq",
+ # Prisma
+ "prisma": "prisma",
+ # PPWizard
+ "it": "ppwiz",
+ "ih": "ppwiz",
+ # Pug
+ "pug": "pug",
+ # Embedded Puppet
+ "epp": "epuppet",
+ # Obj 3D file format
+ # TODO: is there a way to avoid MS-Windows Object files?
+ "obj": "obj",
+ # Oracle Pro*C/C++
+ "pc": "proc",
+ # Privoxy actions file
+ "action": "privoxy",
+ # Software Distributor Product Specification File (POSIX 1387.2-1995)
+ "psf": "psf",
+ # Prolog
+ "pdb": "prolog",
+ # Promela
+ "pml": "promela",
+ # Property Specification Language (PSL)
+ "psl": "psl",
+ # Google protocol buffers
+ "proto": "proto",
+ "txtpb": "pbtxt",
+ "textproto": "pbtxt",
+ "textpb": "pbtxt",
+ "pbtxt": "pbtxt",
+ "aconfig": "pbtxt", # Android aconfig files
+ # Poke
+ "pk": "poke",
+ # Nvidia PTX (Parallel Thread Execution)
+ # See https://docs.nvidia.com/cuda/parallel-thread-execution/
+ "ptx": "ptx",
+ # Purescript
+ "purs": "purescript",
+ # Pyret
+ "arr": "pyret",
+ # Pyrex/Cython
+ "pyx": "pyrex",
+ "pyx+": "pyrex",
+ "pxd": "pyrex",
+ "pxi": "pyrex",
+ # QL
+ "ql": "ql",
+ "qll": "ql",
+ # QML
+ "qml": "qml",
+ "qbs": "qml",
+ # Quarto
+ "qmd": "quarto",
+ # QuickBms
+ "bms": "quickbms",
+ # Racket (formerly detected as "scheme")
+ "rkt": "racket",
+ "rktd": "racket",
+ "rktl": "racket",
+ # Radiance
+ "rad": "radiance",
+ "mat": "radiance",
+ # Raku (formerly Perl6)
+ "pm6": "raku",
+ "p6": "raku",
+ "t6": "raku",
+ "pod6": "raku",
+ "raku": "raku",
+ "rakumod": "raku",
+ "rakudoc": "raku",
+ "rakutest": "raku",
+ # Razor
+ "cshtml": "razor",
+ "razor": "razor",
+ # Renderman Interface Bytestream
+ "rib": "rib",
+ # Rego Policy Language
+ "rego": "rego",
+ # Rexx
+ "rex": "rexx",
+ "orx": "rexx",
+ "rxo": "rexx",
+ "rxj": "rexx",
+ "jrexx": "rexx",
+ "rexxj": "rexx",
+ "rexx": "rexx",
+ "testGroup": "rexx",
+ "testUnit": "rexx",
+ # RSS looks like XML
+ "rss": "xml",
+ # ReScript
+ "res": "rescript",
+ "resi": "rescript",
+ # Relax NG Compact
+ "rnc": "rnc",
+ # Relax NG XML
+ "rng": "rng",
+ # ILE RPG
+ "rpgle": "rpgle",
+ "rpgleinc": "rpgle",
+ # RPL/2
+ "rpl": "rpl",
+ # Robot Framework
+ "robot": "robot",
+ "resource": "robot",
+ # Roc
+ "roc": "roc",
+ # RON (Rusty Object Notation)
+ "ron": "ron",
+ # MikroTik RouterOS script
+ "rsc": "routeros",
+ # Rpcgen
+ "x": "rpcgen",
+ # reStructuredText Documentation Format
+ "rst": "rst",
+ # RTF
+ "rtf": "rtf",
+ # Ruby
+ "rb": "ruby",
+ "rbw": "ruby",
+ # RubyGems
+ "gemspec": "ruby",
+ # RBS (Ruby Signature)
+ "rbs": "rbs",
+ # Rackup
+ "ru": "ruby",
+ # Ruby on Rails
+ "builder": "ruby",
+ "rxml": "ruby",
+ "rjs": "ruby",
+ # Sorbet (Ruby typechecker)
+ "rbi": "ruby",
+ # Rust
+ "rs": "rust",
+ # S-lang
+ "sl": "slang",
+ # Sage
+ "sage": "sage",
+ # SAS script
+ "sas": "sas",
+ # Sass
+ "sass": "sass",
+ # Scala
+ "scala": "scala",
+ "mill": "scala",
+ # SBT - Scala Build Tool
+ "sbt": "sbt",
+ # Slang Shading Language
+ "slang": "shaderslang",
+ # Slint
+ "slint": "slint",
+ # Scilab
+ "sci": "scilab",
+ "sce": "scilab",
+ # SCSS
+ "scss": "scss",
+ # SD: Streaming Descriptors
+ "sd": "sd",
+ # SDL
+ "sdl": "sdl",
+ "pr": "sdl",
+ # sed
+ "sed": "sed",
+ # SubRip
+ "srt": "srt",
+ # SubStation Alpha
+ "ass": "ssa",
+ "ssa": "ssa",
+ # svelte
+ "svelte": "svelte",
+ # Sieve (RFC 3028, 5228)
+ "siv": "sieve",
+ "sieve": "sieve",
+ # TriG
+ "trig": "trig",
+ # Zig and Zig Object Notation (ZON)
+ "zig": "zig",
+ "zon": "zig",
+ # Ziggy and Ziggy Schema
+ "ziggy": "ziggy",
+ "ziggy-schema": "ziggy_schema",
+ # Zserio
+ "zs": "zserio",
+ # Salt state files
+ "sls": "salt",
+ # Sexplib
+ "sexp": "sexplib",
+ # Simula
+ "sim": "simula",
+ # SINDA
+ "sin": "sinda",
+ "s85": "sinda",
+ # SiSU
+ "sst": "sisu",
+ "ssm": "sisu",
+ "ssi": "sisu",
+ "-sst": "sisu",
+ "_sst": "sisu",
+ # SKILL
+ "il": "skill",
+ "ils": "skill",
+ "cdf": "skill",
+ # Cadence
+ "cdc": "cdc",
+ # Cangjie
+ "cj": "cangjie",
+ # SLRN
+ "score": "slrnsc",
+ # Smali
+ "smali": "smali",
+ # Smalltalk
+ "st": "st",
+ # Smarty templates
+ "tpl": "smarty",
+ # SMITH
+ "smt": "smith",
+ "smith": "smith",
+ # Smithy
+ "smithy": "smithy",
+ # Snobol4 and spitbol
+ "sno": "snobol4",
+ "spt": "snobol4",
+ # SNMP MIB files
+ "mib": "mib",
+ "my": "mib",
+ # Solidity
+ "sol": "solidity",
+ # SPARQL queries
+ "rq": "sparql",
+ "sparql": "sparql",
+ # Spec (Linux RPM)
+ "spec": "spec",
+ # Speedup (AspenTech plant simulator)
+ "speedup": "spup",
+ "spdata": "spup",
+ "spd": "spup",
+ # Slice
+ "ice": "slice",
+ # Microsoft Visual Studio Solution
+ "sln": "solution",
+ "slnf": "json",
+ "slnx": "xml",
+ # Spice
+ "sp": "spice",
+ "spice": "spice",
+ # Spyce
+ "spy": "spyce",
+ "spi": "spyce",
+ # SQL for Oracle Designer
+ "tyb": "sql",
+ "tyc": "sql",
+ "pkb": "sql",
+ "pks": "sql",
+ # SQLJ
+ "sqlj": "sqlj",
+ # PRQL
+ "prql": "prql",
+ # SQR
+ "sqr": "sqr",
+ "sqi": "sqr",
+ # Squirrel
+ "nut": "squirrel",
+ # Starlark
+ "ipd": "starlark",
+ "sky": "starlark",
+ "star": "starlark",
+ "starlark": "starlark",
+ # OpenVPN configuration
+ "ovpn": "openvpn",
+ # Stata
+ "ado": "stata",
+ "do": "stata",
+ "imata": "stata",
+ "mata": "stata",
+ # SMCL
+ "hlp": "smcl",
+ "ihlp": "smcl",
+ "smcl": "smcl",
+ # Soy
+ "soy": "soy",
+ # Stored Procedures
+ "stp": "stp",
+ # Standard ML
+ "sml": "sml",
+ # Sratus VOS command macro
+ "cm": "voscm",
+ # Sway (programming language)
+ "sw": "sway",
+ # Swift
+ "swift": "swift",
+ "swiftinterface": "swift",
+ # Swig
+ "swg": "swig",
+ "swig": "swig",
+ # Synopsys Design Constraints
+ "sdc": "sdc",
+ # SVG (Scalable Vector Graphics)
+ "svg": "svg",
+ # Surface
+ "sface": "surface",
+ # SysML
+ "sysml": "sysml",
+ # LLVM TableGen
+ "td": "tablegen",
+ # TAK
+ "tak": "tak",
+ # Unx Tal
+ "tal": "tal",
+ # templ
+ "templ": "templ",
+ # Teal
+ "tl": "teal",
+ # TealInfo
+ "tli": "tli",
+ # Telix Salt
+ "slt": "tsalt",
+ # Terminfo
+ "ti": "terminfo",
+ # Tera
+ "tera": "tera",
+ # Terraform variables
+ "tfvars": "terraform-vars",
+ # TeX
+ "latex": "tex",
+ "sty": "tex",
+ "dtx": "tex",
+ "ltx": "tex",
+ "bbl": "tex",
+ # LaTeX files generated by Inkscape
+ "pdf_tex": "tex",
+ # ConTeXt
+ "mkii": "context",
+ "mkiv": "context",
+ "mkvi": "context",
+ "mkxl": "context",
+ "mklx": "context",
+ # Texinfo
+ "texinfo": "texinfo",
+ "texi": "texinfo",
+ "txi": "texinfo",
+ # Thrift (Apache)
+ "thrift": "thrift",
+ # Tiger
+ "tig": "tiger",
+ # TLA+
+ "tla": "tla",
+ # TPP - Text Presentation Program
+ "tpp": "tpp",
+ # TRACE32 Script Language
+ "cmm": "trace32",
+ "cmmt": "trace32",
+ "t32": "trace32",
+ # Treetop
+ "treetop": "treetop",
+ # TSS - Geometry
+ "tssgm": "tssgm",
+ # TSS - Optics
+ "tssop": "tssop",
+ # TSS - Command Line (temporary)
+ "tsscl": "tsscl",
+ # TSV Files
+ "tsv": "tsv",
+ # Tutor mode
+ "tutor": "tutor",
+ # TWIG files
+ "twig": "twig",
+ # TypeScript module and common
+ "mts": "typescript",
+ "cts": "typescript",
+ # TypeScript with React
+ "tsx": "typescriptreact",
+ # TypeSpec files
+ "tsp": "typespec",
+ # Motif UIT/UIL files
+ "uit": "uil",
+ "uil": "uil",
+ # Ungrammar, AKA Un-grammar
+ "ungram": "ungrammar",
+ # UnrealScript
+ "uc": "uc",
+ # URL shortcut
+ "url": "urlshortcut",
+ # V
+ "vsh": "v",
+ "vv": "v",
+ # Vala
+ "vala": "vala",
+ # VDF
+ "vdf": "vdf",
+ # VDM
+ "vdmpp": "vdmpp",
+ "vpp": "vdmpp",
+ "vdmrt": "vdmrt",
+ "vdmsl": "vdmsl",
+ "vdm": "vdmsl",
+ # Vento
+ "vto": "vento",
+ # Vera
+ "vr": "vera",
+ "vri": "vera",
+ "vrh": "vera",
+ # Verilog-AMS HDL
+ "va": "verilogams",
+ "vams": "verilogams",
+ # SystemVerilog
+ "sv": "systemverilog",
+ "svh": "systemverilog",
+ # VHS tape
+ # .tape is also used by TapeCalc, which we do not support ATM. If TapeCalc
+ # support is needed the contents of the file needs to be inspected.
+ "tape": "vhs",
+ # VHDL
+ "hdl": "vhdl",
+ "vhd": "vhdl",
+ "vhdl": "vhdl",
+ "vbe": "vhdl",
+ "vst": "vhdl",
+ "vho": "vhdl",
+ # Visual Basic
+ # user control, ActiveX document form, active designer, property page
+ "ctl": "vb",
+ "dob": "vb",
+ "dsr": "vb",
+ "pag": "vb",
+ # Visual Basic Project
+ "vbp": "dosini",
+ # VBScript (close to Visual Basic)
+ "vbs": "vb",
+ # Visual Basic .NET (close to Visual Basic)
+ "vb": "vb",
+ # Visual Studio Macro
+ "dsm": "vb",
+ # SaxBasic (close to Visual Basic)
+ "sba": "vb",
+ # VRML V1.0c
+ "wrl": "vrml",
+ # Vroom (mnv testing and executable documentation)
+ "vroom": "vroom",
+ # Vue.js Single File Component
+ "vue": "vue",
+ # WebAssembly
+ "wat": "wat",
+ "wast": "wat",
+ # WebAssembly Interface Type (WIT)
+ "wit": "wit",
+ # Webmacro
+ "wm": "webmacro",
+ # WebGPU Shading Language (WGSL)
+ "wgsl": "wgsl",
+ # Website MetaLanguage
+ "wml": "wml",
+ # Winbatch
+ "wbt": "winbatch",
+ # WSML
+ "wsml": "wsml",
+ # WPL
+ "wpl": "xml",
+ # XHTML
+ "xhtml": "xhtml",
+ "xht": "xhtml",
+ # Xilinx Vivado/Vitis project files and block design files
+ "xpr": "xml",
+ "xpfm": "xml",
+ "spfm": "xml",
+ "bxml": "xml",
+ "mmi": "xml",
+ "bd": "json",
+ "bda": "json",
+ "xci": "json",
+ "mss": "mss",
+ # XS Perl extension interface language
+ "xs": "xs",
+ # Xmath
+ "msc": "xmath",
+ "msf": "xmath",
+ # XMI (holding UML models) is also XML
+ "xmi": "xml",
+ # Unison Language
+ "u": "unison",
+ "uu": "unison",
+ # Qt Linguist translation source and Qt User Interface Files are XML
+ # However, for .ts TypeScript is more common.
+ "ui": "xml",
+ # TPM's are RDF-based descriptions of TeX packages (Nikolai Weibull)
+ "tpm": "xml",
+ # Web Services Description Language (WSDL)
+ "wsdl": "xml",
+ # Workflow Description Language (WDL)
+ "wdl": "wdl",
+ # XLIFF (XML Localisation Interchange File Format) is also XML
+ "xlf": "xml",
+ "xliff": "xml",
+ # XML User Interface Language
+ "xul": "xml",
+ # Xquery
+ "xq": "xquery",
+ "xql": "xquery",
+ "xqm": "xquery",
+ "xquery": "xquery",
+ "xqy": "xquery",
+ # XSD
+ "xsd": "xsd",
+ # Xslt
+ "xsl": "xslt",
+ "xslt": "xslt",
+ # Yacc
+ "yy": "yacc",
+ "yxx": "yacc",
+ "y++": "yacc",
+ # Yaml
+ "yaml": "yaml",
+ "yml": "yaml",
+ "eyaml": "yaml",
+ # Raml
+ "raml": "raml",
+ # YANG
+ "yang": "yang",
+ # YARA, YARA-X
+ "yara": "yara",
+ "yar": "yara",
+ # Yuck
+ "yuck": "yuck",
+ # Zimbu
+ "zu": "zimbu",
+ # Zimbu Templates
+ "zut": "zimbutempl",
+ # Z80 assembler asz80
+ "z8a": "z8a",
+ # Stylus
+ "styl": "stylus",
+ "stylus": "stylus",
+ # Universal Scene Description
+ "usda": "usd",
+ "usd": "usd",
+ # Rofi stylesheet
+ "rasi": "rasi",
+ "rasinc": "rasi",
+ # Zsh module
+ # mdd: https://github.com/zsh-users/zsh/blob/57248b88830ce56adc243a40c7773fb3825cab34/Etc/zsh-development-guide#L285-L288
+ # mdh, pro: https://github.com/zsh-users/zsh/blob/57248b88830ce56adc243a40c7773fb3825cab34/Etc/zsh-development-guide#L268-L271
+ # *.mdd will generate *.mdh, *.pro and *.epro.
+ # module's *.c will #include *.mdh containing module dependency information and
+ # *.pro containing all static declarations of *.c
+ # *.epro contains all external declarations of *.c
+ "mdh": "c",
+ "epro": "c",
+ "mdd": "sh",
+ # Blueprint markup files
+ "blp": "blueprint",
+ # Blueprint build system file
+ "bp": "bp",
+ # Tiltfile
+ "Tiltfile": "tiltfile",
+ "tiltfile": "tiltfile"
+}
+# Key: file name (the final path component, excluding the drive and root)
+# Value: filetype
+const ft_from_name = {
+ # Ant
+ "build.xml": "ant",
+ # Ash of busybox
+ ".ash_history": "sh",
+ # Automake (must be before the *.am pattern)
+ "makefile.am": "automake",
+ "Makefile.am": "automake",
+ "GNUmakefile.am": "automake",
+ # APT config file
+ "apt.conf": "aptconf",
+ # BIND zone
+ "named.root": "bindzone",
+ # Brewfile (uses Ruby syntax)
+ "Brewfile": "ruby",
+ # Busted (Lua unit testing framework - configuration files)
+ ".busted": "lua",
+ # Bun history
+ ".bun_repl_history": "javascript",
+ # Calendar
+ "calendar": "calendar",
+ # Cgdb config file
+ "cgdbrc": "cgdbrc",
+ # Cfengine
+ "cfengine.conf": "cfengine",
+ # Chktex
+ ".chktexrc": "conf",
+ # Codeowners
+ "CODEOWNERS": "codeowners",
+ # Clangd
+ ".clangd": "yaml",
+ # Conda configuration file
+ ".condarc": "yaml",
+ "condarc": "yaml",
+ # Cling
+ ".cling_history": "cpp",
+ # CmakeCache
+ "CMakeCache.txt": "cmakecache",
+ # Configure scripts
+ "configure.in": "config",
+ "configure.ac": "config",
+ # Debian devscripts
+ "devscripts.conf": "sh",
+ ".devscripts": "sh",
+ # Fontconfig config files
+ "fonts.conf": "xml",
+ # Libreoffice config files
+ "psprint.conf": "dosini",
+ "sofficerc": "dosini",
+ # Lynx config files
+ "lynx.cfg": "lynx",
+ # Mamba configuration file
+ ".mambarc": "yaml",
+ "mambarc": "yaml",
+ # XDG mimeapps.list
+ "mimeapps.list": "dosini",
+ # Many tools written in Python use dosini as their config
+ # like setuptools, pudb, coverage, pypi, gitlint, oelint-adv, pylint, bpython, mypy
+ # (must be before *.cfg)
+ "pip.conf": "dosini",
+ "setup.cfg": "dosini",
+ "pudb.cfg": "dosini",
+ ".coveragerc": "dosini",
+ ".pypirc": "dosini",
+ ".gitlint": "dosini",
+ ".oelint.cfg": "dosini",
+ # Many tools written in Python use toml as their config, like black
+ ".black": "toml",
+ # Wakatime config
+ ".wakatime.cfg": "dosini",
+ # Deno history
+ "deno_history.txt": "javascript",
+ # Deny hosts
+ "denyhosts.conf": "denyhosts",
+ # Dict config
+ "dict.conf": "dictconf",
+ ".dictrc": "dictconf",
+ # Earthfile
+ "Earthfile": "earthfile",
+ # EditorConfig
+ ".editorconfig": "editorconfig",
+ # Elinks configuration
+ "elinks.conf": "elinks",
+ # Erlang
+ "rebar.config": "erlang",
+ # Exim
+ "exim.conf": "exim",
+ # Exports
+ "exports": "exports",
+ # Fetchmail RC file
+ ".fetchmailrc": "fetchmail",
+ # Focus Master file (but not for auto.master)
+ "auto.master": "conf",
+ # FStab
+ "fstab": "fstab",
+ "mtab": "fstab",
+ # Git
+ "COMMIT_EDITMSG": "gitcommit",
+ "MERGE_MSG": "gitcommit",
+ "TAG_EDITMSG": "gitcommit",
+ "NOTES_EDITMSG": "gitcommit",
+ "EDIT_DESCRIPTION": "gitcommit",
+ # gnash(1) configuration files
+ "gnashrc": "gnash",
+ ".gnashrc": "gnash",
+ "gnashpluginrc": "gnash",
+ ".gnashpluginrc": "gnash",
+ # Gitolite
+ "gitolite.conf": "gitolite",
+ # Go (Google)
+ "Gopkg.lock": "toml",
+ "go.work": "gowork",
+ # GoAccess configuration
+ "goaccess.conf": "goaccess",
+ # GTK RC
+ ".gtkrc": "gtkrc",
+ "gtkrc": "gtkrc",
+ # Haskell
+ "cabal.project": "cabalproject",
+ # Go checksum file (must be before *.sum Hercules)
+ "go.sum": "gosum",
+ "go.work.sum": "gosum",
+ # Indent profile (must come before IDL *.pro!)
+ ".indent.pro": "indent",
+ # Indent RC
+ "indentrc": "indent",
+ # Ipfilter
+ "ipf.conf": "ipfilter",
+ "ipf6.conf": "ipfilter",
+ "ipf.rules": "ipfilter",
+ # SysV Inittab
+ "inittab": "inittab",
+ # JavaScript, ECMAScript, ES module script, CommonJS script
+ ".node_repl_history": "javascript",
+ # Other files that look like json
+ ".prettierrc": "json",
+ ".firebaserc": "json",
+ ".stylelintrc": "json",
+ ".lintstagedrc": "json",
+ "flake.lock": "json",
+ "deno.lock": "json",
+ ".swcrc": "json",
+ "composer.lock": "json",
+ "symfony.lock": "json",
+ # Kconfig
+ "Kconfig": "kconfig",
+ "Kconfig.debug": "kconfig",
+ "Config.in": "kconfig",
+ # Latexmkrc
+ ".latexmkrc": "perl",
+ "latexmkrc": "perl",
+ # LDAP configuration
+ "ldaprc": "ldapconf",
+ ".ldaprc": "ldapconf",
+ "ldap.conf": "ldapconf",
+ # Luadoc, Ldoc (must be before *.ld)
+ "config.ld": "lua",
+ # lf configuration (lfrc)
+ "lfrc": "lf",
+ # Lilo: Linux loader
+ "lilo.conf": "lilo",
+ # SBCL implementation of Common Lisp
+ "sbclrc": "lisp",
+ ".sbclrc": "lisp",
+ # Luau config
+ ".luaurc": "jsonc",
+ # Luacheck
+ ".luacheckrc": "lua",
+ # Mailcap configuration file
+ ".mailcap": "mailcap",
+ "mailcap": "mailcap",
+ # Meson Build system config
+ "meson.build": "meson",
+ "meson.options": "meson",
+ "meson_options.txt": "meson",
+ # msmtp
+ ".msmtprc": "msmtp",
+ # Mrxvtrc
+ "mrxvtrc": "mrxvtrc",
+ ".mrxvtrc": "mrxvtrc",
+ # Noemutt setup file
+ "Neomuttrc": "neomuttrc",
+ # Netrc
+ ".netrc": "netrc",
+ # NPM RC file
+ "npmrc": "dosini",
+ ".npmrc": "dosini",
+ # ondir
+ ".ondirrc": "ondir",
+ # OpenAL Soft config files
+ ".alsoftrc": "dosini",
+ "alsoft.conf": "dosini",
+ "alsoft.ini": "dosini",
+ "alsoftrc.sample": "dosini",
+ # Packet filter conf
+ "pf.conf": "pf",
+ # ini style config files, using # comments
+ "pacman.conf": "confini",
+ "mpv.conf": "confini",
+ # Pam environment
+ "pam_env.conf": "pamenv",
+ ".pam_environment": "pamenv",
+ # Perl Reply
+ ".replyrc": "dosini",
+ # Pine config
+ ".pinerc": "pine",
+ "pinerc": "pine",
+ ".pinercex": "pine",
+ "pinercex": "pine",
+ # Pip requirements
+ "requirements.txt": "requirements",
+ # Pipenv Pipfiles
+ "Pipfile": "toml",
+ "Pipfile.lock": "json",
+ # Pixi lock
+ "pixi.lock": "yaml",
+ # Postfix main config
+ "main.cf": "pfmain",
+ "main.cf.proto": "pfmain",
+ # Povray configuration
+ ".povrayrc": "povini",
+ # Puppet
+ "Puppetfile": "ruby",
+ # Procmail
+ ".procmail": "procmail",
+ ".procmailrc": "procmail",
+ # PyPA manifest files
+ "MANIFEST.in": "pymanifest",
+ # QMLdir
+ "qmldir": "qmldir",
+ # Ratpoison config/command files
+ ".ratpoisonrc": "ratpoison",
+ "ratpoisonrc": "ratpoison",
+ # Readline
+ ".inputrc": "readline",
+ "inputrc": "readline",
+ # R profile file
+ ".Rhistory": "r",
+ ".Rprofile": "r",
+ "Rprofile": "r",
+ "Rprofile.site": "r",
+ # Resolv.conf
+ "resolv.conf": "resolv",
+ # Robots.txt
+ "robots.txt": "robots",
+ # Interactive Ruby shell
+ ".irbrc": "ruby",
+ "irbrc": "ruby",
+ ".irb_history": "ruby",
+ "irb_history": "ruby",
+ # Bundler
+ "Gemfile": "ruby",
+ # Samba config
+ "smb.conf": "samba",
+ # Sendmail
+ "sendmail.cf": "sm",
+ # SGML catalog file
+ "catalog": "catalog",
+ # Alpine Linux APKBUILDs are actually POSIX sh scripts with special treatment.
+ "APKBUILD": "apkbuild",
+ # Screen RC
+ ".screenrc": "screen",
+ "screenrc": "screen",
+ # skhd (simple hotkey daemon for macOS)
+ ".skhdrc": "skhd",
+ "skhdrc": "skhd",
+ # SLRN
+ ".slrnrc": "slrnrc",
+ # Squid
+ "squid.conf": "squid",
+ # OpenSSH server configuration
+ "sshd_config": "sshdconfig",
+ # Tags
+ "tags": "tags",
+ # Xilinx's xsct and xsdb use tcl
+ ".xsctcmdhistory": "tcl",
+ ".xsdbcmdhistory": "tcl",
+ # TeX configuration
+ "texmf.cnf": "texmf",
+ # Tidy config
+ ".tidyrc": "tidy",
+ "tidyrc": "tidy",
+ "tidy.conf": "tidy",
+ # TF (TinyFugue) mud client
+ ".tfrc": "tf",
+ "tfrc": "tf",
+ # Tilefile
+ "Tiltfile": "tiltfile",
+ "tiltfile": "tiltfile",
+ # Trustees
+ "trustees.conf": "trustees",
+ # Vagrant (uses Ruby syntax)
+ "Vagrantfile": "ruby",
+ # MNVinfo file
+ ".mnvinfo": "mnvinfo",
+ "_mnvinfo": "mnvinfo",
+ # Vgrindefs file
+ "vgrindefs": "vgrindefs",
+ # Wget config
+ ".wgetrc": "wget",
+ "wgetrc": "wget",
+ # Wget2 config
+ ".wget2rc": "wget2",
+ "wget2rc": "wget2",
+ # WvDial
+ "wvdial.conf": "wvdial",
+ ".wvdialrc": "wvdial",
+ # CVS RC file
+ ".cvsrc": "cvsrc",
+ # X11vnc
+ ".x11vncrc": "conf",
+ # Xprofile
+ ".xprofile": "sh",
+ # X compose file
+ ".XCompose": "xcompose",
+ "Compose": "xcompose",
+ # MSBUILD configuration files are also XML
+ "Directory.Packages.props": "xml",
+ "Directory.Build.targets": "xml",
+ "Directory.Build.props": "xml",
+ # ATI graphics driver configuration
+ "fglrxrc": "xml",
+ # Nfs
+ "nfs.conf": "dosini",
+ "nfsmount.conf": "dosini",
+ # Yarn lock
+ "yarn.lock": "yaml",
+ # Zathurarc
+ "zathurarc": "zathurarc",
+}
+
+# Uncomment this line to check for compilation errors early
+# defcompile
diff --git a/mnv/runtime/autoload/dist/json.mnv b/mnv/runtime/autoload/dist/json.mnv
new file mode 100644
index 0000000000..4c24e26881
--- /dev/null
+++ b/mnv/runtime/autoload/dist/json.mnv
@@ -0,0 +1,182 @@
+mnv9script
+
+# Maintainer: Maxim Kim <habamax@gmail.com>
+# Last update: 2023-12-10
+#
+# Set of functions to format/beautify JSON data structures.
+#
+# Could be used to reformat a minified json in a buffer (put it into ~/.mnv/ftplugin/json.mnv):
+# import autoload 'dist/json.mnv'
+# setl formatexpr=json.FormatExpr()
+#
+# Or to get a formatted string out of mnv's dict/list/string:
+# mnv9script
+# import autoload 'dist/json.mnv'
+# echo json.Format({
+# "widget": { "debug": "on", "window": { "title": "Sample \"Konfabulator\" Widget",
+# "name": "main_window", "width": 500, "height": 500
+# },
+# "image": { "src": "Images/Sun.png", "name": "sun1", "hOffset": 250,
+# "vOffset": 250, "alignment": "center" },
+# "text": { "data": "Click Here", "size": 36, "style": "bold", "name": "text1",
+# "hOffset": 250, "vOffset": 100, "alignment": "center",
+# "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" } }
+# })
+#
+# Should output:
+# {
+# "widget": {
+# "debug": "on",
+# "window": {
+# "title": "Sample \"Konfabulator\" Widget",
+# "name": "main_window",
+# "width": 500,
+# "height": 500
+# },
+# "image": {
+# "src": "Images/Sun.png",
+# "name": "sun1",
+# "hOffset": 250,
+# "vOffset": 250,
+# "alignment": "center"
+# },
+# "text": {
+# "data": "Click Here",
+# "size": 36,
+# "style": "bold",
+# "name": "text1",
+# "hOffset": 250,
+# "vOffset": 100,
+# "alignment": "center",
+# "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
+# }
+# }
+# }
+#
+# NOTE: order of `key: value` pairs is not kept.
+#
+# You can also use a JSON string instead of mnv's dict/list to maintain order:
+# echo json.Format('{"hello": 1, "world": 2}')
+# {
+# "hello": 1,
+# "world": 2
+# }
+
+
+# To be able to reformat with `gq` add following to `~/.mnv/ftplugin/json.mnv`:
+# import autoload 'dist/json.mnv'
+# setl formatexpr=json.FormatExpr()
+export def FormatExpr(): number
+ FormatRange(v:lnum, v:lnum + v:count - 1)
+ return 0
+enddef
+
+
+# import autoload 'dist/json.mnv'
+# command -range=% JSONFormat json.FormatRange(<line1>, <line2>)
+export def FormatRange(line1: number, line2: number)
+ var indent_base = matchstr(getline(line1), '^\s*')
+ var indent = &expandtab ? repeat(' ', &shiftwidth) : "\t"
+
+ var [l1, l2] = line1 > line2 ? [line2, line1] : [line1, line2]
+
+ var json_src = getline(l1, l2)->join()
+ var json_fmt = Format(json_src, {use_tabs: !&et, indent: &sw, indent_base: indent_base})->split("\n")
+
+ exe $":{l1},{l2}d"
+
+ if line('$') == 1 && getline(1) == ''
+ setline(l1, json_fmt[0])
+ append(l1, json_fmt[1 : ])
+ else
+ append(l1 - 1, json_fmt)
+ endif
+enddef
+
+
+# Format JSON string or dict/list as JSON
+# import autoload 'dist/json.mnv'
+# echo json.Format('{"hello": "world"}', {use_tabs: false, indent: 2, indent_base: 0})
+
+# {
+# "hello": "world"
+# }
+
+# echo json.Format({'hello': 'world'}, {use_tabs: false, indent: 2, indent_base: 0})
+# {
+# "hello": "world"
+# }
+#
+# Note, when `obj` is dict, order of the `key: value` pairs might be different:
+# echo json.Format({'hello': 1, 'world': 2})
+# {
+# "world": 2,
+# "hello": 1
+# }
+export def Format(obj: any, params: dict<any> = {}): string
+ var obj_str = ''
+ if type(obj) == v:t_string
+ obj_str = obj
+ else
+ obj_str = json_encode(obj)
+ endif
+
+ var indent_lvl = 0
+ var indent_base = get(params, "indent_base", "")
+ var indent = get(params, "use_tabs", false) ? "\t" : repeat(' ', get(params, "indent", 2))
+ var json_line = indent_base
+ var json = ""
+ var state = ""
+ for char in obj_str
+ if state == ""
+ if char =~ '[{\[]'
+ json_line ..= char
+ json ..= json_line .. "\n"
+ indent_lvl += 1
+ json_line = indent_base .. repeat(indent, indent_lvl)
+ elseif char =~ '[}\]]'
+ if json_line !~ '^\s*$'
+ json ..= json_line .. "\n"
+ indent_lvl -= 1
+ if indent_lvl < 0
+ json_line = strpart(indent_base, -indent_lvl * len(indent))
+ else
+ json_line = indent_base .. repeat(indent, indent_lvl)
+ endif
+ elseif json =~ '[{\[]\n$'
+ json = json[ : -2]
+ json_line = substitute(json_line, '^\s*', '', '')
+ indent_lvl -= 1
+ endif
+ json_line ..= char
+ elseif char == ':'
+ json_line ..= char .. ' '
+ elseif char == '"'
+ json_line ..= char
+ state = 'QUOTE'
+ elseif char == ','
+ json_line ..= char
+ json ..= json_line .. "\n"
+ json_line = indent_base .. repeat(indent, indent_lvl)
+ elseif char !~ '\s'
+ json_line ..= char
+ endif
+ elseif state == "QUOTE"
+ json_line ..= char
+ if char == '\'
+ state = "ESCAPE"
+ elseif char == '"'
+ state = ""
+ endif
+ elseif state == "ESCAPE"
+ state = "QUOTE"
+ json_line ..= char
+ else
+ json_line ..= char
+ endif
+ endfor
+ if json_line !~ '^\s*$'
+ json ..= json_line .. "\n"
+ endif
+ return json
+enddef
diff --git a/mnv/runtime/autoload/dist/man.mnv b/mnv/runtime/autoload/dist/man.mnv
new file mode 100644
index 0000000000..e84bb7a0b2
--- /dev/null
+++ b/mnv/runtime/autoload/dist/man.mnv
@@ -0,0 +1,337 @@
+" MNV filetype plugin autoload file
+" Language: man
+" Maintainer: Jason Franklin <jason@oneway.dev>
+" Maintainer: SungHyun Nam <goweol@gmail.com>
+" Autoload Split: Bram Moolenaar
+" Last Change: 2024 Jan 17 (make it work on AIX, see #13847)
+" 2024 Jul 06 (honor command modifiers, #15117)
+" 2025 Mar 05 (add :keepjumps, #16791)
+" 2025 Mar 09 (improve :Man completion for man-db, #16843)
+
+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'
+ " cache the value
+ let uname_s = system('uname -s')
+
+ if uname_s =~ "SunOS" && system('uname -r') =~ "^5"
+ " Special Case for Man on SunOS
+ let s:man_sect_arg = "-s"
+ let s:man_find_arg = "-l"
+ elseif uname_s =~? 'AIX'
+ " Special Case for Man on AIX
+ let s:man_sect_arg = ""
+ let s:man_find_arg = ""
+ endif
+ endif
+catch /E145:/
+ " Ignore the error in restricted mode
+endtry
+
+unlet! uname_s
+
+let s:man_db_pages_by_section = v:null
+func! s:ManDbPagesBySection() abort
+ if s:man_db_pages_by_section isnot v:null
+ return s:man_db_pages_by_section
+ endif
+ let s:man_db_pages_by_section = {}
+ let list_command = 'apropos --long .'
+ let unparsed_lines = []
+ for line in systemlist(list_command)
+ " Typical lines:
+ " mnv (1) - MNV is not Vim, a programmer's text editor
+ "
+ " Unusual lines:
+ " pgm_read_ T _ (3avr) - (unknown subject)
+ "
+ " Code that shows the line's format:
+ " https://gitlab.com/man-db/man-db/-/blob/2607d203472efb036d888e9e7997724a41a53876/src/whatis.c#L409
+ let match = matchlist(line, '^\(.\{-1,}\) (\(\S\+\)) ')
+ if empty(match)
+ call add(unparsed_lines, line)
+ continue
+ endif
+ let [page, section] = match[1:2]
+ if !has_key(s:man_db_pages_by_section, section)
+ let s:man_db_pages_by_section[section] = []
+ endif
+ call add(s:man_db_pages_by_section[section], page)
+ endfor
+ if !empty(unparsed_lines)
+ echomsg 'Unable to parse ' .. string(len(unparsed_lines)) .. ' lines ' ..
+ \ 'from the output of `' .. list_command .. '`. Example lines:'
+ for line in unparsed_lines[:9]
+ echomsg line
+ endfor
+ endif
+ return s:man_db_pages_by_section
+endfunc
+
+func! dist#man#Reload() abort
+ if g:ft_man_implementation ==# 'man-db'
+ let s:man_db_pages_by_section = v:null
+ call s:ManDbPagesBySection()
+ endif
+endfunc
+
+func! s:StartsWithCaseInsensitive(haystack, needle) abort
+ if empty(a:needle)
+ return v:true
+ endif
+ return a:haystack[:len(a:needle)-1] ==? a:needle
+endfunc
+
+func! dist#man#ManDbComplete(arg_lead, cmd_line, cursor_pos) abort
+ let args = split(trim(a:cmd_line[: a:cursor_pos - 1], '', 1), '', v:true)
+ let pages_by_section = s:ManDbPagesBySection()
+ if len(args) > 2
+ " Page in the section args[1]. At least on Debian testing as of
+ " 2025-03-06, man seems to match sections case-insensitively and match any
+ " prefix of the section. E.g., `man 3 sigprocmask` and `man 3PoSi
+ " sigprocmask` with both load sigprocmask(3posix) even though the 3 in the
+ " first command is also the name of a different section.
+ let results = []
+ for [section, pages] in items(pages_by_section)
+ if s:StartsWithCaseInsensitive(section, args[1])
+ call extend(results, pages)
+ endif
+ endfor
+ else
+ " Could be a section, or a page in any section. Add space after sections
+ " since there has to be a second argument in that case.
+ let results = flattennew(values(pages_by_section), 1)
+ for section in keys(pages_by_section)
+ call add(results, section .. ' ')
+ endfor
+ endif
+ call sort(results)
+ call uniq(results)
+ call filter(results,
+ \ {_, val -> s:StartsWithCaseInsensitive(val, a:arg_lead)})
+ return results
+endfunc
+
+func s:ParseIntoPageAndSection()
+ " Accommodate a reference that terminates in a hyphen.
+ "
+ " See init_charset_table() at
+ " https://git.savannah.gnu.org/cgit/groff.git/tree/src/roff/troff/input.cpp?h=1.22.4#n6794
+ "
+ " See can_break_after() at
+ " https://git.savannah.gnu.org/cgit/groff.git/tree/src/roff/troff/charinfo.h?h=1.22.4#n140
+ "
+ " Assumptions and limitations:
+ " 1) Manual-page references (in consequence of command-related filenames)
+ " do not contain non-ASCII HYPHENs (0x2010), any terminating HYPHEN
+ " must have been introduced to mark division of a word at the end of
+ " a line and can be discarded; whereas similar references may contain
+ " ASCII HYPHEN-MINUSes (0x002d) and any terminating HYPHEN-MINUS forms
+ " a compound word in addition to marking word division.
+ " 2) Well-formed manual-page references always have a section suffix, e.g.
+ " "git-commit(1)", therefore suspended hyphenated compounds are not
+ " determined, e.g. [V] (With cursor at _git-merge-_ below...)
+ " ".................... git-merge- and git-merge-base. (See git-cherry-
+ " pick(1) and git-cherry(1).)" (... look up "git-merge-pick(1)".)
+ "
+ " Note that EM DASH (0x2014), a third stooge from init_charset_table(),
+ " neither connects nor divides parts of a word.
+ let str = expand("<cWORD>")
+
+ if str =~ '\%u2010$' " HYPHEN (-1).
+ let str = strpart(str, 0, strridx(str, "\u2010"))
+
+ " Append the leftmost WORD (or an empty string) from the line below.
+ let str .= get(split(get(getbufline(bufnr('%'), line('.') + 1), 0, '')), 0, '')
+ elseif str =~ '-$' " HYPHEN-MINUS.
+ " Append the leftmost WORD (or an empty string) from the line below.
+ let str .= get(split(get(getbufline(bufnr('%'), line('.') + 1), 0, '')), 0, '')
+ endif
+
+ " According to man(1), section name formats vary (MANSECT):
+ " 1 n l 8 3 2 3posix 3pm 3perl 3am 5 4 9 6 7
+ let parts = matchlist(str, '\(\k\+\)(\(\k\+\))')
+ return (len(parts) > 2)
+ \ ? {'page': parts[1], 'section': parts[2]}
+ \ : {'page': matchstr(str, '\k\+'), 'section': ''}
+endfunc
+
+func dist#man#PreGetPage(cnt)
+ if a:cnt == 0
+ let what = s:ParseIntoPageAndSection()
+ let sect = what.section
+ let page = what.page
+ else
+ let what = s:ParseIntoPageAndSection()
+ let sect = a:cnt
+ let page = what.page
+ 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><CR>
+ if page ==? '<cword>'
+ let what = s:ParseIntoPageAndSection()
+ let sect = what.section
+ let page = what.page
+ 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.mnv: 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 a:cmdmods =~ '\<\(tab\|vertical\|horizontal\)\>'
+ let open_cmd = a:cmdmods . ' split'
+ elseif 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 = '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
+ keepjumps %delete _
+ let unsetwidth = 0
+ if empty($MANWIDTH)
+ let $MANWIDTH = winwidth(0)
+ let unsetwidth = 1
+ endif
+
+ " Ensure MNV 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)
+
+ silent exec "r !" . man_cmd
+
+ " Emulate piping the buffer through the "col -b" command.
+ " Ref: https://github.com/Project-Tick/Project-Tick/issues/12301
+ exe 'silent! keepjumps keeppatterns %s/\v(.)\b\ze\1?//e' .. (&gdefault ? '' : 'g')
+
+ if unsetwidth
+ let $MANWIDTH = ''
+ endif
+ " Remove blank lines from top and bottom.
+ while line('$') > 1 && getline(1) =~ '^\s*$'
+ keepjumps 1delete _
+ endwhile
+ while line('$') > 1 && getline('$') =~ '^\s*$'
+ keepjumps $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"
+ call cursor(s:man_tag_lin, 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
+
+" mnv: set sw=2 ts=8 noet:
diff --git a/mnv/runtime/autoload/dist/mnv.mnv b/mnv/runtime/autoload/dist/mnv.mnv
new file mode 100644
index 0000000000..6e14895d28
--- /dev/null
+++ b/mnv/runtime/autoload/dist/mnv.mnv
@@ -0,0 +1,35 @@
+" MNV runtime support library,
+" runs the MNV9 script version or legacy script version
+" on demand (mostly for Neomnv compatibility)
+"
+" Maintainer: The MNV Project <https://github.com/Project-Tick/Project-Tick>
+" Last Change: 2026 Jan 11
+
+
+" enable the zip and gzip plugin by default, if not set
+if !exists('g:zip_exec')
+ let g:zip_exec = 1
+endif
+
+if !exists('g:gzip_exec')
+ let g:gzip_exec = 1
+endif
+
+if !has('mnv9script')
+ function dist#mnv#IsSafeExecutable(filetype, executable)
+ let cwd = getcwd()
+ if empty(exepath(a:executable))
+ return v:false
+ endif
+ return get(g:, a:filetype .. '_exec', get(g:, 'plugin_exec', 0)) &&
+ \ (fnamemodify(exepath(a:executable), ':p:h') !=# cwd
+ \ || (split($PATH, has('win32') ? ';' : ':')->index(cwd) != -1 &&
+ \ cwd != '.'))
+ endfunction
+
+ finish
+endif
+
+def dist#mnv#IsSafeExecutable(filetype: string, executable: string): bool
+ return dist#mnv9#IsSafeExecutable(filetype, executable)
+enddef
diff --git a/mnv/runtime/autoload/dist/mnv9.mnv b/mnv/runtime/autoload/dist/mnv9.mnv
new file mode 100644
index 0000000000..010a46418c
--- /dev/null
+++ b/mnv/runtime/autoload/dist/mnv9.mnv
@@ -0,0 +1,154 @@
+mnv9script
+
+# MNV runtime support library
+#
+# Maintainer: The MNV Project <https://github.com/Project-Tick/Project-Tick>
+# Last Change: 2026 Mar 10
+
+export def IsSafeExecutable(filetype: string, executable: string): bool
+ if empty(exepath(executable))
+ return v:false
+ endif
+ var cwd = getcwd()
+ return get(g:, filetype .. '_exec', get(g:, 'plugin_exec', 0))
+ && (fnamemodify(exepath(executable), ':p:h') !=# cwd
+ || (split($PATH, has('win32') ? ';' : ':')->index(cwd) != -1
+ && cwd != '.'))
+enddef
+
+def Redir(): string
+ if get(g:, 'netrw_suppress_gx_mesg', true)
+ if &srr =~# "%s"
+ return printf(&srr, has("win32") ? "nul" : "/dev/null")
+ elseif &srr =~# '>&\?$'
+ return &srr .. (has("win32") ? "nul" : "/dev/null")
+ else
+ return &srr .. (has("win32") ? "> nul" : "> /dev/null")
+ endif
+ endif
+ return ''
+enddef
+
+if has('unix')
+ if has('win32unix')
+ # Cygwin provides cygstart
+ if executable('cygstart')
+ export def Launch(args: string)
+ execute $'silent ! cygstart --hide {args} {Redir()}' | redraw!
+ enddef
+ elseif !empty($MSYSTEM) && executable('start')
+ # MSYS2/Git Bash comes by default without cygstart; see
+ # https://www.msys2.org/wiki/How-does-MSYS2-differ-from-Cygwin
+ # Instead it provides /usr/bin/start script running `cmd.exe //c start`
+ # Adding "" //b` sets void title, hides cmd window and blocks path conversion
+ # of /b to \b\ " by MSYS2; see https://www.msys2.org/docs/filesystem-paths/
+ export def Launch(args: string)
+ execute $'silent !start "" //b {args} {Redir()}' | redraw!
+ enddef
+ else
+ # imitate /usr/bin/start script for other environments and hope for the best
+ export def Launch(args: string)
+ execute $'silent !cmd /c start "" /b {args} {Redir()}' | redraw!
+ enddef
+ endif
+ elseif exists('$WSL_DISTRO_NAME') # use cmd.exe to start GUI apps in WSL
+ export def Launch(args: string)
+ const command = (args =~? '\v<\f+\.(exe|com|bat|cmd)>')
+ ? $'cmd.exe /c start /b {args} {Redir()}'
+ : $'nohup {args} {Redir()} &'
+ execute $'silent ! {command}' | redraw!
+ enddef
+ else
+ export def Launch(args: string)
+ # Use job_start, because using !xdg-open is known not to work with zsh
+ # ignore signals on exit
+ job_start(split(args), {'stoponexit': ''})
+ enddef
+ endif
+elseif has('win32')
+ export def Launch(args: string)
+ try
+ execute ':silent !start' args | redraw!
+ catch /^MNV(!):E371:/
+ echohl ErrorMsg
+ echom "dist#mnv9#Launch(): can not start" args
+ echohl None
+ endtry
+ enddef
+else
+ export def Launch(dummy: string)
+ echom 'No common launcher found'
+ enddef
+endif
+
+var os_viewer = null_string
+# Git Bash
+if has('win32unix')
+ # (cyg)start suffices
+ os_viewer = ''
+# Windows
+elseif has('win32')
+ os_viewer = '' # Use :!start
+# WSL
+elseif executable('explorer.exe')
+ os_viewer = 'explorer.exe'
+# Linux / BSD
+elseif executable('xdg-open')
+ os_viewer = 'xdg-open'
+# MacOS
+elseif executable('open')
+ os_viewer = 'open'
+endif
+
+def Viewer(): string
+ # g:Openprg could be a string of program + its arguments, test if first
+ # argument is executable
+ var user_viewer = get(g:, "Openprg", get(g:, "netrw_browsex_viewer", ""))
+
+ # Take care of an off-by-one check for "for" too
+ if executable(trim(user_viewer))
+ return user_viewer
+ endif
+
+ var args = split(user_viewer, '\s\+\zs')
+ var viewer = get(args, 0, '')
+
+ for arg in args[1 :]
+ if executable(trim(viewer))
+ return user_viewer
+ endif
+
+ viewer ..= arg
+ endfor
+
+ if os_viewer == null
+ echoerr "No program to open this path found. See :help Open for more information."
+ endif
+
+ return os_viewer
+enddef
+
+export def Open(file: string)
+ # disable shellslash for shellescape, required on Windows #17995
+ if exists('+shellslash') && &shellslash
+ &shellslash = false
+ defer setbufvar('%', '&shellslash', true)
+ endif
+ if &shell == 'pwsh' || &shell == 'powershell'
+ const shell = &shell
+ setlocal shell&
+ defer setbufvar('%', '&shell', shell)
+ endif
+ if has('unix') && !has('win32unix') && !exists('$WSL_DISTRO_NAME')
+ # Linux: using job_start, so do not use shellescape.
+ Launch($"{Viewer()} {file}")
+ else
+ # Windows/WSL/Cygwin: NEEDS shellescape because Launch uses '!'
+ Launch($"{Viewer()} {shellescape(file, 1)}")
+ endif
+enddef
+
+# Uncomment this line to check for compilation errors early
+# defcompile
+
+# mnv: ts=8 sts=2 sw=2 et
diff --git a/mnv/runtime/autoload/dist/mnvindent.mnv b/mnv/runtime/autoload/dist/mnvindent.mnv
new file mode 100644
index 0000000000..fd1f6177d4
--- /dev/null
+++ b/mnv/runtime/autoload/dist/mnvindent.mnv
@@ -0,0 +1,1277 @@
+mnv9script
+
+# Language: MNV script
+# Maintainer: github user lacygoill
+# Last Change: 2025 Oct 09
+#
+# Includes changes from The MNV Project:
+
+# NOTE: Whenever you change the code, make sure the tests are still passing:
+#
+# $ cd runtime/indent/
+# $ make clean; make test || mnvdiff testdir/mnv.{ok,fail}
+
+# Config {{{1
+
+const TIMEOUT: number = get(g:, 'mnv_indent', {})
+ ->get('searchpair_timeout', 100)
+
+def IndentMoreInBracketBlock(): number # {{{2
+ if get(g:, 'mnv_indent', {})
+ ->get('more_in_bracket_block', false)
+ return shiftwidth()
+ endif
+ return 0
+enddef
+
+def IndentMoreLineContinuation(): number # {{{2
+ var n: any = get(g:, 'mnv_indent', {})
+ # We inspect `g:mnv_indent_cont` to stay backward compatible.
+ ->get('line_continuation', get(g:, 'mnv_indent_cont', shiftwidth() * 3))
+
+ if n->typename() == 'string'
+ return n->eval()
+ endif
+ return n
+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_MNV9_COMMENT {{{3
+
+const INLINE_MNV9_COMMENT: string = '\s#'
+
+# COMMENT {{{3
+
+# TODO: Technically, `"\s` is wrong.
+#
+# First, whitespace is not required.
+# Second, in MNV9, a string might appear at the start of the line.
+# To be sure, we should also inspect the syntax.
+# We can't use `INLINE_COMMENT` here. {{{
+#
+# const COMMENT: string = $'^\s*{INLINE_COMMENT}'
+# ^------------^
+# ✘
+#
+# Because `INLINE_COMMENT` asserts the presence of a whitespace before the
+# comment leader. This assertion is not satisfied for a comment starting at the
+# start of the line.
+#}}}
+const COMMENT: string = '^\s*\%(#\|"\\\=\s\).*$'
+
+# DICT_KEY {{{3
+
+const DICT_KEY: string = '^\s*\%('
+ .. '\%(\w\|-\)\+'
+ .. '\|'
+ .. '"[^"]*"'
+ .. '\|'
+ .. "'[^']*'"
+ .. '\|'
+ .. '\[[^]]\+\]'
+ .. '\)'
+ .. ':\%(\s\|$\)'
+
+# END_OF_COMMAND {{{3
+
+const END_OF_COMMAND: string = $'\s*\%($\|||\@!\|{INLINE_COMMENT}\)'
+
+# END_OF_LINE {{{3
+
+const END_OF_LINE: string = $'\s*\%($\|{INLINE_COMMENT}\)'
+
+# END_OF_MNV9_LINE {{{3
+
+const END_OF_MNV9_LINE: string = $'\s*\%($\|{INLINE_MNV9_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:
+#
+# [^-+*/%.:#[:blank:][: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'],
+ enum: ['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\>!\=
+ [cl]f\=do\>!\=
+ folddoc\%[losed]\>
+ foldd\%[oopen]\>
+ 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('\|')}\)\%(\s\|$\)\@='
+
+# 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\|$\|!\)\@=\%(\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\=\>\%(\s\|$\)\@=\%(\s*{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->add($'\%({MODIFIERS[kwd]}\)\={kwd}')
+ else
+ patterns->add(kwd)
+ endif
+ endfor
+ endfor
+}
+
+const STARTS_NAMED_BLOCK: string = $'^\s*\%(sil\%[ent]!\=\s\+\)\=\%({patterns->join('\|')}\)\>\%(\s\|$\|!\)\@='
+
+# 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\>!\=\s\@='
+
+# 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\+.*$
+ \<reg\%[isters]\%(\s\+\S\+\)\+$
+ \%(\<sil\%[ent]!\=\s\+\)\=\<[nvxsoilct]\=\%(nore\|un\)\=map!\=\s
+ \<set\%(\%[global]\|\%[local]\)\>.*,$
+ {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_MNV9_LINE
+
+# CLOSING_BRACKET_AT_EOL {{{3
+
+const CLOSING_BRACKET_AT_EOL: string = CLOSING_BRACKET .. END_OF_MNV9_LINE
+
+# COMMA_AT_EOL {{{3
+
+const COMMA_AT_EOL: string = $',{END_OF_MNV9_LINE}'
+
+# COMMA_OR_DICT_KEY_AT_EOL {{{3
+
+const COMMA_OR_DICT_KEY_AT_EOL: string = $'\%(,\|{DICT_KEY}\){END_OF_MNV9_LINE}'
+
+# LAMBDA_ARROW_AT_EOL {{{3
+
+const LAMBDA_ARROW_AT_EOL: string = $'\s=>{END_OF_MNV9_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'
+ .. '\|' .. '->\s*(' # lambda call: ->((v) => v ? "ON" : "OFF")()
+ .. '\|' .. '\.\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:mnvindent.startindent
+ unlet! b:mnvindent
+ if line_A.text =~ ENDS_BLOCK_OR_CLAUSE
+ return ind - shiftwidth()
+ endif
+ 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)
+ endif
+ return Indent(line_B.lnum) + IndentMoreLineContinuation()
+ endif
+
+ if line_A->AtStartOf('FuncHeader')
+ && !IsInInterface()
+ line_A.lnum->CacheFuncHeader()
+ elseif line_A.lnum->IsInside('FuncHeader')
+ return b:mnvindent.startindent + 2 * shiftwidth()
+ elseif line_A.lnum->IsRightBelow('FuncHeader')
+ var startindent: number = b:mnvindent.startindent
+ unlet! b:mnvindent
+ if line_A.text =~ ENDS_FUNCTION
+ return startindent
+ endif
+ return startindent + shiftwidth()
+ endif
+
+ var past_bracket_block: dict<any>
+ if exists('b:mnvindent')
+ && b:mnvindent->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:mnvindent.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 MNV9 script. And in a MNV9 script, the first line is
+ # `mnv9script`. Or maybe some legacy code/comment (see `:help mnv9-mix`).
+ # But you can't find a MNV9 function header or MNV9 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()
+ endif
+
+ if 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)
+ endif
+ return -1
+ 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
+
+ return base_ind + Offset(line_A, line_B)
+enddef
+
+def g:GetMNVIndent(): 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
+ endif
+
+ # increase indentation inside a block
+ if line_B.text =~ STARTS_NAMED_BLOCK
+ || line_B->EndsWithCurlyBlock()
+ # But don't indent if the line starting the block also closes it.
+ if line_B->AlsoClosesBlock()
+ return 0
+ endif
+ # 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.
+ if (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()
+ endif
+ return shiftwidth()
+ endif
+
+ # increase indentation of a line if it's the continuation of a command which
+ # started on a previous line
+ if !line_A.isfirst
+ && (line_B->EndsWithLineContinuation()
+ || line_A.text =~ LINE_CONTINUATION_AT_SOL)
+ && !(line_B->EndsWithComma() && line_A.lnum->IsInside('EnumBlock'))
+ return shiftwidth()
+ endif
+
+ return 0
+enddef
+
+def HereDocIndent(line_A: string): number # {{{2
+ # at the end of a heredoc
+ if line_A =~ $'^\s*{b:mnvindent.endmarker}$'
+ # `END` must be at the very start of the line if the heredoc is not trimmed
+ if !b:mnvindent.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
+ endif
+ var ind: number = b:mnvindent.startindent
+ # invalidate the cache so that it's not used for the next heredoc
+ unlet! b:mnvindent
+ return ind
+ endif
+
+ # In a non-trimmed heredoc, all of leading whitespace is semantic.
+ # Leave it alone.
+ if !b:mnvindent.is_trimmed
+ # But do save the indent of the assignment line.
+ if !b:mnvindent->has_key('startindent')
+ b:mnvindent.startindent = b:mnvindent.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:mnvindent->has_key('offset')
+ var old_startindent: number = b:mnvindent.startindent
+ var new_startindent: number = b:mnvindent.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:mnvindent.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:mnvindent.offset = offset
+ b:mnvindent.startindent = new_startindent
+ endif
+
+ return Indent(v:lnum) + b:mnvindent.offset
+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 mnvindent_save: dict<any> = get(b:, 'mnvindent', {})->deepcopy()
+ var ind: number = next->Expr()
+ # The previous `Expr()` might have set or deleted `b:mnvindent`.
+ # 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 mnvindent_save->empty()
+ unlet! b:mnvindent
+ else
+ b:mnvindent = mnvindent_save
+ endif
+ if getline(next) =~ ENDS_BLOCK
+ return ind + shiftwidth()
+ endif
+ return ind
+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:mnvindent.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:mnvindent.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:mnvindent = {
+ is_HereDoc: true,
+ startlnum: line_A.lnum,
+ endlnum: endlnum,
+ endmarker: endmarker,
+ is_trimmed: is_trimmed,
+ }
+ if is_trimmed
+ b:mnvindent.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:mnvindent = {
+ 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:mnvindent')
+ b:mnvindent = {
+ 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:mnvindent.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:mnvindent',
+ event: 'ModeChanged',
+ group: '__MNVIndent__',
+ once: true,
+ pattern: '*:n',
+ replace: true,
+ }])
+enddef
+
+def RemovePastBracketBlock(line_A: dict<any>): dict<any> # {{{2
+ var stack: list<dict<any>> = b:mnvindent.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:mnvindent
+ 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
+ # MNV_INDENT_TEST_TRACE_START
+ return searchpair('\C' .. s, (middle == '' ? '' : '\C' .. middle), '\C' .. e,
+ flags, (): bool => InCommentOrString(), stopline, TIMEOUT)
+ # MNV_INDENT_TEST_TRACE_END dist#mnvindent#SearchPair
+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:mnvindent') || !b:mnvindent->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:mnvindent')
+ && !b:mnvindent->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 syntax == 'EnumBlock'
+ var cur_pos = getpos('.')
+ cursor(lnum, 1)
+ var enum_pos = search('^\C\s*\%(export\s\)\=\s*enum\s\+\S\+', 'bnW')
+ var endenum_pos = search('^\C\s*endenum\>', 'bnW')
+ setpos('.', cur_pos)
+
+ if enum_pos == 0 && endenum_pos == 0
+ return false
+ endif
+ if (enum_pos > 0 && (endenum_pos == 0 || enum_pos > endenum_pos))
+ return true
+ endif
+ return false
+ endif
+
+ if !exists('b:mnvindent')
+ || !b:mnvindent->has_key($'is_{syntax}')
+ return false
+ endif
+
+ if syntax == 'BracketBlock'
+ if !b:mnvindent->has_key('block_stack')
+ || b:mnvindent.block_stack->empty()
+ return false
+ endif
+ return lnum <= b:mnvindent.block_stack[0].endlnum
+ endif
+
+ return lnum <= b:mnvindent.endlnum
+enddef
+
+def IsRightBelow(lnum: number, syntax: string): bool # {{{3
+ return exists('b:mnvindent')
+ && b:mnvindent->has_key($'is_{syntax}')
+ && lnum > b:mnvindent.endlnum
+enddef
+
+def IsInCurlyBlock(): bool # {{{3
+ return b:mnvindent.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)
+ # MNV_INDENT_TEST_TRACE_START
+ var match_lnum: number = search(pat, 'cnW', line.lnum, TIMEOUT, (): bool => InCommentOrString())
+ # MNV_INDENT_TEST_TRACE_END dist#mnvindent#NonCommentedMatch
+ setpos('.', pos)
+ return match_lnum > 0
+enddef
+# }}}1
+# mnv:sw=4
diff --git a/mnv/runtime/autoload/dist/script.mnv b/mnv/runtime/autoload/dist/script.mnv
new file mode 100644
index 0000000000..faa6d8e2fd
--- /dev/null
+++ b/mnv/runtime/autoload/dist/script.mnv
@@ -0,0 +1,490 @@
+mnv9script
+
+# MNV function for detecting a filetype from the file contents.
+# Invoked from "scripts.mnv" in 'runtimepath'
+#
+# Maintainer: The MNV Project <https://github.com/Project-Tick/Project-Tick>
+# Last Change: 2025 Dec 22
+# Former Maintainer: Bram Moolenaar <Bram@mnv.org>
+
+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\zs--split-string[ \t=]', '', '')
+ line1 = substitute(line1, '\s\zs[A-Za-z0-9_]\+=\S*\ze\s', '', 'g')
+ line1 = substitute(line1, '\s\zs\%(-[iS]\+\|--ignore-environment\)\ze\s', '', 'g')
+ 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*[/\\]\(\f\+\).*', '\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
+
+ var ft = Exe2filetype(name, line1)
+ if ft != ''
+ exe 'setl ft=' .. ft
+ endif
+enddef
+
+# Returns the filetype name associated with program "name".
+# "line1" is the #! line at the top of the file. Use the same as "name" if
+# not available.
+# Returns an empty string when not recognized.
+export def Exe2filetype(name: string, line1: string): string
+ # Bourne-like shell scripts: bash bash2 dash ksh ksh93 sh
+ if name =~ '^\(bash\d*\|dash\|ksh\d*\|sh\)\>'
+ return dist#ft#SetFileTypeSH(line1, false)
+
+ # csh scripts
+ elseif name =~ '^csh\>'
+ return dist#ft#SetFileTypeShell(exists("g:filetype_csh") ? g:filetype_csh : 'csh', false)
+
+ # tcsh scripts
+ elseif name =~ '^tcsh\>'
+ return dist#ft#SetFileTypeShell("tcsh", false)
+
+ # Z shell scripts
+ elseif name =~ '^zsh\>'
+ return 'zsh'
+
+ # TCL scripts
+ elseif name =~ '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>'
+ return 'tcl'
+
+ # Expect scripts
+ elseif name =~ '^expect\>'
+ return 'expect'
+
+ # Gnuplot scripts
+ elseif name =~ '^gnuplot\>'
+ return 'gnuplot'
+
+ # Makefiles
+ elseif name =~ 'make\>'
+ return 'make'
+
+ # Pike
+ elseif name =~ '^pike\%(\>\|[0-9]\)'
+ return 'pike'
+
+ # Lua
+ elseif name =~ 'lua'
+ return 'lua'
+
+ # Perl
+ elseif name =~ 'perl'
+ return 'perl'
+
+ # PHP
+ elseif name =~ 'php'
+ return 'php'
+
+ # Python
+ elseif name =~ 'python'
+ return 'python'
+
+ # Groovy
+ elseif name =~ '^groovy\>'
+ return 'groovy'
+
+ # Raku
+ elseif name =~ 'raku'
+ return 'raku'
+
+ # Ruby
+ elseif name =~ 'ruby'
+ return 'ruby'
+
+ # JavaScript
+ elseif name =~ 'node\(js\)\=\>\|js\>' || name =~ 'rhino\>'
+ return 'javascript'
+
+ elseif name =~# 'just'
+ return 'just'
+
+ # BC calculator
+ elseif name =~ '^bc\>'
+ return 'bc'
+
+ # sed
+ elseif name =~ 'sed\>'
+ return 'sed'
+
+ # OCaml-scripts
+ elseif name =~ 'ocaml'
+ return 'ocaml'
+
+ # Awk scripts; also finds "gawk"
+ elseif name =~ 'awk\>'
+ return 'awk'
+
+ # Website MetaLanguage
+ elseif name =~ 'wml'
+ return 'wml'
+
+ # Scheme scripts
+ elseif name =~ 'scheme'
+ return 'scheme'
+
+ # CFEngine scripts
+ elseif name =~ 'cfengine'
+ return 'cfengine'
+
+ # Erlang scripts
+ elseif name =~ 'escript'
+ return 'erlang'
+
+ # Haskell
+ elseif name =~ 'haskell'
+ return 'haskell'
+
+ # Scala
+ elseif name =~ 'scala\>'
+ return 'scala'
+
+ # Clojure
+ elseif name =~ 'clojure'
+ return 'clojure'
+
+ # Free Pascal
+ elseif name =~ 'instantfpc\>'
+ return 'pascal'
+
+ # Fennel
+ elseif name =~ 'fennel\>'
+ return 'fennel'
+
+ # MikroTik RouterOS script
+ elseif name =~ 'rsc\>'
+ return 'routeros'
+
+ # Fish shell
+ elseif name =~ 'fish\>'
+ return 'fish'
+
+ # Gforth
+ elseif name =~ 'gforth\>'
+ return 'forth'
+
+ # Icon
+ elseif name =~ 'icon\>'
+ return 'icon'
+
+ # Guile
+ elseif name =~ 'guile'
+ return 'scheme'
+
+ # Nix
+ elseif name =~ 'nix-shell'
+ return 'nix'
+
+ # Crystal
+ elseif name =~ '^crystal\>'
+ return 'crystal'
+
+ # Rexx
+ elseif name =~ '^\%(rexx\|regina\)\>'
+ return 'rexx'
+
+ # Janet
+ elseif name =~ '^janet\>'
+ return 'janet'
+
+ # Dart
+ elseif name =~ '^dart\>'
+ return 'dart'
+
+ # Execline (s6)
+ elseif name =~ '^execlineb\>'
+ return 'execline'
+
+ # Bpftrace
+ elseif name =~ '^bpftrace\>'
+ return 'bpftrace'
+
+ # MNV
+ elseif name =~ '^mnv\>'
+ return 'mnv'
+
+ endif
+
+ return ''
+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\>'
+ setl ft=zsh
+
+ # ELM Mail files
+ elseif line1 =~ '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$'
+ || line1 =~ '^\creturn-path:\s<.*@.*>$'
+ setl ft=mail
+
+ # Mason
+ elseif line1 =~ '^<[%&].*>'
+ setl ft=mason
+
+ # MNV scripts (must have '" mnv' as the first line to trigger this)
+ elseif line1 =~ '^" *[vV]im$'
+ setl ft=mnv
+
+ # libcxx and libstdc++ standard library headers like "iostream" do not have
+ # an extension, recognize the Emacs file mode.
+ elseif line1 =~? '-\*-.*C++.*-\*-'
+ setl ft=cpp
+
+ # MOO
+ elseif line1 =~ '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$'
+ setl ft=moo
+
+ # Diff file:
+ # - "diff" in first line (context diff)
+ # - "Only in " in first line
+ # - "34,35c34,35" normal diff format output
+ # - "--- " 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\+\(,\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\)')
+ setl ft=diff
+
+ # PostScript Files (must have %!PS as the first line, like a2ps output)
+ elseif line1 =~ '^%![ \t]*PS'
+ setl 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\>'
+ setl ft=m4
+
+ # AmigaDos scripts
+ elseif $TERM == "amiga" && (line1 =~ "^;" || line1 =~? '^\.bra')
+ setl ft=amiga
+
+ # SiCAD scripts (must have procn or procd as the first line to trigger this)
+ elseif line1 =~? '^ *proc[nd] *$'
+ setl ft=sicad
+
+ # Purify log files start with "**** Purify"
+ elseif line1 =~ '^\*\*\*\* Purify'
+ setl ft=purifylog
+
+ # XML
+ elseif line1 =~ '<?\s*xml.*?>'
+ setl ft=xml
+
+ # XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN")
+ elseif line1 =~ '\<DTD\s\+XHTML\s'
+ setl 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\>'
+ setl ft=html
+
+ # PDF
+ elseif line1 =~ '^%PDF-'
+ setl ft=pdf
+
+ # XXD output
+ elseif line1 =~ '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} '
+ setl ft=xxd
+
+ # RCS/CVS log output
+ elseif line1 =~ '^RCS file:' || line2 =~ '^RCS file:'
+ setl ft=rcslog
+
+ # CVS commit
+ elseif line2 =~ '^CVS:' || getline("$") =~ '^CVS: '
+ setl ft=cvs
+
+ # Prescribe
+ elseif line1 =~ '^!R!'
+ setl ft=prescribe
+
+ # Send-pr
+ elseif line1 =~ '^SEND-PR:'
+ setl ft=sendpr
+
+ # SNNS files
+ elseif line1 =~ '^SNNS network definition file'
+ setl ft=snnsnet
+ elseif line1 =~ '^SNNS pattern definition file'
+ setl ft=snnspat
+ elseif line1 =~ '^SNNS result file'
+ setl ft=snnsres
+
+ # Virata
+ elseif line1 =~ '^%.\{-}[Vv]irata'
+ || line2 =~ '^%.\{-}[Vv]irata'
+ || line3 =~ '^%.\{-}[Vv]irata'
+ || line4 =~ '^%.\{-}[Vv]irata'
+ || line5 =~ '^%.\{-}[Vv]irata'
+ setl ft=virata
+
+ # Strace
+ # inaccurate fast match first, then use accurate slow match
+ elseif (line1 =~ 'execve(' && line1 =~ '^[0-9:. ]*execve(')
+ || line1 =~ '^__libc_start_main'
+ setl ft=strace
+
+ # VSE JCL
+ elseif line1 =~ '^\* $$ JOB\>' || line1 =~ '^// *JOB\>'
+ setl ft=vsejcl
+
+ # TAK and SINDA
+ elseif line4 =~ 'K & K Associates' || line2 =~ 'TAK 2000'
+ setl ft=takout
+ elseif line3 =~ 'S Y S T E M S I M P R O V E D '
+ setl ft=sindaout
+ elseif getline(6) =~ 'Run Date: '
+ setl ft=takcmp
+ elseif getline(9) =~ 'Node File 1'
+ setl ft=sindacmp
+
+ # DNS zone files
+ elseif line1 .. line2 .. line3 .. line4 =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA'
+ setl ft=bindzone
+
+ # BAAN
+ elseif line1 =~ '|\*\{1,80}' && line2 =~ 'VRC '
+ || line2 =~ '|\*\{1,80}' && line3 =~ 'VRC '
+ setl ft=baan
+
+ # Valgrind
+ elseif line1 =~ '^==\d\+== valgrind' || line3 =~ '^==\d\+== Using valgrind'
+ setl ft=valgrind
+
+ # Go docs
+ elseif line1 =~ '^PACKAGE DOCUMENTATION$'
+ setl ft=godoc
+
+ # Renderman Interface Bytestream
+ elseif line1 =~ '^##RenderMan'
+ setl ft=rib
+
+ # Scheme scripts
+ elseif line1 =~ 'exec\s\+\S*scheme' || line2 =~ 'exec\s\+\S*scheme'
+ setl ft=scheme
+
+ # Git output
+ elseif line1 =~ '^\(commit\|tree\|object\) \x\{40,\}\>\|^tag \S\+$'
+ setl ft=git
+
+ # Gprof (gnu profiler)
+ elseif line1 == 'Flat profile:'
+ && line2 == ''
+ && line3 =~ '^Each sample counts as .* seconds.$'
+ setl ft=gprof
+
+ # Erlang terms
+ # (See also: http://www.gnu.org/software/emacs/manual/html_node/emacs/Choosing-Modes.html#Choosing-Modes)
+ elseif line1 =~? '-\*-.*erlang.*-\*-'
+ setl ft=erlang
+
+ # YAML
+ elseif line1 =~ '^%YAML'
+ setl ft=yaml
+
+ # MikroTik RouterOS script
+ elseif line1 =~ '^#.*by RouterOS.*$'
+ setl 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\)'
+ setl ft=sed
+
+ elseif line1 =~ '^#\s\+Reconstructed via infocmp from file:'
+ setl ft=terminfo
+
+ elseif line1 =~ '^File: .*\.info, Node: .*, \%(Next\|Prev\): .*, Up: \|This is the top of the INFO tree.'
+ setl ft=info
+
+ else
+ var lnum = 1
+ while getline(lnum) =~ "^? " && lnum < line("$")
+ lnum += 1
+ endwhile
+ if getline(lnum) =~ '^Index:\s\+\f\+$'
+ # CVS diff
+ setl 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