diff options
Diffstat (limited to 'mnv/runtime/autoload/dist')
| -rw-r--r-- | mnv/runtime/autoload/dist/ft.mnv | 3444 | ||||
| -rw-r--r-- | mnv/runtime/autoload/dist/json.mnv | 182 | ||||
| -rw-r--r-- | mnv/runtime/autoload/dist/man.mnv | 337 | ||||
| -rw-r--r-- | mnv/runtime/autoload/dist/mnv.mnv | 35 | ||||
| -rw-r--r-- | mnv/runtime/autoload/dist/mnv9.mnv | 154 | ||||
| -rw-r--r-- | mnv/runtime/autoload/dist/mnvindent.mnv | 1277 | ||||
| -rw-r--r-- | mnv/runtime/autoload/dist/script.mnv | 490 |
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 |
