GithubHelp home page GithubHelp logo

vim-jp / vim-vimlparser Goto Github PK

View Code? Open in Web Editor NEW
159.0 12.0 29.0 1.22 MB

Vim script parser

License: Other

JavaScript 30.91% Python 31.92% Shell 0.21% Batchfile 0.04% Makefile 0.14% Vim Script 36.77%

vim-vimlparser's Introduction

Vim script parsers

Build Status codecov

This is Vim script language (a.k.a. VimL) parsers.

Features

The parser to make AST (Abstract Syntax Tree)

Supported languages

This parser provide same feature for following languages.

  • Vim script
  • Python
  • JavaScript

Example

All of interfaces are provided from vimlparser module. VimLParser parse into AST using StringReader, and Compiler to compile nodes.

let s:VP = vimlparser#import()
let code = [
\ 'let s:message = printf("hello %d", 1+(2*3))'
\]
let r = s:VP.StringReader.new(code)
let p = s:VP.VimLParser.new()
let c = s:VP.Compiler.new()
echo join(c.compile(p.parse(r)), "\n")

This above code output following.

(let = s:message (printf "hello %d" (+ 1 (* 2 3))))

About project name

We know a name "VimL" is not the common short form of "Vim scripting language". But we choice "VimL" for historical and practical reasons and compatibility.

vim-vimlparser's People

Contributors

blueyed avatar get-me-power avatar haya14busa avatar iamcco avatar ichizok avatar itchyny avatar k-takata avatar koron avatar kuniwak avatar lehmacdj avatar mattn avatar rhysd avatar rianfuro avatar ryu-ichiroh avatar syngan avatar thinca avatar tony avatar tyru avatar ujihisa avatar ynkdir avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vim-vimlparser's Issues

Colon included in identifier name (e.g. `foo[bar:]`)

Given t-undef-slice.py:

import pprint
import vimlparser

s = vimlparser.StringReader(['echo foo[bar:]'])
pprint.pprint(vimlparser.VimLParser(s).parse(s))

Running PYTHONPATH=py python t-undef-slice.py displays:

{'body': [{'ea': {'addr_count': 0,
                  'amount': 0,
                  'append': 0,
                  'argcmd': {},
                  'argopt': {},
                  'argpos': {'col': 6, 'i': 5, 'lnum': 1, 'offset': 5},
                  'bad_char': 0,
                  'cmd': {'flags': 'EXTRA|NOTRLCOM|SBOXOK|CMDWIN',
                          'minlen': 2,
                          'name': 'echo',
                          'parser': 'parse_cmd_echo'},
                  'cmdpos': {'col': 1, 'i': 0, 'lnum': 1, 'offset': 0},
                  'do_ecmd_cmd': '',
                  'do_ecmd_lnum': 0,
                  'flags': 0,
                  'force_bin': 0,
                  'force_enc': 0,
                  'force_ff': 0,
                  'forceit': 0,
                  'line1': 0,
                  'line2': 0,
                  'linepos': {'col': 1, 'i': 0, 'lnum': 1, 'offset': 0},
                  'modifiers': [],
                  'range': [],
                  'read_edit': 0,
                  'regname': 0,
                  'usefilter': 0},
           'list': [{'left': {'pos': {'col': 6, 'i': 5, 'lnum': 1, 'offset': 5},
                              'type': 86,
                              'value': 'foo'},
                     'pos': {'col': 9, 'i': 8, 'lnum': 1, 'offset': 8},
                     'right': {'pos': {'col': 10,
                                       'i': 9,
                                       'lnum': 1,
                                       'offset': 9},
                               'type': 86,
                               'value': 'bar:'},
                     'type': 76}],
           'pos': {'col': 1, 'i': 0, 'lnum': 1, 'offset': 0},
           'type': 28}],
 'pos': {'col': 1, 'i': 0, 'lnum': 1, 'offset': 0},
 'type': 1}

Note the value bar: (with leading :) for the identifier.
It should be just bar.

Run vint on CI

Current autoload/vimlparser.vim has lots of warnings with vint.
Fix them all and run vint on CI.

変換候補の削除プロンプトが壊れている

  • X で消そうとすると消そうとした候補の変換後の文字列が入力に表れる (yes/no を入力する場面で)
  • ↑を消して yes<CR> として消したあと、変な状態になる (△あいう がずっとつきまとってくる…)

seems dead loop while parse `write ++enc=utf-8`

Way to reproducte:

node vimlparser.js --neovim t.vim

https://github.com/oblitum/formatvim/blob/master/autoload/format.vim

t.vim

"▶1 Начало
scriptencoding utf-8
execute frawor#Setup('4.1', {'@/options': '0.0',
            \                     '@/os': '0.0',
            \                    '@/fwc': '0.0',
            \        '@/fwc/constructor': '4.2',
            \              '@/functions': '0.0',
            \                  '@/table': '0.1',
            \   '@/decorators/altervars': '0.0',
            \                 '@/base64': '0.0',})
let s:formats={}
let s:keylist=['begin',
            \   'sbsdstart',
            \    'sbsdsep',
            \    'foldstart',
            \     'linestart',
            \      'foldcolumn', 'sign', 'linenr',
            \      'tagstart',
            \       'linkstart',
            \        'concealedstart',
            \         'line',
            \        'concealedend',
            \       'linkend',
            \      'tagend',
            \      'fold', 'difffiller', 'collapsedfiller',
            \     'lineend',
            \    'foldend',
            \   'sbsdend',
            \  'end', 'style']
" Used solely to not let vim delete functions which were profiled
let s:profiled=[]
let s:strdisplaywidthstr=(exists('*strdisplaywidth')?'strdisplaywidth': 's:_r.strdisplaywidth')
"▶1
if !has('syntax')
    call s:_f.warn('synnsup')
endif
"▶1 Options
" Debugging options:
"   Debugging: Disabled by default, enables other Debugging_* options.
"   Debugging_FuncF: If not zero, writes the resulting compiled function to the given file
"   Debugging_MinFu: If true, minimizes compiled function code
"   Debugging_Break: Dictionary mapping function name to a list of breaks. Break is either a regex 
"                    that line must match or a line number. Regex is a subject to %var substitutions 
"                    for compiledformat function.
"   Debugging_SaveO: Save opts.* values in Debugging_FuncF.
let s:_options={
            \   'DefaultFormat': {'default': 'html',
            \                     'checker': 'key formats',},
            \    'IgnoreCursor': {'default':  1,  'filter': 'bool'},
            \     'IgnoreFolds': {'default':  0,  'filter': 'bool'},
            \      'IgnoreList': {'default':  0,  'filter': 'bool'},
            \        'AllFolds': {'default':  0,  'filter': 'bool'},
            \     'IgnoreSigns': {'default':  0,  'filter': 'bool'},
            \      'IgnoreDiff': {'default':  0,  'filter': 'bool'},
            \     'IgnoreSpell': {'default':  0,  'filter': 'bool'},
            \      'IgnoreTags': {'default':  1, 'checker': 'range  0  2' },
            \    'ShowProgress': {'default':  0, 'checker': 'range  0  2' },
            \   'CollapsFiller': {'default':  0, 'checker': 'range  0 inf'},
            \        'NoLineNR': {'default': -1, 'checker': 'range -1  1' },
            \  'RelativeNumber': {'default': -1, 'checker': 'range -1  1' },
            \      'FoldColumn': {'default': -1, 'checker': 'range -1 inf'},
            \      'MaxDupTags': {'default':  5, 'checker': 'range  0 inf'},
            \ 'FormatConcealed': {'default':  1, 'checker': 'range  0  2' },
            \      'MinColumns': {'default': 40, 'checker': 'range  0 inf'},
            \   'FormatMatches': {'default': -1,
            \                     'checker': 'in [none search matches all]'},
            \     'StartTagReg': {'default':  0, 'checker': 'isreg'},
            \       'EndTagReg': {'default':  0, 'checker': 'isreg'},
            \       'ColorFile': {'default':  0,
            \                      'scopes': 'g',
            \                     'checker': 'either (is=(0) path)'},
            \'AddTagCmdEscapes': {'default': '[]*.~',
            \                     'checker': 'type string'},
            \   'UseStyleNames': {'default': 0, 'filter': 'bool'},
            \     'FormatLinks': {'default': 1, 'filter': 'bool'},
            \     'LinkRegexes': {'default': [
            \                       ['\vhttps?\:\/\/'.
            \                          '%([[:alnum:]\-]+\.)+\a{2,3}\/'.
            \                          '%(\S{-}%([.?!]?%(\ |$))@=|\S+>)',
            \                                                                        'submatch(0)'],
            \                       ['\v%(%(git|ssh)\:\/\/%(git|hg)\@)'.
            \                          '(github\.com|bitbucket\.org)[:/]'.
            \                          '([^/ ]+/[^/ ]+>)%(\.git)?',
            \                                         '''https://''.submatch(1).''/''.submatch(2)'],
            \                     ],
            \                     'checker': 'list (tuple ((isreg) (type string)))',},
            \
            \       'Debugging': {'default': 0, 'filter': 'bool'},
            \ 'Debugging_FuncF': {'default': 0,
            \                     'checker': 'either (is=(0) path)'},
            \ 'Debugging_MinFu': {'default': 0, 'filter': 'bool'},
            \ 'Debugging_Break': {'default': 0,
            \                     'checker': 'dict {?either ((in keylist)'.
            \                                               '(in [compiledformat '.
            \                                                    'compiledspec '.
            \                                                    'strescape]))'.
            \                                      'list (either ((range 1 inf) '.
            \                                                    '(isreg)))}'},
            \ 'Debugging_SaveO': {'default': 1, 'filter': 'bool'},
            \
            \'HTMLAnchorFileNameExpr': {'default': 'a:tag._tfname.".html"',
            \                           'checker': 'type string'},
            \'HTMLUseTagNameInAnchor': {'default': 0, 'filter': 'bool'},
            \'HTMLAddLinkAtTagLine':   {'default': 1, 'filter': 'bool'},
            \'HTMLTitleExpr':          {'default': 'expand(''%:p:~'')',
            \                           'checker': 'type string',},
            \'HTMLFontFamily':         {'default': '"Bitstream Vera Sans Mono",'.
            \                                      '"DejaVu Sans Mono",'.
            \                                      'Monaco,'.
            \                                      'monospace',
            \                           'checker': 'type string'},
            \'HTMLAdditionalCSS':      {'default': '', 'checker': 'type string'},
            \
            \'VOHelpPrefix':     {'default': 'http://vimpluginloader.sourceforge.net/doc/',
            \                     'checker': 'type string',},
            \'VOHelpSuffix':     {'default': '.html', 'checker': 'type string'},
            \'VOHelpAnchorExpr': {'default': 'tag.__ename=~#'':''?'.
            \                                   '(''line''.tag._linenr.''-0 (''.tag.name.'')''):'.
            \                                   '(tag.__ename)',
            \                     'checker': 'type string',},
        \}
function s:_options.LinkRegexes.merger(old, new, oname, plid, vname)
    unsilent echomsg string(a:vname)
    return extend(copy(a:old), a:new)
endfunction
"▶1 Выводимые сообщения
let s:_messages={
            \   'misskey': 'Required key is missing: %s',
            \   'synnsup': 'I wonder, why do you need this plugin: '.
            \              'this vim is compiled without syntax support',
            \   'misscol': 'File with colors list not found, '.
            \              'see :h format-o-ColorFile',
            \    'exists': 'Format already exists',
            \   'upcspec': 'Undefined %%. sequence: %%%s',
            \'nomagicchg': 'Changing magic state is not allowed',
            \   'nofloat': 'You must either use terminal vim or vim compiled '.
            \              'with +float feature',
            \'toolongpat': 'Pattern is too long. This may be caused by '.
            \              'too long StartTagReg or EndTagReg options or by '.
            \              'too long single tag.',
            \    'atnstr': 'The result of evaluating @<@{expr}@>@ is not a string',
            \   'nelines': 'Internal error: formatted buffer does not contain '.
            \              'enough lines. If you can reproduce this error please '.
            \              'post a bug report',
            \'noatdollar': '@$@ is not allowed inside @<@expr@>@ and %!cmd!%.',
        \}
"▶1 squote
function s:F.squote(str)
    return substitute(substitute(string(a:str), "\n", '''."\\n".''', 'g'),
                \                               '%',  '%%',          'g')
endfunction
"▶1 addsubs
function s:F.addsubs(var, ...)
    return a:var.join(map(copy(a:000), '((type(v:val)=='.type(0).')?'.
                \                           '("[".v:val."]"):'.
                \                      '((v:val=~#"\\W")?'.
                \                           '("[".s:F.squote(v:val)."]")'.
                \                      ':'.
                \                           '(".".v:val)))'), '')
endfunction
"▶1 getcolors
function s:F.getcolors(file)
    if !filereadable(a:file)
        call s:_f.throw('misscol')
    endif
    return map(filter(readfile(a:file, 'b'), 'v:val[:1] is# "- "'),'v:val[3:9]')
endfunction
"▶1 getcolorfile
function s:F.getcolorfile()
    let r=s:_f.getoption('ColorFile')
    if type(r)!=type('')
        if s:whatterm isnot# 'gui'
            let r=s:_r.os.path.join(s:_frawor.runtimepath, 'config',
                        \                     'formatvim',
                        \                     'colors-default-'.&t_Co.'.yaml')
        endif
        if s:whatterm is# 'gui' || !filereadable(r)
            let r=s:_r.os.path.join(s:_frawor.runtimepath, 'config',
                        \                     'formatvim',
                        \                     'colors-default.yaml')
        endif
    elseif stridx(r, '/')==-1 && stridx(r, s:_r.os.sep)==-1
        let r=s:_r.os.path.join(s:_frawor.runtimepath, 'config', 'formatvim', r.'.yaml')
    else
        return r
    endif
    return r
endfunction
"▶1 getexpr
"▶2 s:colors
if has('gui_running') || has('termguicolors') && &termguicolors
    let s:whatterm = 'gui'
    augroup FormatRedrawProgress
        autocmd VimResized * call s:F.redrawprogress()
    augroup END
    let s:_augroups+=['FormatRedrawProgress']
else
    let s:whatterm = 'cterm'
    let s:colors=s:F.getcolors(s:F.getcolorfile())
endif
"▶2 s:fmtexpressions
let s:fmtexpressions={
            \ 'f': '@fgcolor@',
            \ 'b': '@bgcolor@',
            \ 'S': '@S@',
            \'.S': '@.S@',
            \ 'N': '@-@',
            \ 'C': '@.@',
            \ ':': '@:@',
            \ '%': "'%'",
            \ '@': "'@'",
            \ '~': '@_difffillchar@',
            \'.~': '@_foldfillchar@',
            \ '-': '@$@repeat(@_difffillchar@,@_columns@-@=@)',
            \'.-': '@$@repeat(@_foldfillchar@,@_columns@-@=@)',
            \ '|': '@_vertseparator@',
        \}
"▶2 s:fcompexpressions
let s:spacesexpr="''repeat(''.s:F.squote(@_leadingspace@).'',''.@_linenumlen@.''-len(@@@))''"
let s:fcompexpressions={
            \ '#': "'@<@(@_dosomenr@?".
            \               "((@_donr@ && @_dornr@)?".
            \                   "(''((@-@==''.@_cline@.'')?".
            \                       "(@@@.''.".s:spacesexpr.".''):".
            \                       "(''.".s:spacesexpr.".''.@@@))''):".
            \                   "(".s:spacesexpr.".''.@@@'')):".
            \               "(''\"\"''))@>@'",
            \ '+': "'@$@repeat('.a:opts.qleadingspace.','.a:opts.columns.'-@=@)'",
            \ '_': "'@<@".
            \           "(@_dosomenr@?".
            \               "(''repeat(''.s:F.squote(@_leadingspace@).'',''.@_linenumlen@.'')''):".
            \               "(''\"\"'')".
            \           ")".
            \       "@>@'",
            \ ' ': 'a:opts.qleadingspace',
            \ '^': "'@<@".
            \           "(@_dosomenr@?".
            \               "(s:F.squote(@_leadingspace@)):".
            \               "(''\"\"'')".
            \           ")".
            \       "@>@'",
            \ 's': "get(a:cf.format, 'strescape', '@@@')",
        \}
unlet s:spacesexpr
"▲2
function s:F.getexpr(cf, str, opts)
    let fkey=matchstr(a:str, '\v\.?.')
    "▶2 Простые выражения (%f, %b, %*S, %N, %C, %:, %%, %@, %*~, %*-, %|)
    if has_key(s:fmtexpressions, fkey)
        return [len(fkey), s:fmtexpressions[fkey]]
    "▶2 Сложные выражения (%#, %_, %, %^, %*s)
    elseif has_key(s:fcompexpressions, fkey)
        return [len(fkey), eval(s:fcompexpressions[fkey])]
    "▶2 %={expr}=%
    elseif fkey is# '='
        let str=matchstr(a:str, '\v^([^\\%]|\\.){-}(\=\%)@=', 1)
        let shift=3+len(str)
        let str=substitute(str, '\m\\\([\\%]\)', '\1', 'g')
        return [shift, str]
    "▶2 %'{expr}'%
    elseif fkey is# "'"
        let str=matchstr(a:str, '\v^.{-}(\''\%)@=', 1)
        let shift=3+len(str)
        return [shift, str]
    "▶2 %>{expr}
    elseif fkey is# '>'
        return [len(a:str), a:str[1:]]
    "▶2 Остальные %*
    else
        call s:_f.throw('upcspec', a:str)
    endif
endfunction
"▶1 increq
function s:F.inqreq(req, key)
    if a:req isnot 0
        let a:req[a:key]=get(a:req, a:key, 0)+1
    endif
endfunction
"▶1 getats
let s:atargs={
            \'@': ['str', "''", 'begin', 'end', 'sbsdstart', 'sbsdsep',
            \      'sbsdend'],
            \'-': ['line', 0, 'begin', 'style'],
            \'.': ['char', 0, 'linestart', 'linenr', 'fold',
            \      'collapsedfiller', 'begin', 'end', 'sbsdstart', 'style'],
        \}
let s:atexpratargs={
            \'opts': 'a:opts',
        \}
function s:F.getats(str, opts, req, key, atargs, exprempty)
    let r=''
    let str=a:str
    while !empty(str)
        let atidx=stridx(str, '@')
        if atidx==-1
            let r.=str
            break
        elseif atidx!=0
            let r.=str[:(atidx-1)]
        endif
        let str=str[(atidx+1):]
        let atv=str[0]
        if atv is# '<'
            let idx=stridx(str, '@>@')
            if idx!=-1
                let expr=str[2:(idx-1)]
                try
                    let expr=s:F.getats(expr, a:opts, 0, 0, s:atexpratargs, -1)
                    let result=eval(expr)
                endtry
                if type(result)!=type('')
                    call s:_f.throw('atnstr')
                endif
                let arg=''
                let r.=s:F.getats(result, a:opts, a:req, a:key, a:atargs, -1)
                let str=str[(idx+3):]
            endif
        elseif atv is# '_' && str[1] is# '_'
            let arg=matchstr(str, '\v^_\w+\@@=', 1)
            if empty(arg)
                unlet arg
            else
                let str=str[len(arg)+2:]
                if a:req isnot 0
                    call s:F.inqreq(a:req, 'a:opts')
                endif
                let arg=(a:atargs.opts).'.'.arg
            endif
        elseif atv is# '_'
            let arg=matchstr(str, '\v^\l+\@@=', 1)
            if empty(arg)
                unlet arg
            else
                let str=str[len(arg)+2:]
                if type(a:opts[arg])==type('') || type(a:opts[arg])==type(0)
                    let arg=string(a:opts[arg])
                else
                    if a:req isnot 0
                        call s:F.inqreq(a:req, 'a:opts')
                    endif
                    let arg=(a:atargs.opts).'.'.arg
                endif
            endif
        elseif a:key is 0
            " Do nothing
        elseif has_key(s:atargs, atv) && str[1] is# '@'
            if index(s:atargs[atv], a:key, 2)==-1
                let arg=a:atargs[s:atargs[atv][0]]
            else
                let arg=s:atargs[atv][1]
            endif
            let str=str[2:]
        elseif atv=~#'\v^\l'
            let arg=matchstr(str, '\v^\l+%(\@)@=')
            if empty(arg)
                unlet arg
            else
                let str=str[len(arg)+1:]
                call s:F.inqreq(a:req, 'a:spec')
                let arg=a:atargs.spec.'[0].'.arg
            endif
        elseif atv is# '!'
            call s:F.inqreq(a:req, 'a:cf')
            if str[1] is# '!'
                call s:F.inqreq(a:req, 'sbsdstate')
                let arg=matchstr(str, '\v^\l+%(\@)@=', 2)
                let str=str[len(arg)+3:]
                let arg=(a:atargs.cf).'.sbsdstate.'.arg
            else
                call s:F.inqreq(a:req, 'state')
                let arg=matchstr(str, '\v^\l+%(\@)@=', 1)
                let str=str[len(arg)+2:]
                let arg=(a:atargs.cf).'.state.'.arg
            endif
        elseif atv is# 'S' && str[1] is# '@'
            call s:F.inqreq(a:req, 'a:spec')
            let arg=a:atargs.spec.'[0].'.(a:opts.usestylenames?('name'):('styleid'))
            let str=str[2:]
        elseif atv is# '.' && str[1:2] is# 'S@'
            call s:F.inqreq(a:req, 'a:cspec')
            let arg=a:atargs.cspec.'[0].'.(a:opts.usestylenames?('name'):('styleid'))
            let str=str[3:]
        elseif atv is# '$' && str[1] is# '@'
            if a:exprempty is 0
                call s:F.inqreq(a:req, '=')
                let r=substitute(r, '\.$', '', '')
                let r.="\nlet str\.="
            elseif a:exprempty is -1
                call s:_f.throw('noatdollar')
            endif
            let str=str[2:]
            let arg=''
        elseif atv is# '=' && str[1] is# '@'
            call s:F.inqreq(a:req, 'a:cur')
            call s:F.inqreq(a:req, 'a:opts')
            call s:F.inqreq(a:req, 'a:opts.strlen')
            if a:req isnot 0 && has_key(a:req, '=')
                let r.=a:atargs.opts.'.strlen('.a:atargs.cur.'.str)'
            else
                let r.=a:atargs.opts.'.strlen('.a:atargs.cur.')'
            endif
            let arg=''
            let str=str[2:]
        elseif atv is# '~'
            let arg=a:atargs.spec.'[0]'
            call s:F.inqreq(a:req, 'a:spec')
            let str=str[2:]
        elseif atv is# '^'
            let arg=a:atargs.cspec.'[0]'
            call s:F.inqreq(a:req, 'a:cspec')
            let str=str[2:]
        elseif atv is# ':'
            if a:key is# 'begin' || a:key is# 'end'
                let arg=a:atargs.style
            else
                let arg=a:atargs.spec.'[1]'
                call s:F.inqreq(a:req, 'a:spec')
            endif
            let str=str[2:]
        elseif atv is# '&' && str[1] is# '@'
            let arg=a:atargs.cspec.'[1]'
            call s:F.inqreq(a:req, 'a:cspec')
            let str=str[2:]
        endif
        if exists('arg')
            if arg[0] is# 'a'
                call s:F.inqreq(a:req, arg)
            endif
            let r.=arg
            unlet arg
        else
            let r.='@'
        endif
    endwhile
    return r
endfunction
"▶1 addexprchunk
function s:F.addexprchunk(prevchar, expr, exprchunk)
    if empty(a:exprchunk) || a:exprchunk is# '""' || a:exprchunk is# "''"
        return [a:prevchar, a:expr]
    elseif (a:prevchar is# "'" || a:prevchar is# '"') && a:exprchunk[0] is# a:prevchar
        return [a:exprchunk[-1:], a:expr[:-3].a:exprchunk[1:].'.']
    else
        return [a:exprchunk[-1:], a:expr.a:exprchunk.'.']
    endif
endfunction
"▶1 procpc
function s:F.procpc(cf, str, opts, key, req, atargs, escpc)
    let str=a:str
    let prevchar=0
    let expr=''
    while !empty(str)
        let pidx=stridx(str, '%')
        if pidx==-1
            let [prevchar, expr]=s:F.addexprchunk(prevchar, expr, s:F.squote(str))
            break
        elseif pidx!=0
            let [prevchar, expr]=s:F.addexprchunk(prevchar, expr, s:F.squote(str[:(pidx-1)]))
        endif
        let str=str[(pidx+1):]
        let [shift, chunk]=s:F.getexpr(a:cf, str, a:opts)
        let str=str[(shift):]
        if a:escpc
            let chunk=substitute(chunk, '%', '%%', 'g')
        endif
        let exprchunk=s:F.getats(chunk, a:opts, a:req, a:key, a:atargs, empty(expr))
        let [prevchar, expr]=s:F.addexprchunk(prevchar, expr, exprchunk)
    endwhile
    let expr=substitute(expr, '\v\.%(\n|$)@=', '', 'g')
    return expr
endfunction
"▶1 ftmcompile
"▶2 Create list of arguments
let s:keyargslist=['str', 'spec', 'line', 'char', 'cur', 'opts', 'style', 'cf']
let s:keynoargs={'str': s:atargs['@'][2:],
            \   'line': s:atargs['-'][2:],
            \   'char': s:atargs['.'][2:],
            \  'style': filter(copy(s:keylist), 'v:val isnot# "begin" && '.
            \                                   'v:val isnot# "end"')}
let s:keyargs={}
for s:key in s:keylist
    let s:keyargs[s:key]=[]
    for s:arg in s:keyargslist
        if has_key(s:keynoargs, s:arg) && index(s:keynoargs[s:arg], s:key)!=-1
            continue
        endif
        let s:keyargs[s:key]+=[s:arg]
    endfor
endfor
let s:keyargs.strescape=['str', 'opts']
let s:defatargs={}
call map(s:keyargslist, 'extend(s:defatargs, {v:val : "a:".v:val})')
unlet s:key s:arg s:keyargslist s:keynoargs
"▲2
function s:F.fmtcompileone(cf, str, opts, key)
    let r={'req': {}, 'isexpr': 1, 'str': a:str}
    let str=a:str
    "▶2 %!
    if str[0:1] is# '%!'
        let cmd=matchstr(str, '\v.{-}%(\!\%)@=', 2)
        let str=str[(4+len(cmd)):]
        let cmd=s:F.getats(cmd, a:opts, r.req, a:key, s:defatargs, -1)
        let r.isexpr=0
    endif
    "▲2
    let args=join(s:keyargs[a:key], ', ')
    let funstr='function r.f('.args.")\n"
    if exists('cmd')
        let funstr.=cmd."\n"
    endif
    let expr=s:F.procpc(a:cf, str, a:opts, a:key, r.req, s:defatargs, 0)
    let r.isconst=empty(filter(keys(r.req), 'v:val[0:5] isnot# "a:opts"'))
    if has_key(r.req, '=')
        let funstr.='let str='.expr."\nreturn str"
        let r.isexpr=0
    else
        let funstr.='return '.expr
    endif
    let funstr.="\nendfunction"
    execute funstr
    call a:cf.savefunc(a:key, funstr, r.f)
    return r
endfunction
"▶1 fmtprepare
function s:F.fmtprepare(type, options, slnr, elnr, sbsd)
    let format=s:formats[a:type]
    "▶2 s:F.getcolor
    if !has_key(s:F, 'getcolor')
        unlockvar 1 s:F
        if s:whatterm is# 'gui'
            function s:F.getcolor(color)
                return a:color
            endfunction
        else
            function s:F.getcolor(color)
                if a:color!~#'\v^\d+$'
                    return ''
                endif
                return get(s:colors, a:color, '')
            endfunction
        endif
        lockvar s:F
    endif
    "▶2 opts
    let opts={}
    let opts.strlen=get(format, 'strlen', s:_r.strdisplaywidth)
    call map(copy(get(format, 'addopts', {})), 'extend(opts, {"_".v:key : deepcopy(v:val)})')
    let id=hlID('Normal')
    let opts.fgcolor=s:F.getcolor(synIDattr(id, 'fg#', s:whatterm))
    let opts.bgcolor=s:F.getcolor(synIDattr(id, 'bg#', s:whatterm))
    if opts.fgcolor==''
        let opts.fgcolor=((&background is# 'dark')?('#ffffff'):('#000000'))
    endif
    if opts.bgcolor==''
        let opts.bgcolor=((&background is# 'dark')?('#000000'):('#ffffff'))
    endif
    call s:F.initopts(opts, a:options, format, a:sbsd)
    lockvar! opts
    "▲2
    let cf=s:cf.new(format, opts)
    return cf
endfunction
"▶1 s:formats
"▶2 HTML
let s:htmlreplaces={
            \'&': '&',
            \'"': '"',
            \'<': '<',
            \'>': '>',
        \}
let s:escapehtml='substitute(@@@,''['.join(keys(s:htmlreplaces),'').']'','.
            \              '''\=@__replaces@[submatch(0)]'',''g'')'
let s:htmlstylestr='((@inverse@)?'.
            \             '("color: ".'.
            \              '((@bgcolor@!=#"")?'.
            \                '(@bgcolor@):'.
            \                '(@_bgcolor@))."; background-color: ".'.
            \              '((@fgcolor@!=#"")?'.
            \                '(@fgcolor@):'.
            \                '(@_fgcolor@))."; "):'.
            \             '(((@fgcolor@!=#"")?'.
            \               '("color: ".@fgcolor@."; "):'.
            \               '("color: ".@_fgcolor@."; ")).'.
            \              '((@bgcolor@!=#"")?'.
            \               '("background-color: ".@bgcolor@."; "):'.
            \               '("background-color: ".@_bgcolor@."; ")))).'.
            \           '((@bold@)?'.
            \             '("font-weight: bold; "):'.
            \             '("")).'.
            \           '((@italic@)?'.
            \             '("font-style: italic; "):'.
            \             '("")).'.
            \           '((@underline@)?'.
            \             '("text-decoration: underline; "):'.
            \             '(""))'
let s:htmlinput=''
let s:formats.html={
            \'style':        '%>((@S@!=#"")?'.
            \                   '(".s".@S@." {".'.
            \                     s:htmlstylestr.
            \                   '."} "):'.
            \                   '(""))',
            \'begin':        "".
            \                ''.
            \                ''.
            \                ''.
            \                '<style type="text/css"> '.
            \                'body { font-family: %''@___fontfamily@''%; '.
            \                        'white-space: nowrap; '.
            \                        'margin: 0; padding: 0; border: 0;  '.
            \                        'color: %''@_fgcolor@''%; '.
            \                        'background-color: %''@_bgcolor@''% } '.
            \                'p { margin: 0; padding: 0; border: 0; '.
            \                    'color: %''@_fgcolor@''%; '.
            \                    'background-color: %''@_bgcolor@''%; '.
            \                    'text-indent: 0; white-space: pre; } '.
            \                'div { margin: 0; padding: 0; border: 0; '.
            \                      'color: %''@_fgcolor@''%; '.
            \                      'background-color: %''@_bgcolor@''%; '.
            \                      'white-space: pre; } '.
            \                '.open-fold   > .fulltext { display: block; }'.
            \                '.closed-fold > .fulltext { display: none;  }'.
            \                '.open-fold   > .toggle-open   {display: none; }'.
            \                '.open-fold   > .toggle-closed {display: block;}'.
            \                '.closed-fold > .toggle-open   {display: block;}'.
            \                '.closed-fold > .toggle-closed {display: none; }'.
            \                '.closed-fold:hover > .fulltext{display: block;}'.
            \                '.closed-fold:hover > .toggle-filler '.
            \                                              '{display: none;}'.
            \                '.Sign { width: 2.5ex; } '.
            \                '.SignImage { background-size: contain; '.
            \                             'background-repeat: no-repeat; '.
            \                             'background-position: center; }'.
            \                'span.Sign { display: inline-block; } '.
            \                '.Sign > image { max-width: 100%%; }'.
            \                '.Present { display: none; }'.
            \                '.Line { position: relative; zoom: 1; '.
            \                        'white-space: pre; }'.
            \                'input { display: inline; '.
            \                        'padding: 0; margin: 0; '.
            \                        'border: 0; background: none; '.
            \                        'font-family: inherit; '.
            \                        'font-size: 100%%; '.
            \                        'pointer-events: none; '.
            \                        'height: 100%%; }'.
            \                '.Line { min-height: 1em; } '.
            \                '.Line:hover .Shown   { display: none; }'.
            \                '.Line:hover .Present { display: inline; }'.
            \                '            .TagLink { margin-left: 1em; }'.
            \                '            .TagLink { display: none; }'.
            \                '.Line:hover .TagLink { display: inline; }'.
            \                '.s%''hlID("Conceal")''% { font-weight: normal; '.
            \                                          'font-style: normal; '.
            \                                          'text-decoration: none;'.
            \                                        '}'.
            \                '%''((@_allfolds@)?'.
            \                    '(".Fold {display:none;}'.
            \                      'a {white-space:pre;'.
            \                         'font-family:inherit;'.
            \                         'color:inherit;}'.
            \                      '.fulltext{white-space:nowrap;}"):'.
            \                    '("")).'.
            \                   '((@_sbsd@)?'.
            \                    '("table, tr, td { margin: 0; '.
            \                                      'padding: 0; '.
            \                                      'border: 0; } "):'.
            \                    '(""))''%'.
            \                '%:%''@___additionalcss@''%</style>'.
            \                '<title>%'''.substitute(s:escapehtml, '\V@@@',
            \                                        'eval(@___titleexpr@)', '').
            \                '''%</title>'.
            \                '%''((@_allfolds@)?('''.
            \                   '<script type="text/javascript">'.
            \                       'function toggleFold(event, objID) {'.
            \                           'var fold;'.
            \                           'fold=document.getElementById(objID);'.
            \                           'if(fold.className=="closed-fold")'.
            \                               '{fold.className="open-fold";'.
            \                                'if (event){event._doNotClose_=true;}}'.
            \                           'else {if (!event._doNotClose_){'.
            \                                   'fold.className="closed-fold";'.
            \                                   'if (event){event.stopPropagation();}}}'.
            \                  '}</script>'''.
            \                '):(""))''%'.
            \                ''.
            \                '%''((@_sbsd@)?(""):(""))''%',
            \'end':          '%''((@_sbsd@)?("
"):(""))''%'. \ '', \'linestart': '
'. \ '%''(@@@==0?join(map(copy('. \ 'get(@_curtags@,@-@,[])),'. \ '"''''"),'. \ '""):"")''%', \'linenr': printf(s:htmlinput, 'LineNR', \ '%''(@_linenumlen@+1)''%', \ '%#% ', ''), \'line': '%>empty(@S@)'. \ '?'.s:escapehtml. \ ':''''.'. \ s:escapehtml. \ '.''''', \'concealedstart': ''. \ '%s'. \ '', \'concealedend': '', \'lineend': '%''repeat("",'. \ 'len(get(@_curtags@,@-@,[]))*(@@@==0)).'. \ '('.'@___addlinkattagline@'. \ '&&!empty(get(@_curtags@,@-@))'. \ '&&@@@==0'. \ '?"'. \ ''. \ 'LINK'. \ ''. \ '"'. \ ':"")''%
', \'tagstart': '%''get(get(@_tags@,@@@,[]),0,'. \ '[{"__anchor":""}])[-1].__anchor''%', \'linkstart': '', \'linkend': '', \'tagend': '', \'foldcolumn': printf(s:htmlinput, 'FoldColumn', \ '%''@_foldcolumn@''%', \ '%''substitute(@@@,''\\V>'',''\\>'','. \ '''g'')''%', ''), \'fold': '%s'. \ '% %.-', \'difffiller': '%-', \'collapsedfiller': '%~ Deleted lines: %s %-', \'foldstart': '
'. \ '
'. \ '%s
'. \ '
', \'foldend': '
', \'strescape': s:escapehtml, \'sbsdstart': ''. \ '', \'sbsdsep': '%|'. \ '', \'sbsdend': '', \'sign': printf(s:htmlinput, \ 'Sign %''@__signstylelist@[@.@]''%', 2, '%s', ''), \'addopts': {'stylelist': ['Line', 'Fold', 'DiffFiller', \ 'CollapsedFiller', 'TrailLine'], \ 'signstylelist': ['SignAbsent', 'SignText', 'SignIcon'], \ 'replaces': s:htmlreplaces,}, \} unlet s:htmlreplaces "▶3 strlen function s:formats.html.strlen(str) let str=a:str let str=substitute(str, '', '\1', 'g') let str=substitute(str, '\m<.\{-}>', '', 'g') let str=substitute(str, '\m&.\{-};', '.', 'g') return s:_r.strdisplaywidth(str) endfunction "▶3 addopts.anchorescape " wiki-style .XX escapes function s:formats.html.addopts.anchorescape(char) let nr=char2nr(a:char) let nrchar=nr2char(nr) let r='' while nr let r .= printf('.%02x', nr%0x100) let nr = nr/0x100 endwhile " Caught character with diacritics if nrchar isnot# a:char && len(nrchar) ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of " > letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods ("."). let a:tag.__ename=substitute(a:tag.name, '[^a-zA-Z0-9_\-]', \ '\=a:opts._anchorescape(submatch(0))', 'g') endfunction "▶3 tagproc function s:formats.html.tagproc(opts, tag) call a:opts._addename(a:opts, a:tag) if a:opts.__usetagname let a:tag.__href='#'.a:tag.__ename else let a:tag.__href='#line'.a:tag._linenr.'-0' endif if !a:tag._incurf let fname=a:opts.strescape(eval(a:opts.__anchorfnameexpr)) let a:tag.__href=fname.a:tag.__href endif let a:tag.__anchor='' return a:tag endfunction "▶3 addoptsfun function s:formats.html.addoptsfun() return { \ 'usetagname': s:_f.getoption('HTMLUseTagNameInAnchor'), \ 'anchorfnameexpr': s:_f.getoption('HTMLAnchorFileNameExpr'), \'addlinkattagline': s:_f.getoption('HTMLAddLinkAtTagLine'), \ 'titleexpr': s:_f.getoption('HTMLTitleExpr'), \ 'fontfamily': s:_f.getoption('HTMLFontFamily'), \ 'additionalcss': s:_f.getoption('HTMLAdditionalCSS'), \} endfunction "▲3 if s:whatterm is# 'gui' function s:F.readfile(fname) if !filereadable(a:fname) return '' endif let escapedfile=shellescape(fnamemodify(a:fname, ':p')) if executable('file')==1 let mime=system('file --mime-type --brief -- '.escapedfile)[:-2] else let mime='image/'.tolower(fnamemodify(a:fname, ':e')) if mime!~#'\v^image\/[a-z\-]+$' let mime='image' endif endif if executable('base64')==1 && stridx(a:fname, "\n")==-1 let b64=system('base64 -w0 -- '.escapedfile) else let b64=s:_r.base64.encodelines(readfile(a:fname, 'b')) endif return 'data:'.mime.';base64,'.b64 endfunction let s:formats.html.addopts.readfile=s:F.readfile let s:formats.html.sign="%=((@.@==2)?". \"(''=%".printf(s:htmlinput, 'Sign SignImage', 2, ' ', \ 'style="background-image:url(%'''. \ '@__readfile@(@@@)''%)"')."%=''):". \"(''=%".s:formats.html.sign."%=''))=%" endif unlet s:htmlinput let s:styleattr='%''((!empty(@styleid@))?'. \ '(" style=\"".'.s:htmlstylestr.'."\""):'. \ '(""))''%' let s:formats['html-vimwiki']={ \'style': s:styleattr, \'begin': '
', \'end': '
', \'linestart': '', \'linenr': '%#% ', \'foldcolumn': '%s', \'lineend': '', \'fold': '%s% %.-', \'difffiller': '%-', \'collapsedfiller': '%~ Deleted lines: %s %-', \'strlen': s:formats.html.strlen, \'strescape': substitute(s:escapehtml, '&', '& ', ''), \'line': '%s', \'addopts': {'replaces': extend({' ': ' '}, \ s:formats.html.addopts.replaces)}, \} unlet s:styleattr unlet s:escapehtml s:htmlstylestr "▶2 BBcode (unixforum) let s:bbufostylestart= \'((@inverse@)?'. \ '((@bgcolor@!=#"")?("[color=".@bgcolor@):("")):'. \ '("[color=".((@fgcolor@!=#"")?(@fgcolor@):(@_fgcolor@))))."]".'. \'((@bold@)?("[b]"):("")).((@italic@)?("[i]"):(""))' let s:bbufostyleend='((@italic@)?("[/i]"):("")).'. \'((@bold@)?("[/b]"):("")).'. \'(((@inverse@ && (@bgcolor@!=#"")) || (!@inverse@))?'. \ '("[/color]"):(""))' let s:bbufoescape='substitute('. \ 'substitute('. \ 'substitute(@@@, "\\V&", "\\&", "g"), '. \ '"[", "\\[", "g"), '. \ '"]", "\\]", "g")' let s:formats["bbcode-unixforum"]={ \'begin': '%>((&background is# "dark")?'. \ '("[sh=".substitute(expand("%:p:~%"), "\\V[]",'. \ '''\="&#".char2nr(submatch(0)).";"'', "g")." '. \ '(Created by format.vim)]"):'. \ '("[codebox]"))', \'end': '%>((&background is# "dark")?'. \ '("[/sh]"):'. \ '("[/codebox]"))', \'linenr': '%>substitute(@:@, "%s", ''\='. \ 'repeat(@_leadingspace@, '. \ "@_linenumlen@-len(@-@)).". \ '@-@.@_leadingspace@'', "")', \'line': '%>substitute(@:@, "%s", ''\='.s:bbufoescape.''','. \ '"")', \'strescape': s:bbufoescape, \'style': '%>' . s:bbufostylestart. \ '."%s".'.s:bbufostyleend, \'sbsdsep': '%+%|', \'difffiller': '%-', \} unlet s:bbufostylestart s:bbufostyleend s:bbufoescape "▶3 stuf.bbstrlen function s:formats['bbcode-unixforum'].strlen(str) let str=a:str let str=substitute(str, '\m\[.\{-}\]', '', 'g') let str=substitute(str, '\m&[^;]\+;\|.', '.', 'g') return len(str) endfunction "▶2 LaTeX (xcolor) let s:texescape= \'substitute('. \ 'substitute(@@@, ''\v[\\\[\]{}&$_\^%#]'', '. \ '''\=''''\char''''.char2nr(submatch(0))."{}"'', '. \ '"g"),'. \'" ", ''\\enskip{}'', "g")' let s:texstylestart= \'((@inverse@)?'. \ '(''\colorbox[HTML]{''.'. \ '((@fgcolor@!=#"")?'. \ '(toupper(@fgcolor@[1:])):'. \ '(toupper(@_fgcolor@[1:])))."}{".'. \ '''\textcolor[HTML]{''.'. \ '((@bgcolor@!=#"")?'. \ '(toupper(@bgcolor@[1:])):'. \ '(toupper(@_bgcolor@[1:])))."}{"):'. \ '(((@bgcolor@!=#"")?'. \ '(''\colorbox[HTML]{''.toupper(@bgcolor@[1:])."}{"):'. \ '("")).'. \ '''\textcolor[HTML]{''.'. \ '((@fgcolor@!=#"")?'. \ '(toupper(@fgcolor@[1:])):'. \ '(toupper(@_fgcolor@[1:])))."}{"))' let s:texstyleend= \'repeat("}", '. \ '((@inverse@)?'. \ '(2):'. \ '((@bgcolor@!=#"")+1)))' let s:formats['latex-xcolor']={ \'begin': '\documentclass[a4paper,12pt]{article}'. \ '\usepackage[utf8]{inputenc}'. \ '\usepackage[HTML]{xcolor}'. \ '\pagecolor[HTML]{%''toupper(@_bgcolor@[1:])''%}'. \ '\color[HTML]{%''toupper(@_fgcolor@[1:])''%}'. \ '\begin{document}{\ttfamily\noindent', \'line': '%>'.s:texstylestart.".". \ s:texescape.".". \ s:texstyleend, \'lineend': '\\', \'end': '}\end{document}', \'strescape': s:texescape, \} unlet s:texstylestart s:texstyleend s:texescape "▶2 CSI let s:c={} if s:whatterm is# 'gui' function s:c.rgbtolist(color) return map(split(a:color[1:], '\v(..)@<='), 'eval("0x".v:val)') endfunction function s:c.distance(point1, point2) " For all x>0, y>0 sqrt(x)>sqrt(y) <=> x>y, thus no need in using " floating-point arithmetics. 255*255*3 which is the maximum value is " far beyond 32-bit border (needs 18 bits) return eval(join(map(map(range(len(a:point1)), \ 'a:point1[v:val]-a:point2[v:val]'), \ 'v:val*v:val'), \ '+')) endfunction function s:c.rgbtoterm(color) if !exists('s:colors') let s:colors=s:F.getcolors(s:F.getcolorfile()) lockvar! s:colors endif if !exists('s:colpoints') let s:colpoints=map(copy(s:colors), 'self.rgbtolist(v:val)') lockvar! s:colpoints let s:colmap={} elseif has_key(s:colmap, a:color) return s:colmap[a:color] endif let curcpoint=self.rgbtolist(a:color) let distances=map(copy(s:colpoints), 'self.distance(curcpoint, v:val)') let mindistance=min(distances) let r=index(distances, mindistance) let s:colmap[a:color]=r return r endfunction else function s:c.rgbtoterm(color) return index(s:colors, a:color) endfunction endif function s:c.tocsi(color, isbg) let colcode=self.rgbtoterm(a:color) if colcode==0 return "\e[".(10*a:isbg+39)."m" elseif colcode==16 return "\e[".(10*a:isbg+30)."m" elseif colcode<8 return "\e[".(10*a:isbg+30+colcode)."m" elseif colcode<16 return "\e[".(10*a:isbg+90+colcode-8)."m" else return "\e[".(10*a:isbg+38).";5;".colcode."m" endif endfunction function s:c.strlen(str) return s:_r.strdisplaywidth(substitute(a:str, "\e\\[[^m]*m", '', 'g')) endfunction let s:formats.csi={ \'style': '%>(@bold@ ?"\e[1m":"").'. \ '(@inverse@ ?"\e[7m":"").'. \ '(@underline@?"\e[4m":"").'. \ '(@italic@ ?"" :"").'. \ '(empty(@fgcolor@)?@[email protected](@_fgcolor@, 0):'. \ '@[email protected](@fgcolor@, 0)).'. \ '(empty(@bgcolor@)?@[email protected](@_bgcolor@, 1):'. \ '@[email protected](@bgcolor@, 1))', \'line': "%:%s\e[0m", \'lineend': '%:', \'strlen': remove(s:c, 'strlen'), \'addopts': {'funcs': s:c}, \} unlet s:c "▶2 vimorg-tagged let s:formats['vimorg-tagged']={ \'line': '%s', \'end': '%>@!foundtagsstr@', \'tagend': '%>@!recordtag@(@_tags@,@@@)', \'state': {}, \'addopts': {'anchorescape': s:formats.html.addopts.anchorescape}, \'tagproc': s:formats.html.addopts.addename, \'haslf': 1, \} function s:formats['vimorg-tagged'].state.init(opts) let self.foundtags={} let self.foundtagsstr='' let self.lastfound=1 let self.helpprefix=s:_f.getoption('VOHelpPrefix') let self.helpsuffix=s:_f.getoption('VOHelpSuffix') let self.anchorexpr=s:_f.getoption('VOHelpAnchorExpr') endfunction function s:formats['vimorg-tagged'].state.recordtag(tags, tag) if !has_key(a:tags, a:tag) || empty(a:tags[a:tag]) return '' endif if has_key(self.foundtags, a:tag) return ' ['.self.foundtags[a:tag].']' endif let tagnr=self.lastfound let self.lastfound+=1 let self.foundtags[a:tag]=tagnr let tag=a:tags[a:tag][0][-1] let tname=a:tag let helplink='['.tagnr.'] '. self.helpprefix . fnamemodify(tag._tfname, ':t') . self.helpsuffix let helplink.='#'.eval(self.anchorexpr) let self.foundtagsstr.="\n".helplink return ' ['.tagnr.']' endfunction "▶2 tokens let s:formats.tokens={ \'begin': "%>string(['b', @~@, expand('%'), bufnr('%')])", \'sbsdstart': "['ss', %'string(@_vertseparator@)'%, ", \'foldstart': "['fs', %:, %s, %C]", \'foldend': "['fe', %:, %s, %C]", \'linestart': "['%'@__linetypes@[@@@]'%', %:, ", \'foldcolumn': "['fc', %s, %:], ", \'sign': "['sc', %s, %:, %C], ", \'linenr': "['ln', %s, %:], ", \'tagstart': "['ts', %s ], ", \'line': "['l' , %s, %:], ", \'concealedstart': "['cs', %s, %:], ", \'concealedend': "['ce', %s, %:], ", \'tagend': "['te', %s ], ", \'fold': "['f' , %s, %:], ", \'difffiller': "['df', '', %:], ", \'collapsedfiller': "['cf', %s, %:], ", \'style': '%>string(@~@)', \'lineend': "]", \'sbsdsep': ", ", \'sbsdend': "]", \'end': "%>string(['e', @~@, @_tags@])", \'strescape': "string(@@@)", \'addopts': {'linetypes': ['lr', 'lf', 'ld', 'lc']}, \} "▶1 ishlcleared let s:cleared_dict={ \ 'fgcolor': '', \ 'bgcolor': '', \ 'bold': '', \ 'italic': '', \ 'underline': '', \ 'inverse': '', \} function s:F.ishlcleared(specdict) return empty(filter(copy(s:cleared_dict), 'v:val isnot# a:specdict[v:key]')) endfunction "▶1 getspecdict function s:F.getspecdict(hlname, ...) if type(a:hlname)==type([]) let specdicts=map(copy(a:hlname), 's:F.getspecdict(v:val)') let r=specdicts[0] call map(specdicts[1:], 'extend(l:, {"r": call(s:F.mergespecdicts, [r, v:val]+a:000, {})})') if !empty(filter(copy(a:hlname), 'v:val[0] is# "!"')) let spellidx=filter(map(copy(a:hlname), '[v:key, v:val]'), 'v:val[1][0] is# "!"')[0][0] if spellidx > 0 && spellidx < len(a:hlname)-1 for attr in ['bgcolor', 'fgcolor'] if !empty(specdicts[spellidx-1][attr]) && empty(specdicts[spellidx+1][attr]) && \!s:F.ishlcleared(specdicts[spellidx+1]) let r[attr]=specdicts[spellidx-1][attr] endif endfor endif endif return r endif if a:hlname[0] is# '!' let hlname=a:hlname[1:] else let hlname=a:hlname endif let id=synIDtrans(hlID(hlname)) return { \ 'name': hlname, \ 'styleid': id, \ 'fgcolor': s:F.getcolor( \ synIDattr(id, 'fg#', s:whatterm)), \ 'bgcolor': s:F.getcolor( \ synIDattr(id, 'bg#', s:whatterm)), \ 'bold': synIDattr(id, 'bold'), \ 'italic': synIDattr(id, 'italic'), \ 'underline': synIDattr(id, 'underline'), \ 'inverse': synIDattr(id, 'inverse'), \} endfunction "▶1 mergespecdicts function s:F.mergespecdicts(oldspecdict, newspecdict, ...) return { \'name': a:oldspecdict.name. '_'.a:newspecdict.name, \'styleid': a:oldspecdict.styleid.'_'.a:newspecdict.styleid, \'fgcolor': ((empty(a:newspecdict.fgcolor) && !a:0) \ ?(a:oldspecdict.fgcolor) \ :(a:newspecdict.fgcolor)), \'bgcolor': ((empty(a:newspecdict.bgcolor)) \ ?(a:oldspecdict.bgcolor) \ :(a:newspecdict.bgcolor)), \'bold': a:oldspecdict.bold || a:newspecdict.bold, \'italic': a:oldspecdict.italic || a:newspecdict.italic, \'underline': a:oldspecdict.underline || a:newspecdict.underline, \'inverse': a:oldspecdict.inverse || a:newspecdict.inverse, \} endfunction "▶1 redrawprogress function s:F.redrawprogress() if !get(s:progress, 'showprogress', 0) return 0 endif let barlen=((winwidth(0))- \((s:progress.showprogress==2)? \ (len(s:progress.elnr)*2+10): \ (8))) let colnum=barlen*s:progress.linesprocessed/ \ s:progress.linestoprocess let s:progress.oldcolnum=0 let bar='['.repeat('=', s:progress.oldcolnum).'>'. \repeat(' ', barlen-s:progress.oldcolnum).'] '. \((s:progress.showprogress==2)? \ (repeat(' ', len(s:progress.elnr)- \ len(s:progress.clnr)). \ (s:progress.clnr). \ '/'.(s:progress.elnr).' '): \ ('')). \repeat(' ', 3-len(s:progress.progress)). \(s:progress.progress).'%%' endfunction "▶1 cmppos function s:F.cmppos(pos1, pos2) return ((a:pos1[0]a:pos2[0]) \ ?(1) \ :((a:pos1[1]a:pos2[1]) \ ?(1) \ :(0))))) endfunction "▶1 matchestoevents function s:F.matchestoevents(matches) let lineevents={} for mtch in a:matches if mtch.startpos ==# mtch.endpos continue endif let lineevents[mtch.startpos[0]]= \extend(get(lineevents, mtch.startpos[0], {}), \ { \ mtch.startpos[1]: \ add(get(get(lineevents, mtch.startpos[0], {}), \ mtch.startpos[1], []), \ ['push', mtch]), \ }) let lineevents[mtch.endpos[0]]= \extend(get(lineevents, mtch.endpos[0], {}), \ { \ mtch.endpos[1]: \ add(get(get(lineevents, mtch.endpos[0], {}), \ mtch.endpos[1], []), \ ['pop', mtch]), \ }) endfor return lineevents endfunction "▶1 ItemsNrSort function s:ItemsNrSort(i1, i2) return a:i1[0]>a:i2[0]?1:-1 endfunction let s:_functions+=[function('s:ItemsNrSort')] "▶1 nritems function s:F.nritems(dct) return map(items(a:dct), '[+v:val[0], v:val[1]]') endfunction "▶1 eventstosplcolumns function s:F.eventstosplcolumns(lineevents) let splcolumns={} let stack=[] let emptymtch={} let oldmtch=emptymtch for [lnr, columnevents] in sort(s:F.nritems(a:lineevents), function('s:ItemsNrSort')) let splcolumns[lnr]={} for [col, cevents] in sort(s:F.nritems(columnevents), function('s:ItemsNrSort')) for [type, mtch] in cevents if type is# 'pop' let i=-1 while stack[i] isnot mtch let i-=1 endwhile call remove(stack, i) elseif type is# 'push' call add(stack, mtch) endif if empty(stack) let mmtch=emptymtch else let mmtch=stack[0] for cmtch in stack[1:] if s:CmpMatches(mmtch, cmtch)==-1 let mmtch=cmtch endif endfor endif if mmtch isnot oldmtch let splcolumns[lnr][col]=[['matchborder', mmtch is emptymtch ? 0 : mmtch.group]] let oldmtch=mmtch endif endfor endfor endfor call filter(splcolumns, '!empty(v:val)') return splcolumns endfunction "▶1 CmpMatches function s:CmpMatches(m1, m2) " Note: sorting must be stable " Note2: It is impossible for two dictionaries to have equal .num keys. Thus if a:m1.num is not " greater then a:m2.num then it is definitely lesser. return a:m1.priority>a:m2.priority \?1 \:(a:m1.num>a:m2.num \ ?1 \ :-1) endfunction let s:_functions+=[function('s:CmpMatches')] "▶1 regescapeslash function s:F.regescapeslash(s) return substitute(a:s, '\v%(\\@1||(col('$')!=1&&a:scol!=col('$')))], \'empty(v:val)'. \ '?extend(self.pasteol, {'. \ a:slnr.'+v:key : matchdct.group'. \ '})'. \ ':0') if has_key(self, 'pointer') while self.pointer (FormatVimFindNextBadlySpelledWord) ]s nnoremap (FormatVimFoundBadlySpelledWord) FoundBadlySpelledWord() try call cursor(a:slnr, 1) if empty(spellbadword()[1]) normal! ]s endif let l:empty='' while line('.') <= a:elnr let [word, type]=spellbadword() let a:d.spelltype=type call a:d.recordmatch(line('.'), col('.'), word) execute 'normal '. \"\(FormatVimFindNextBadlySpelledWord)". \"\(FormatVimFoundBadlySpelledWord)" if !s:found_badly_spelled_word break endif let s:found_badly_spelled_word=0 endwhile finally let &l:wrapscan=saved_wrapscan call winrestview(winview) nunmap (FormatVimFindNextBadlySpelledWord) nunmap (FormatVimFoundBadlySpelledWord) let s:found_badly_spelled_word=0 endtry endfunction "▶1 formatmisspells function s:F.formatmisspells(slnr, elnr) let d={'recordmatch': s:F.recordspellmatch, 'splcolumns': {}} call s:F.collectmisspells(a:slnr, a:elnr, d) return d.splcolumns endfunction "▶1 Collect matches if v:version>703 || (v:version==703 && has('patch627')) "▶2 function s:F.collectmatches(slnr, elnr, mtch, d) if type(a:mtch)!=type({}) || has_key(a:mtch, 'pattern') let regex=((type(a:mtch)==type(''))?(a:mtch):(a:mtch.pattern)) let save_history=&history if save_history let &history+=1 let lasthistitem=histget('/', -1) endif let save_atslash=@/ let winview=winsaveview() try execute 'lockmarks keepmarks keepjumps silent '.a:slnr.','.a:elnr \'s/'.regex.'/\='. \ 'a:d.recordmatch(line("."), col("."), submatch(0))/gne' " Too long pattern error catch /\m^Vim(substitute):E339:/ throw 'TOO LONG REGEX' finally if save_history let &history=save_history if histget('/', -1) isnot# lasthistitem call histdel('/', -1) endif endif let @/=save_atslash call winrestview(winview) endtry else let i=1 while has_key(a:mtch, 'pos'.i) let pos=a:mtch['pos'.i] if a:slnr <= pos[0] && pos[0] <= a:elnr if len(pos) == 1 call a:d.recordmatch(pos[0], 1, getline(pos[0])) call a:d.recordmatch(pos[0], col([pos[0], '$']), '') else if pos[1] <= col([pos[0], '$']) let match=matchstr(getline(pos[0]), '\v\C.{-}%(.%>'.pos[2].'c|$)', \ pos[1]-1) call a:d.recordmatch(pos[0], pos[1], match) endif endif endif let i+=1 endwhile endif endfunction let s:F.collectonelinematches=s:F.collectmatches function s:F.multilinewrapper(lnr, col, match) dict let lines=split(a:match, "\n", 1) if len(lines)==1 return self.recordmlmatch(a:lnr, a:col, lines) endif let lnr=a:lnr let realmatch=[] let idx=a:col-1 while !empty(lines) let line=getline(lnr) let chunk=remove(lines, 0) if empty(lines) call add(realmatch, chunk) break else " If we did not hit NUL chunk represents match from starting column (first column in " case of non-starting line) to the end of line. if line[(idx):] is# chunk call add(realmatch, chunk) else " If we did match was split at NUL (as it is represented as newline). We need to " determine how many NULs were there. Alternative and, probably, better approach " would be comparing len(chunk) to len(line)-idx in the same cycle. Note that " match may be actually one line match, but contain NULs. let chunk.="\n".remove(lines, 0) while line[(idx):] isnot# chunk && !empty(lines) let chunk.="\n".remove(lines, 0) endwhile call add(realmatch, chunk) endif " In non-starting line match starts in first column let idx=0 endif let lnr+=1 endwhile call self.recordmlmatch(a:lnr, a:col, realmatch) endfunction function s:F.collectmultilinematches(slnr, elnr, mtch, d) let a:d.recordmatch=s:F.multilinewrapper call s:F.collectmatches(a:slnr, a:elnr, a:mtch, a:d) endfunction else "▶2 function s:F.collectonelinematches(slnr, elnr, regex, d) let winview=winsaveview() try let lnr=a:slnr while lnr call cursor(lnr, 1) let lnr=search(a:regex, 'nWc', a:elnr) if lnr let line=getline(lnr) let notinlinestr="\n\n" while stridx(line, notinlinestr)!=-1 let notinlinestr.="\n" endwhile let matches=[] let chunks=split(substitute(line, \ a:regex, \ '\=[notinlinestr, add(matches, submatch(0))][0]', \ 'g'), notinlinestr, 1) let col=1 for m in matches let col+=len(remove(chunks, 0)) call a:d.recordmatch(lnr, col, m) let col+=len(m) endfor let lnr+=1 endif endwhile catch /\m^Vim(let):E339:/ throw 'TOO LONG REGEX' finally call winrestview(winview) endtry endfunction endif "▶1 collecttags function s:F.collecttags(slnr, elnr, tags, tagregexpref, tagregexsuf, d) let tagregex=join(map(map(copy(a:tags), 'v:val.name'), 'escape(v:val, "\\")'), '\|') let tagregex=escape(a:tagregexpref.tagregex.a:tagregexsuf, '/') try call s:F.collectonelinematches(a:slnr, a:elnr, tagregex, a:d) catch /\m\c^TOO LONG REGEX$/ let tlen=len(a:tags) if tlen==1 call s:_f.warn('toolongpat') else call s:F.collecttags(a:slnr, a:elnr, a:tags[:(tlen/2)], a:tagregexpref, a:tagregexsuf, \ a:d) call s:F.collecttags(a:slnr, a:elnr, a:tags[(tlen/2+1):], a:tagregexpref, a:tagregexsuf, \ a:d) endif endtry endfunction "▶1 formatlinks function s:F.formatlinks(opts, slnr, elnr) let splcolumns={} for [regex, expr] in a:opts.linkregexes let d={'recordmatch': s:F.recordlinkmatch, 'splcolumns': {}, 'expr': expr} try call s:F.collectonelinematches(a:slnr, a:elnr, s:F.regescapeslash(regex), d) endtry let splcolumns=s:F.mergesplcolumns(splcolumns, d.splcolumns) endfor return d.splcolumns endfunction "▶1 formattags function s:F.formattags(opts, slnr, elnr, starttagreg, endtagreg) "▶2 Объявление переменных let fname=expand('%:.') " Имя обрабатываемого файла let tags=taglist('.') " Список тёгов if empty(tags) return [{}, {}, {}, []] endif let tag2flmap={} " Словарь: имя тёга — список местонахождений let fcontents={} " Кэш содержимого файлов " Список символов, которых надо дополнительно экранировать let addescapes=s:_f.getoption('AddTagCmdEscapes') let curfl2tagsmap={} " Словарь: номер линии (в этом файле) — список тёгов let filetags={} " Dictionary mapping file names to list of " tags contained there and commands to find them let splcolumns={} " Dictionary mapping lines to specialcolumns dictionary that maps " columns to name of tags that start or stop at given position let winview=winsaveview() "▶2 Process tags in the current file try "▶3 Init some tag keys and filter tags call map(tags, 'extend(v:val, {"_incurf":('. \ 'extend(v:val, {"_tfname": fnamemodify(v:val.filename, ":.")})'. \ '._tfname is# fname)})') if a:opts.ignoretags call filter(tags, '!v:val._incurf') endif "▶3 Generate prefix and suffix of the regular expression let tagregexpref='\C\V' if !empty(a:starttagreg) let tagregexpref.=a:starttagreg.'\zs' endif let tagregexpref.='\k\@a:elnr || linenr1 call insert(tag2flmap[tag.name], \remove(tag2flmap[tag.name], -1)) endif else call remove(tag2flmap[tag.name], -1) endif endif "▶3 Тёг находится в другом файле elseif filereadable(tag._tfname) let tfname=tag._tfname if tag.cmd[0] is# '/' let filetags[tfname]=add(get(filetags, tfname, []), \ [tag, tag2flmap[tag.name], len(tag2flmap[tag.name])-1, \ tag2flmap[tag.name][-1]]) else let linenr=(+matchstr(tag.cmd, '\v^\d+')) if linenr call insert(tag2flmap[tag.name][-1], [tfname, linenr]) let tag._linenr=linenr else call remove(tag2flmap[tag.name], -1) endif endif "▶3 Файл, в котором должен находится тёг, не существует else call remove(tag2flmap[tag.name], -1) endif endfor finally call winrestview(winview) endtry "▶2 Tags in the files that should be searched for " WARNING: tags list below is modified for [tfname, tags] in items(filetags) let fc=readfile(tfname, 'b') call map(tags, 'add(v:val, '. \'"\\m".escape(substitute(v:val[0].cmd, ''\m^/\|/$'', "", "g"), addescapes))') let linenr=1 for line in fc try if empty(filter(tags, \'line=~#v:val[-1]' \ .'? empty(insert(v:val[-2], [tfname, linenr]))' \ .': !empty(extend(v:val[0], {"_linenr": linenr}))')) break endif catch endtry let linenr+=1 endfor call map(reverse(tags), 'remove(v:val[1], v:val[2])') endfor unlet filetags tags "▶2 Удаление лишних записей call filter(usedtags, 'has_key(v:val, "_linenr")') call map(tag2flmap, 'filter(v:val, "has_key(v:val[-1], \"_linenr\")")') call filter(tag2flmap, '!empty(v:val)') let maxduptags=s:_f.getoption('MaxDupTags') if maxduptags call filter(tag2flmap, 'type(get(v:val, '.maxduptags.', 0))=='.type(0)) endif return [tag2flmap, curfl2tagsmap, splcolumns, usedtags] endfunction "▶1 getsigns function s:F.getsigns(buf) let defined={} let placed={} redir => placedstr silent execute 'sign place buffer='.a:buf redir END for [line, id, name] in map(filter(split(placedstr, "\n"), \ 'v:val[:3] is# " "'), \ 'map(split(v:val[4:]), '. \ '"v:val[stridx(v:val, ''='')+1:]")') if !has_key(defined, name) redir => defstr silent execute 'sign list' name redir END let defstr=defstr[(6+len(name)):] let defined[name]={'id': name} " TODO check icons with spaces for prop in split(defstr) let eqidx=stridx(prop, '=') let defined[name][prop[:(eqidx-1)]]=prop[(eqidx+1):] endfor if s:whatterm isnot# 'gui' && has_key(defined[name], 'icon') unlet defined[name].icon endif endif let placed[line]=[+id, defined[name]] endfor return [defined, placed] endfunction "▶1 cf let s:cf={} "▶2 cf.new function s:cf.new(format, opts) let cf=extend(copy(self), {'format': a:format, \ 'opts': a:opts, \ 'vars': {}, \ 'nextvar': 'a', \ 'cache': {}, \ 'stylestr': ''}) if !a:opts.minimizefunc call map(copy(s:cf), 'v:key[-6:] is# "_nomin" && empty(extend(cf, {v:key[:-7]: v:val}))') endif if has_key(a:format, 'sbsdstate') let cf.sbsdstate=deepcopy(a:format.sbsdstate) if exists('*cf.sbsdstate.init') call cf.sbsdstate.init(cf.opts) endif endif if cf.opts.funcfile isnot 0 && filereadable(cf.opts.funcfile) call writefile([], cf.opts.funcfile) endif return cf endfunction "▶2 cf.savefunc function s:cf.savefunc(fname, ftext, Func) if !self.opts.debugging return endif let ftext=(type(a:ftext)==type([]) ? a:ftext : split(a:ftext, "\n")) if self.opts.breaks isnot 0 if has_key(self.opts.breaks, a:fname) let breakarg=matchstr(string(a:Func), '''\@<=.*''\@=') for v in self.opts.breaks[a:fname] if type(v)==type(0) execute 'breakadd func' v breakarg else if a:fname is# 'compiledformat' let v=self.expr(v) endif let lnr=1 for line in ftext[1:] if line=~#v execute 'breakadd func' lnr breakarg endif let lnr+=1 endfor endif endfor endif endif if self.opts.funcfile isnot 0 let firstline=substitute(ftext[0], '\v(^function\ )@<=([^(]+)', 'Compiled:'.a:fname, 'g') let lastline=ftext[-1] let ftext=add(insert(map(ftext[1:-2],'repeat(" ",shiftwidth()).v:val'),firstline),lastline) let self.fcontents=extend(get(self, 'fcontents', []), ftext) endif endfunction "▶2 cf.writefunc function s:cf.writefunc() if self.opts.funcfile is 0 return endif if self.opts.saveopts let optslst=['let opts={}'] let keys=sort(keys(self.opts)) let maxklen=max(map(copy(keys), 'len(v:val)')) for k in keys call add(optslst, printf('let opts.%-*s = %s', maxklen, k, string(self.opts[k]))) endfor else let optslst=[] endif call writefile(optslst+get(self, 'fcontents', []), self.opts.funcfile) endfunction "▶2 cf.compile function s:cf.compile(slnr, elnr, options, sbsd) let cformat={} let self.cformat=cformat let cformat.strescape=s:F.fmtcompileone(self, '%>'.get(self.format, 'strescape', '@@@'), \ self.opts, 'strescape') unlockvar self.opts call s:F.initstrescapeopts(self.opts, self) call s:F.initfileopts(self.opts, a:slnr, a:elnr, a:options, self.format, a:sbsd) lockvar! self.opts for key in s:keylist if has_key(self.format, key) let cformat[key]=s:F.fmtcompileone(self, self.format[key], self.opts, key) lockvar! cformat[key] if self.opts.profiling || self.opts.debugging call add(s:profiled, cformat[key].f) endif endif endfor for key in ['haslf', 'nolf'] let cformat[key]=get(self.format, key, 0) lockvar cformat[key] endfor let cformat.compiledspec=s:F.compilespecfunc(self) lockvar! cformat if self.opts.profiling || self.opts.debugging call add(s:profiled, cformat.compiledspec) endif unlockvar self.opts call s:F.initcfopts(self.opts, self) lockvar! self.opts if has_key(self.format, 'state') let self.state=deepcopy(self.format.state) if exists('*self.state.init') call self.state.init(self.opts) endif endif endfunction "▶2 cf.has function s:cf.has(key) return has_key(self.cformat, a:key) endfunction "▶2 cf.newvar function s:F.inc(next) if empty(a:next) return add(a:next, 'a') endif let c=a:next[-1] if c is# '9' call remove(a:next, -1) return add(s:F.inc(a:next), 'a') elseif c is# 'Z' if len(a:next)>1 let a:next[-1]='0' else call remove(a:next, -1) return add(s:F.inc(a:next), 'a') endif elseif c is# 'z' let a:next[-1]='A' else let a:next[-1]=nr2char(char2nr(c)+1) endif return a:next endfunction function s:cf.newvar(var) if a:var !~# '\v^(\a\:)@!\a\w*$' throw 'Invalid variable name: '.a:var endif if !has_key(self.vars, a:var) let self.vars[a:var]=self.nextvar let i=len(self.nextvar)-1 let self.nextvar=join(s:F.inc(split(self.nextvar, '\v.@=')), '') endif return self.vars[a:var] endfunction function s:cf.newvar_nomin(var) return a:var endfunction "▶2 cf.getvar function s:cf.getvar(var) let v=matchstr(a:var, '\v^(\a\:)@!\w+') if empty(v) return a:var elseif !has_key(self.vars, v) throw 'No variable: '.v endif return self.vars[v] endfunction "▶2 cf.getvar_nomin function s:cf.getvar_nomin(var) return a:var endfunction "▶2 cf.expr function s:cf.expr(expr, ...) return substitute(a:expr, '\v\%(\%|\-?\d+|\a\w*)', \ '\=submatch(1) is# "%" ? '. \ '"%" : '. \ (a:0&&a:1 \ ? 'self.newvar(submatch(1))' \ : 'self.getvar(submatch(1))'), \ 'g') endfunction "▶2 cf.specstr function s:cf.specstr(list) if type(a:list)==type('') return 'call(%cformat.compiledspec,[%cf]+'.a:list.',{})' else return '%cformat.compiledspec(%cf,'.join(a:list,',').')' endif endfunction "▶2 cf.get function s:cf.get(key, ...) if self.cformat[a:key].isconst return s:F.squote(call(self.cformat[a:key].f, a:000+[self.opts, self], {})) elseif self.cformat[a:key].isexpr let atargs=copy(s:defatargs) call map(s:keyargs[a:key][:-3], 'extend(atargs,{v:val : a:000[v:key]})') let atargs.opts='%opts' let atargs.cf='%cf' return s:F.procpc(self, self.cformat[a:key].str, self.opts, a:key, 0, atargs, 1) endif return '%cformat.'.a:key.'.f('.join(a:000, ',').',%opts,%cf)' endfunction "▶2 cf.getnrstr function s:cf.getnrstr(nrvarstr) return ((self.opts.dornr)? \ ((self.opts.donr)? \ ('(('.a:nrvarstr.'=='.self.opts.cline.')?'. \ self.opts.cline.' : '. \ ('abs('.a:nrvarstr.'-'.self.opts.cline.')').')'): \ ('abs('.a:nrvarstr.'-'.self.opts.cline.')')): \ (a:nrvarstr)) endfunction "▶2 cf.hasreq function s:cf.hasreq(key, reqs) let reqs=self.cformat[a:key].req for req in a:reqs if has_key(reqs, req) return 1 endif endfor return 0 endfunction "▶1 gettagreg function s:F.gettagreg(options, st) let tagreg=((a:options[a:st.'tagreg'] is 0)? \ (s:_f.getoption(toupper(a:st[0]).a:st[1:].'TagReg')): \ (a:options[a:st.'tagreg'])) if tagreg is 0 let tagreg='' else let tagreg='\m\%('.tagreg.'\)\V' endif return tagreg endfunction "▶1 ff let s:ff={} let s:ffcomp={} "▶2 ffcomp.c function s:ffcomp.c(cmd, arg) return get(self._cmds, a:cmd, a:cmd). \((a:arg =~# '\v^[a-zA-Z0-9_@!]')?(' '):('')).a:arg endfunction "▶2 ff.expr function s:ff.expr(...) return call(self.__cf.expr, a:000, self.__cf) endfunction "▶2 ff.__let function s:ff.__let(var, type, expr) return self._out()._deeper('let',self.expr(a:var, 1),a:type,self.expr(a:expr)) \._out() endfunction "▶2 ff.let function s:ff.let(var, expr) return self.__let(a:var, '', a:expr) endfunction "▶2 ff.__leta function s:ff.__leta(var, type, expr) if !empty(self._l) && self._l[-1][0] is# 'let' && self._l[-1][2] is# a:type let var=self.expr(a:var, 1) let expr=self.expr(a:expr) let lblock=self._l[-1] if lblock[1][0] is# '[' let lblock[1]=lblock[1][:-2].','.var.']' let lblock[3]=lblock[3][:-2].','.expr.']' else let lblock[1]='['.lblock[1].','.var.']' let lblock[3]='['.lblock[3].','.expr.']' endif return self else return call(self.__let, [a:var, a:type, a:expr], self) endif endfunction "▶2 ff.leta " TODO: Automatically determine validity of concatenating lets by recording variables used by right " expression function s:ff.leta(var, expr) return self.__leta(a:var, '', a:expr) endfunction "▶2 ff.letabreak function s:ffcomp.letabreak(...) " Do nothing endfunction function s:ff.letabreak() return self._out()._deeper('letabreak')._out() endfunction "▶2 ff.letspec function s:ff.letspec(spname, ...) return self.leta('%'.a:spname.'spec', self.__cf.specstr(map(copy(a:000),'"''".v:val."''"'))) endfunction "▶2 ff.letcf function s:ff.letcf(var, ...) if self.__cf.has(a:1) return self.leta(a:var, call(self.__cf.get, a:000+["''"], self.__cf)) else return self.leta(a:var, "''") endif endfunction "▶2 ff.appendcf function s:ff.appendcf(var, ...) if self.__cf.has(a:1) return self.append(a:var, call(self.__cf.get, a:000+[a:var], self.__cf)) else return self endif endfunction "▶2 ff.letc function s:ff.letc(var, ...) let self.__curvar=a:var if a:0>1 return call(self.letcf, [self.__curvar]+a:000, self) else return call(self.let, [self.__curvar, a:1], self) endif endfunction "▶2 ff.appendc function s:ff.appendc(...) if a:0>1 return call(self.appendcf, [self.__curvar]+a:000, self) else return call(self.append, [self.__curvar, a:1], self) endif endfunction "▶2 ff.for function s:ff.for(var, expr) return call(self.__orig.for, [self.expr(a:var, 1), self.expr(a:expr)], self) endfunction "▶2 ff.call, .while, .if, .elseif, .addif, .strappend, .echo, .echomsg, .echon, .unlet for s:f in ['call', 'while', 'if', 'elseif', 'addif', 'strappend', 'echo', 'echomsg', 'echon', \'unlet'] execute 'function s:ff.'.s:f."(...)\n". \' return call(self.__orig.'.s:f.', '. \ 'map(copy(a:000), "self.expr(v:val)"), '. \ "self)\n". \'endfunction' endfor unlet s:f "▶2 ff.increment function s:ff.increment(var, ...) return self.__let(a:var, '+', get(a:000, 0, 1)) endfunction "▶2 ff.decrement function s:ff.decrement(var, ...) return self.__let(a:var, '-', get(a:000, 0, 1)) endfunction "▶2 newff function s:F.newff(cf) let r=s:_r.new_constructor() let r.__orig=filter(copy(r), 'type(v:val)==2') call extend(r, s:ff) call extend(r._comp, s:ffcomp) let r.append=r.strappend let r.__cf=a:cf if !a:cf.opts.minimizefunc let r.__leta=r.__let endif return r endfunction "▶1 s:NRSort :: Integer, Integer → -1|1 " We really don’t care about the order of equal integers function s:NRSort(a, b) return a:a>a:b ? 1 : -1 endfunction let s:_functions+=['s:NRSort'] "▶1 mergesplcolumns function s:F.mergesplcolumns(splcolumns1, splcolumns2) if empty(a:splcolumns1) if empty(a:splcolumns2) return {} else return deepcopy(a:splcolumns2) endif elseif empty(a:splcolumns2) return deepcopy(a:splcolumns1) endif let splcolumns=deepcopy(a:splcolumns1) call map(copy(a:splcolumns2), \'has_key(splcolumns, v:key)'. \ '?map(copy(v:val), '. \ '"has_key(splcolumns[".v:key."], v:key)'. \ '?extend(splcolumns[".v:key."][v:key], v:val)'. \ ':extend(splcolumns[".v:key."], {v:key : v:val})")'. \ ':extend(splcolumns, {v:key : v:val})') return splcolumns endfunction "▶1 mergeallsplcolumns function s:F.mergeallsplcolumns(splcolumns1, ...) let d={} let d.ret=deepcopy(a:splcolumns1) if a:0==0 return empty(d.ret)?{}:d.ret endif for splcolumns in a:000 let d.ret=s:F.mergesplcolumns(d.ret, splcolumns) unlet splcolumns endfor return d.ret endfunction "▶1 flattensplcolumns function s:F.flattensplcolumns(lowsplcolumns, highsplcolumns) let splcolumns=s:F.mergesplcolumns(a:lowsplcolumns, a:highsplcolumns) if empty(a:lowsplcolumns) || empty(a:highsplcolumns) return splcolumns endif " FIXME Make sure they do not overlap return splcolumns endfunction "▶1 initfileopts function s:F.initfileopts(opts, slnr, elnr, options, format, sbsd) let opts=a:opts " TODO Control it with a:options? let opts.sbsd=a:sbsd "▶2 Intended display width let columns=0+(((a:options.columns)+0)? \ (a:options.columns): \ (-1)) if columns==-1 let columns=max(map(range(1, line('$')), 'virtcol([v:val,''$''])-1') \ +[s:_f.getoption('MinColumns')]) endif let opts.columns=columns "▶2 Folds let foldcolumn=0 if !opts.ignorefolds && has_key(a:format, 'foldcolumn') " TODO Support for formatting foldcolumn using .line let foldcolumn=((a:options.foldcolumn==-2)? \ (s:_f.getoption('FoldColumn')): \ (a:options.foldcolumn)) if foldcolumn==-1 let foldcolumn=&foldcolumn endif endif let opts.foldminlines=&foldminlines let opts.foldcolumn = foldcolumn let opts.columns += foldcolumn "▶2 Concealed characters let formatconcealed=0 if has('conceal') && &conceallevel if a:options.concealed is -1 let formatconcealed=s:_f.getoption('FormatConcealed') elseif a:options.concealed is 'both' let formatconcealed=2 elseif a:options.concealed is 'shown' let formatconcealed=1 else let formatconcealed=a:options.concealed endif endif if formatconcealed==2 \&& !(has_key(a:format, 'concealedstart') || has_key(a:format, 'concealedend')) let formatconcealed=1 endif let opts.formatconcealed=formatconcealed let opts.conceallevel=&conceallevel "▶2 Cursor let opts.ignorecursor=(a:options.cursor==-1? \ s:_f.getoption('IgnoreCursor'): \ !a:options.cursor) let opts.cline=line('.') let hascline=(a:slnr<=opts.cline && opts.cline<=a:elnr) let opts.docline=(!opts.ignorecursor && &cursorline && hascline) let opts.hascline=(!opts.ignorecursor && hascline) if formatconcealed && !opts.ignorecursor let opts.formatconcealedcursor=stridx(&concealcursor, 'n')==-1 else let opts.formatconcealedcursor=0 endif let opts.checkcline=opts.docline || opts.formatconcealedcursor "▶2 Line numbers if has_key(a:format, 'linenr') let opts.donr=((a:options.number==-1)? \ (s:_f.getoption('NoLineNR')): \ (!a:options.number)) if opts.donr!=-1 let opts.donr=!opts.donr endif let opts.dornr=((a:options.relativenumber==-1)? \ (s:_f.getoption('RelativeNumber')): \ (a:options.relativenumber)) if opts.dornr==-1 if exists('+relativenumber') let opts.dornr=&relativenumber else let opts.dornr=0 endif endif if opts.donr==-1 let opts.donr=&number endif let opts.dosomenr=(opts.donr || opts.dornr) if opts.dosomenr if opts.donr let opts.linenumlen=max([len(a:elnr), &numberwidth-1]) elseif opts.dornr let opts.linenumlen=max([len(a:elnr-a:slnr), &numberwidth-1]) endif let opts.columns+=1+opts.linenumlen else let opts.linenumlen = 0 endif else " TODO Add support for formatting line numbers using .line let opts.donr = 0 let opts.dornr = 0 let opts.dosomenr = 0 let opts.linenumlen = 0 endif "▶2 Structures with tags if opts.ignoretags!=2 let starttagreg=s:F.gettagreg(a:options, 'start') let endtagreg=s:F.gettagreg(a:options, 'end') let [opts.tags, opts.curtags, opts.tagssplcolumns, usedtags]= \s:F.formattags(opts, a:slnr, a:elnr, starttagreg, endtagreg) if has_key(a:format, 'tagproc') call map(usedtags, 'a:format.tagproc(opts, v:val)') endif let opts.dotags=!empty(opts.tagssplcolumns) else let opts.tags={} let opts.curtags={} let usedtags=0 let opts.dotags=0 let opts.tagssplcolumns=0 endif "▶2 Structures with link if opts.formatlinks let opts.linkssplcolumns=s:F.formatlinks(opts, a:slnr, a:elnr) let opts.dolinks=!empty(opts.linkssplcolumns) else let opts.linkssplcolumns=0 let opts.dolinks=0 endif "▶2 Structures with matches if opts.formatsomematch let [opts.matchessplcolumns, opts.matchespasteol]=s:F.formatmatches(opts, a:slnr, a:elnr) let opts.domatches=!empty(opts.matchessplcolumns) else let opts.domatches=0 let opts.matchespasteol=0 let opts.matchessplcolumns=0 endif "▶2 Structures with spell if opts.formatspell let opts.spellsplcolumns=s:F.formatmisspells(a:slnr, a:elnr) let opts.dospell=!empty(opts.spellsplcolumns) else let opts.spellsplcolumns=0 let opts.dospell=0 endif "▶2 'listchars' let listchars={} let npregex='\v\t|\p@!.' if &list && !((a:options.list)? \ (s:_f.getoption('IgnoreList')): \ (!a:options.list)) let opts.list=1 call map(map(split(&listchars, \'\v\,%(%(eol|tab|trail|extends|precedes|nbsp|conceal|space)\:)@='), \'matchlist(v:val,''\v^(\w+)\:(.*)$'')[1:2]'), \'extend(listchars, {v:val[0]: substitute(v:val[-1],''\\\(.\)'',''\1'',"g")})') if has_key(listchars, 'nbsp') let npregex=substitute(npregex, '\\t', '\0| ', '') endif else let opts.list=0 let listchars.tab=' ' endif let opts.npregex=npregex let opts.listchars=listchars let opts.spsplcolumns=s:F.formatspecialchars(a:opts, a:slnr, a:elnr) if empty(opts.spsplcolumns) if has_key(opts.listchars, 'nbsp') unlet opts.listchars.nbsp endif endif if has_key(listchars, 'space') let opts.spacesplcolumns=s:F.formatspaces(a:slnr, a:elnr) if empty(opts.spacesplcolumns) unlet opts.listchars.space endif else let opts.spacesplcolumns=0 endif "▶2 special columns let opts.splcolumns=s:F.mergeallsplcolumns(opts.linkssplcolumns, \ opts.tagssplcolumns, \ opts.matchessplcolumns, \ opts.spsplcolumns, \ opts.spellsplcolumns, \ opts.spacesplcolumns) let opts.existingspecials={} call map(copy(opts.splcolumns), \'map(copy(v:val), '. \ '''map(copy(v:val), '. \ '"extend(opts.existingspecials, {v:val[0]: 1})")'')') if has_key(opts.listchars, 'trail') let opts.existingspecials.trail=1 endif "▶2 Signs let dosigns=0 if has_key(a:format, 'sign') " TODO Support for formatting signs column using .line let dosigns=((a:options.signs==-1)? \ (!s:_f.getoption('IgnoreSigns')): \ (a:options.signs)) if dosigns let gsr=s:F.getsigns(bufnr('%')) let opts.signdefinitions=gsr[0] let opts.placedsigns=gsr[1] if empty(opts.placedsigns) let dosigns=0 endif let opts.columns+=2 endif endif let opts.dosigns=dosigns "▲2 "▶2 Progress bar: bar length let opts.barlen=((winwidth(0))- \((opts.showprogress==2)? \ (len(a:elnr)*2+10): \ (8))) if opts.barlen<0 let opts.showprogress=0 endif "▲2 let opts.dolinemergehl=(opts.dodiff || opts.docline || opts.dosigns) let opts.domergehl=(opts.dolinemergehl || opts.domatches || opts.dospell) return opts endfunction "▶1 initstrescapeopts function s:F.initstrescapeopts(opts, cf) let opts=a:opts let opts.strescape_=a:cf.cformat.strescape.f function! opts.strescape(s) dict return self.strescape_(a:s, self) endfunction let opts.leadingspace = opts.strescape(' ') let opts.difffillchar = opts.strescape(opts.fillchars.diff) let opts.foldfillchar = opts.strescape(opts.fillchars.fold) let opts.vertseparator = opts.strescape(opts.fillchars.vert) let opts.qleadingspace = s:F.squote(opts.leadingspace) return opts endfunction "▶1 initcfopts function s:F.initcfopts(opts, cf) let opts=a:opts "▶2 persistentfiller let opts.persistentfiller=0 if opts.dodiff if opts.collapsafter let opts.persistentfiller=0 elseif a:cf.has('difffiller') let opts.persistentfiller=!a:cf.hasreq('difffiller', ['a:line','a:char','a:cur','a:cf']) else let opts.persistentfiller=1 endif endif "▶2 persistent fold column if a:cf.has('foldcolumn') let opts.persistentfdc=!a:cf.hasreq('foldcolumn', ['a:line', 'a:cf']) else let opts.persistentfdc=1 endif "▶2 persistent sign column if a:cf.has('sign') let opts.persistentsc=!a:cf.hasreq('sign', ['a:line', 'a:cf']) else let opts.persistentsc=1 endif "▲2 return opts endfunction "▶1 initopts function s:F.initopts(opts, options, format, sbsd) let opts=a:opts call filter(opts, 'v:key[:1] isnot# "__"') let opts.highlight=s:F.gethighlight() let opts.dodiff=&diff && (a:options.diff == -1 \ ? !s:_f.getoption('IgnoreDiff') \ : a:options.diff) let opts.usestylenames=s:_f.getoption('UseStyleNames') "▶2 Debugging/profiling let opts.profiling=v:profiling let opts.debugging=s:_f.getoption('Debugging') if opts.debugging let opts.funcfile = s:_f.getoption('Debugging_FuncF') let opts.minimizefunc = s:_f.getoption('Debugging_MinFu') let opts.breaks = s:_f.getoption('Debugging_Break') let opts.saveopts = s:_f.getoption('Debugging_SaveO') else let opts.funcfile = 0 let opts.minimizefunc = 1 let opts.breaks = 0 let opts.saveopts = 0 endif "▶2 Folds " Ignore folds if IgnoreFolds is set, there is no “fold” key or vim does not " support folding. let ignorefolds=((a:options.folds==-1)? \ (s:_f.getoption('IgnoreFolds')): \ (!a:options.folds)) || \!has('folding') let allfolds=!(ignorefolds || a:sbsd) && \((a:options.allfolds==-1)? \ (s:_f.getoption('AllFolds')): \ (a:options.allfolds)) && \(has_key(a:format, 'foldstart') || \ has_key(a:format, 'foldend')) let ignorefolds=ignorefolds || !has_key(a:format, 'fold') let opts.ignorefolds = ignorefolds let opts.allfolds = allfolds "▶2 fillchars let fillchars={} if has('windows') && has('folding') && (!ignorefolds || opts.dodiff || a:sbsd==1) let fcs=split(&fillchars, '\v\,%(%(stl%(nc)?|vert|fold|diff)\:)@=') for fc in fcs let [o, v]=matchlist(fc, '\v^(\w*)\:(.*)$')[1:2] let fillchars[o]=v endfor endif let opts.fillchars = extend({'diff': '-', 'fold': '-', 'vert': '|'}, fillchars) "▶2 Tags let opts.ignoretags=2 if has_key(a:format, 'tagstart') || has_key(a:format, 'tagend') if a:options.tags is -1 let opts.ignoretags=s:_f.getoption('IgnoreTags') elseif a:options.tags is# 'all' let opts.ignoretags=0 elseif a:options.tags is# 'local' let opts.ignoretags=1 else let opts.ignoretags=(2-a:options.tags) endif endif "▶2 Links let opts.formatlinks=0 if has_key(a:format, 'linkstart') || has_key(a:format, 'linkend') if a:options.links is -1 let opts.formatlinks=s:_f.getoption('FormatLinks') else let opts.formatlinks=a:options.links endif endif if opts.formatlinks let opts.linkregexes=s:_f.getoption('LinkRegexes') else let opts.linkregexes=[] endif "▶2 Matches let matches=((a:options.matches == -1)? \ (s:_f.getoption('FormatMatches')): \ (a:options.matches)) if matches is -1 if has('extra_search') && &hlsearch && (exists('v:hlsearch') \ ? v:hlsearch \ : 1) let matches='all' else let matches='matches' endif endif let opts.formatsearch = (matches is# 'all' || matches is# 'search') && !empty(@/) let opts.formatmatches = (matches is# 'all' || matches is# 'matches') && !empty(getmatches()) let opts.formatsomematch = opts.formatsearch || opts.formatmatches "▶2 Spelling support let opts.formatspell = &spell && (a:options.spell == -1 \ ? !s:_f.getoption('IgnoreSpell') \ : a:options.spell) if opts.formatspell try call spellbadword() catch /Vim(call):E756:/ let opts.formatspell=0 endtry endif "▶2 collapsafter if a:sbsd let opts.collapsafter=0 else let opts.collapsafter=((a:options.collapsfiller==-1)? \ (s:_f.getoption('CollapsFiller')): \ (a:options.collapsfiller)) if !has_key(a:format, 'difffiller') && has_key(a:format, 'collapsedfiller') let opts.collapsafter=1 elseif !has_key(a:format, 'collapsedfiller') let opts.collapsafter=0 endif endif "▲2 "▶2 addoptsfun if has_key(a:format, 'addoptsfun') call map(copy(a:format.addoptsfun()), 'extend(opts, {"__".v:key : deepcopy(v:val)})') endif "▶2 Progress bar let opts.showprogress=0 if has('statusline') if a:options.progress is -1 let opts.showprogress=s:_f.getoption('ShowProgress') elseif a:options.progress is 'lines' let opts.showprogress=2 elseif a:options.progress is 'percent' let opts.showprogress=1 else let opts.showprogress=a:options.progress endif endif let opts.canresize=(s:whatterm is# 'gui') "▲2 return opts endfunction "▶1 highlight function s:F.gethighlight() let highlight={} call map(split(&highlight, ','), 'extend(highlight, {v:val[0]: v:val[2:]})') " FIXME 'highlight' option does not necessary hold C:HiGroup pairs return map({ \'SpecialKey': '8', \'NonText': '@', \'Directory': 'd', \'ErrorMsg': 'e', \'IncSearch': 'i', \'Search': 'l', \'MoreMsg': 'm', \'ModeMsg': 'M', \'LineNr': 'n', \'CursorLineNr': 'N', \'Question': 'r', \'StatusLine': 's', \'StatusLineNC': 'S', \'Title': 't', \'VertSplit': 'c', \'Visual': 'v', \'VisualNOS': 'V', \'WarningMsg': 'w', \'WildMenu': 'W', \'Folded': 'f', \'FoldColumn': 'F', \'DiffAdd': 'A', \'DiffChange': 'C', \'DiffDelete': 'D', \'DiffText': 'T', \'SignColumn': '>', \'SpellBad': 'B', \'SpellCap': 'P', \'SpellRare': 'R', \'SpellLocal': 'L', \'Conceal': '-', \'Pmenu': '+', \'PmenuSel': '=', \'PmenuSbar': 'x', \'PmenuThumb': 'X', \}, 'get(highlight, v:val, v:key)') endfunction "▶1 initspecs function s:F.initspecs(ff, opts) "normalspec — Default format "specialspec — Format for special symbols, including lcs=tab и lcs=trail "ntspec — Format for lcs=eol и lcs=nbsp "foldspec — Folds format "fcspec — Fold column format (+folds) "nrspec — Line number format (&nu||&rnu) "fillspec — Deleted lines format (&diff) "clspec — Cursor line format (&cul) "cspec — Cursor format "conspec — Concealed characters format "scspec — Sign column format call a:ff.letspec('normal', 'Normal') if a:opts.formatconcealed call a:ff.letspec('con', a:opts.highlight.Conceal) endif if a:opts.dosigns call a:ff.letspec('sc', a:opts.highlight.SignColumn) endif "▶2 Folds if a:opts.foldcolumn call a:ff.letspec('fc', a:opts.highlight.FoldColumn) endif if !a:opts.ignorefolds || a:opts.allfolds call a:ff.letspec('fold', a:opts.highlight.Folded) endif "▲2 if a:opts.dodiff call a:ff.letspec('fill', a:opts.highlight.DiffDelete) endif "▶2 Spelling support if a:opts.dospell call a:ff.letspec('spb', a:opts.highlight.SpellBad) call a:ff.letspec('spc', a:opts.highlight.SpellCap) call a:ff.letspec('spr', a:opts.highlight.SpellRare) call a:ff.letspec('spl', a:opts.highlight.SpellLocal) endif "▶2 Line numbers if a:opts.dosomenr call a:ff.letspec('nr', a:opts.highlight.LineNr) if a:opts.hascline if v:version>703 || (v:version==703 && has('patch479')) call a:ff.letspec('nrcl', a:opts.highlight.CursorLineNr) else call a:ff.letspec('nrcl', a:opts.highlight.LineNr, 'CursorLine') endif endif endif "▲2 endfunction "▶1 compilespecfunc function s:F.compilespecfunc(cf) let opts=a:cf.opts let cformat=a:cf.cformat let d={} let hlnamearg='a:hlname' if opts.domergehl let hlnamearg='a:0?([a:hlname]+a:000):a:hlname' endif let specfunction=[ \'function d.compiledspec(cf, hlname'.((opts.domergehl)?(', ...'):('')).')', \ (opts.domergehl \ ?('let key=a:0?join([a:hlname]+a:000,''+''):(empty(a:hlname)?''-'':a:hlname)') \ :('let key=empty(a:hlname)?''-'':a:hlname')), \'if has_key(a:cf.cache,key)', \ 'retu a:cf.cache[key]', \'en', \((opts.docline)? \ ('let r=[call(s:F.getspecdict,'. \ '['.hlnamearg.']+'. \ '((a:hlname is#'.string(opts.highlight.LineNr).')?([0]):([])),{}),'''']'): \ ('let r=[s:F.getspecdict('.hlnamearg.'),'''']')), \] if a:cf.has('style') call add(specfunction, \'let r[1]=a:cf.cformat.style.f(key,r,'''',a:cf.opts,a:cf)') if (a:cf.has('begin') && a:cf.hasreq('begin', ['a:style'])) || \(a:cf.has('end') && a:cf.hasreq('end', ['a:style'])) call add(specfunction, 'let a:cf.stylestr.=r[1]') endif endif call extend(specfunction, [ \'let a:cf.cache[key]=r', \'retu r', \'endfunction']) execute join(specfunction, "\n") call a:cf.savefunc('compiledspec', specfunction, d.compiledspec) return d.compiledspec endfunction "▶1 gentrailinglines function s:F.gentrailinglines(ldiff, slnr, cf) let cformat=a:cf.cformat let opts=a:cf.opts let ldiff=a:ldiff let clnr=a:slnr+1 let ntspec=cformat.compiledspec(a:cf, 'NonText') if !opts.ignorefolds let fcspec=cformat.compiledspec(a:cf, 'FoldColumn') endif if opts.dosigns let scspec=cformat.compiledspec(a:cf, 'SignColumn') endif let r=[] while ldiff let curstr='' if a:cf.has('linestart') let curstr.=cformat.linestart.f(4, ntspec, clnr, curstr, opts, a:cf) endif if opts.foldcolumn let curstr.=cformat.foldcolumn.f(repeat(' ', opts.foldcolumn), fcspec, clnr, \ 0, curstr, opts, a:cf) endif if opts.dosigns let curstr.=cformat.sign.f(' ', scspec, clnr, 0, curstr, opts, a:cf) endif if opts.dosomenr " XXX " Do nothing: line numbers are not displayed in this case, neither any space " is skipped endif let curstr.=cformat.line.f('~', ntspec, clnr, 0, curstr, opts, a:cf) if a:cf.has('lineend') let curstr.=cformat.lineend.f(4, ntspec, clnr, 1, curstr, opts, a:cf) endif call add(r, curstr) let clnr+=1 let ldiff-=1 endwhile return r endfunction "▶1 Run side-by-side diff function s:F.sbsdrun(type, slnr, elnr, options, cf, sbsd) let opts=a:cf.opts let cformat=a:cf.cformat let normalspec=cformat.compiledspec(a:cf, 'Normal') let vsspec=cformat.compiledspec(a:cf, opts.highlight.VertSplit) "▶2 Используемые буфера let lastwin=winnr('$') let curwin=winnr() let i=1 let dwinnrs=[] while i<=lastwin if i!=curwin && getwinvar(i, '&diff') call add(dwinnrs, i) endif let i+=1 endwhile if dwinnrs==[] return [] endif "▶2 Получение реальных номеров линий в текущем буфере let clnr=1 let virtclnr=1 let maxline=line('$') let virtslnr=0 let virtelnr=0 while clnr<=maxline let virtclnr+=diff_filler(clnr) if !virtslnr && clnr==a:slnr let virtslnr=clnr endif if clnr==a:elnr let virtelnr=virtclnr break endif let virtclnr+=1 let clnr+=1 endwhile "▶2 call insert(dwinnrs, curwin) for dwinnr in dwinnrs if getwinvar(curwin, '&foldmethod') isnot# 'diff' || \getwinvar(dwinnr, '&foldmethod') isnot# 'diff' let a:options.ignorefolds=1 let ignorefolds=1 endif endfor let r=[] let i=2 let width=0 for dwinnr in dwinnrs "▶2 Получение номеров линий в другом буфере execute dwinnr.'wincmd w' let clnr=1 let virtclnr=1 let maxline=line('$') let dslnr=0 let dstartinfiller=0 let delnr=0 let notenoughlines=0 while clnr<=maxline+1 let filler=diff_filler(clnr) let virtclnr+=filler if !dslnr && virtclnr>=virtslnr let dstartinfiller=filler-(virtclnr-virtslnr) let dslnr=clnr endif if virtclnr>virtelnr let delnr=((clnr<=maxline)?(clnr):(maxline)) break endif let virtclnr+=1 let clnr+=1 endwhile if !delnr let delnr=line('$') let notenoughlines=1 endif "▶2 Получение отформатированных текстов if !opts.ignorefolds normal! zM endif let r2=s:F.format(a:type, dslnr, delnr, a:options, i, a:cf) "▶2 Добавление sbsdstart или sbsdsep let oldcolumns=opts.columns unlockvar opts.columns let opts.columns=width let width+=oldcolumns+1 unlockvar opts.sbsd let opts.sbsd=a:sbsd lockvar! opts if empty(r) let r=r2 if a:cf.has('sbsdstart') call map(r, 'cformat.sbsdstart.f(normalspec, v:key, "", '. \ 'opts, a:cf).v:val') endif else let r2=r2[(dstartinfiller):(len(r)-1+dstartinfiller)] if len(r2)1 let cf=a:2 else let cf=s:F.fmtprepare(a:type, a:options, slnr, elnr, sbsd) endif call cf.compile(slnr, elnr, a:options, sbsd) let cformat=cf.cformat let opts=cf.opts call s:F.compilespecfunc(cf) "▶2 side-by-side diff if sbsd==1 return s:F.sbsdrun(a:type, slnr, elnr, a:options, cf, sbsd) endif "▲2 "▶2 Precreation of deleted line if possible, here if opts.dodiff if opts.persistentfiller if cf.has('difffiller') let fillspec=cformat.compiledspec(cf, 'DiffDelete') let fillerstr=cformat.difffiller.f(opts.fillchars.fold, fillspec, 0, 0, '', opts, cf) else let fillerstr='' endif endif endif "▲2 let ff=s:F.newff(cf) "▶2 Initialize ff variables call ff.leta('%cf', 'a:cf') call ff.leta('%cformat', 'a:cformat') call ff.leta('%opts', 'a:opts') call ff.leta('%r', '[]') " List with formatted output call ff.leta('%clnr', slnr) " Line being processed if opts.domatches call ff.leta('%matchhlname', 0) call ff.leta('%oldmatchhlname', 0) endif if opts.dospell call ff.leta('%spellhlname', 0) call ff.leta('%oldspellhlname', 0) endif if !empty(opts.existingspecials) call ff.leta('%name2sptypemap', ff.expr(substitute(string(filter({ \ 'special': '%specialchar', \ 'tagstart': '%startedtag', \ 'tagend': '%endedtag', \ 'linkstart': '%startedlink', \ 'linkend': '%endedlink', \ 'spacestart': '%startedspace', \ 'spaceend': '%endedspace', \ 'spellborder': '%spellhlname', \ 'matchborder': '%matchhlname', \ 'trail': '%dummy', \ }, 'has_key(opts.existingspecials, v:key)')), ' ', '', 'g'), 1)) call ff.leta('%spcol', '[]') endif call ff.leta('%id', 0) call ff.leta('%concealinfo', '[0]') call ff.leta('%nocbreak', 0) call ff.leta('%concealedbreak', 0) call ff.letabreak() call s:F.initspecs(ff, opts) call ff.letabreak() "▶2 Progress bar support: init if opts.showprogress set laststatus=2 call ff.leta('%oldprogress', 0 ) call ff.leta('%linesprocessed', 0 ) call ff.leta('%linestoprocess', elnr-slnr+1 ) if !opts.canresize " Вторая часть прогресс бара " Старые значения % сделанного и длины строки из '='; " первая часть progress bar’а со строкой = call ff.leta('%oldcolnum', 0) call ff.leta('%barstart', string('[')) call ff.leta('%barlen', opts.barlen) call ff.leta('%barend', string(repeat(' ', opts.barlen).'] ')) else let s:progress.showprogress=opts.showprogress let s:progress.oldcolnum=0 let s:progress.clnr=slnr let s:progress.progress=0 let s:progress.elnr=elnr let s:progress.linesprocessed=0 let s:progress.linestoprocess=(elnr-slnr+1) endif endif "▶2 Precreation of all sign columns if possible, inside function if opts.dosigns if opts.persistentsc call ff.letcf('%nosignsc', 'sign', '" "', '%scspec', 0, 0) call ff.leta('%scols', '{}') call ff.for('[%sname,%sign]', 'items(%opts.signdefinitions)') call ff.if('has_key(%sign,''texthl'')') call ff.leta('%spec', cf.specstr(['%sign.texthl'])) call ff.else() call ff.leta('%spec', '%scspec') call ff.endif() if s:whatterm is# 'gui' call ff.if('has_key(%sign,''icon'')') call ff.letcf('%scols[%sname]', 'sign', '%sign.icon', '%spec', 0, 2) call ff._up() endif call ff.addif('has_key(%sign,''text'')') call ff.letcf('%scols[%sname]', 'sign','%sign.text','%spec', 0, 1) call ff.else() " XXX If sign is missing text attribute texthl attribute does not apply call ff.letcf('%scols[%sname]', 'sign', ''' ''', '%scspec', 0, 1) call ff.endif() call ff.endfor() endif endif "▶2 Folds if opts.allfolds || opts.foldcolumn call ff.leta('%fclnr', slnr) "▶3 Get folds closed at the moment if !opts.ignorefolds call ff.leta('%closedfolds', '{}') call ff.leta('%closedfoldslist', '[]') if !opts.allfolds call ff.leta('%closedfoldsends', '[]') endif call ff.while('%fclnr<='.elnr) call ff.if('foldclosed(%fclnr)!=-1') call ff.call('add(%closedfoldslist,%fclnr)') call ff.letc('%closedfolds[%fclnr]', 'linestart', 1, '%foldspec', '%fclnr') if !opts.foldcolumn if opts.dosigns call ff.appendc('sign', '" "', '%foldspec', '%fclnr', 0) endif if opts.dosomenr call ff.appendc('sign', '" "', '%foldspec', '%fclnr', 0) endif call ff.appendc('fold', 'foldtextresult(%fclnr)', '%foldspec', '%fclnr') call ff.appendc('lineend', 1, '%foldspec', '%fclnr', 0) endif if !opts.allfolds call ff.call('add(%closedfoldsends, foldclosedend(%fclnr))') if opts.showprogress call ff.decrement('%linestoprocess', '%closedfoldsends[-1]-%fclnr') endif call ff.leta('%fclnr', '%closedfoldsends[-1]') endif call ff.endif() call ff.increment('%fclnr') call ff.endwhile() endif "▲3 call setwinvar(0, '&foldminlines', 0) "▶3 Process other folds "▶4 Initializing fold column-related variables if opts.foldcolumn call ff.leta('%foldlevel', -1) call ff.leta('%fdchange', 0) call ff.leta('%foldlevels', '{}') call ff.leta('%foldcolumns', '{}') call ff.leta('%foldclosedcolumns', '{}') if opts.persistentfdc call ff.leta('%foldcolumns[-1]', 'repeat(['. \ cf.get('foldcolumn', \ 'repeat('' '','.opts.foldcolumn.')', \ '%fcspec', 0, -1, "''").'], 3)') else call ff.leta('%foldcolumnstarts', '{}') call ff.leta('%foldcolumns[-1]', repeat(' ', opts.foldcolumn)) endif endif "▶4 Initializing common variables call ff.leta('%possiblefolds', '{}') call ff.leta('&foldlevel', 0) call ff.leta('%oldfoldnumber', -1) call ff.leta('%foldnumber', 0) "▶4 Main cycle: getting all folds call ff.while('%oldfoldnumber!=%foldnumber') call ff.leta('%oldfoldnumber', '%foldnumber') call ff.leta('%fclnr', slnr) "▶5 Fold column if opts.foldcolumn if opts.foldcolumn>1 call ff.if('&foldlevel>='.(opts.foldcolumn-1)) endif call ff.leta('%rstart', '&foldlevel'.printf('%+d', -(opts.foldcolumn-3))) call ff.leta('%rend', '&foldlevel') call ff.let('%fdctext', \ '((%rstart<=%rend)?'. \ '((%rend<10)?'. \ '(join(range(%rstart,%rend),'''')):'. \ '((%rstart<10)?'. \ '(join(range(%rstart,9),'''').'. \ 'repeat(''>'',%rend-9))'. \ ':'. \ '(repeat(''>'','.(opts.foldcolumn-2).'))))'. \ ':'. \ '(''''))') call ff.let('%fdcnexttext', '%fdctext.((&foldlevel>=9)?'. \ '(''>''):'. \ '((&foldlevel)?'. \ '(&foldlevel+1):'. \ '(''|'')))') if opts.foldcolumn>1 call ff.leta('%fdcclosedtext', '((&foldlevel>='.opts.foldcolumn.')?'. \ '(((%rstart<=10)?'. \ '(%rstart-1):'. \ '(''>'')).%fdctext):'. \ '(repeat(''|'','.(opts.foldcolumn-1).'))).''+''') else call ff.leta('%fdcclosedtext', '''+''') endif call ff.leta('%fdctextend','repeat('' '','.(opts.foldcolumn-1).'-len(%fdctext))') if opts.foldcolumn>1 call ff.else() call ff.let('%fdctext', 'repeat(''|'',&foldlevel)') call ff.let('%fdcnexttext', '%fdctext.''|''') call ff.leta('%fdctextend','repeat('' '','.(opts.foldcolumn-1).'-len(%fdctext))') call ff.let('%fdcclosedtext', '%fdctext.''+''.%fdctextend') call ff.endif() endif call ff.append('%fdcnexttext', '%fdctextend') call ff.leta('%fdcopenedtext', '%fdctext.''-''.%fdctextend') if opts.persistentfdc call ff.let('%foldcolumns[&foldlevel]', '['. \ join(map(['%fdcclosedtext', '%fdcopenedtext', '%fdcnexttext'], \ 'cf.get("foldcolumn",v:val,"%fcspec",0,'. \ '"&foldlevel","''''")'), ',').']') else call ff.leta('%foldcolumns[&foldlevel]', '%fdcnexttext') endif endif "▶5 Obtaining folds positions call ff.while('%fclnr<='.elnr) call ff.if('foldclosed(%fclnr)>-1') call ff.leta('%foldend', 'foldclosedend(%fclnr)') if opts.allfolds let foldtextstr='foldtextresult(%fclnr)' if opts.dosomenr let formatstr='''%%'.opts.linenumlen.'u''' if opts.donr && opts.dornr let formatstr='%fclnr=='.opts.cline. \ '?'.substitute(formatstr, '%%', '%%-', ''). \ ':'.formatstr endif let foldtextstr='printf('.formatstr.','.cf.getnrstr('%fclnr').').'' ''.'. \ foldtextstr endif if opts.dosigns let foldtextstr=''' ''.'.foldtextstr endif if opts.foldcolumn let foldtextstr='%fdcclosedtext.'.foldtextstr endif call ff.leta('%foldtext', foldtextstr) if opts.foldminlines>0 call ff.if('%foldend-%fclnr>'.opts.foldminlines) endif if cf.has('foldstart') call ff.if('!has_key(%possiblefolds,%fclnr)') call ff.let('%possiblefolds[%fclnr]', '{}') call ff.endif() call ff.if('!has_key(%possiblefolds[%fclnr],''start'')') call ff.let('%possiblefolds[%fclnr].start', '[]') call ff.endif() call ff.call('add(%possiblefolds[%fclnr].start,'. \ cf.get('foldstart', '%foldtext', '%foldspec', '%fclnr', \ '&foldlevel', "''").')') endif if cf.has('foldend') call ff.leta('%foldinsbefore', '%foldend+1') call ff.if('!has_key(%possiblefolds,%foldinsbefore)') call ff.let('%possiblefolds[%foldinsbefore]', '{}') call ff.endif() call ff.if('!has_key(%possiblefolds[%foldinsbefore],''end'')') call ff.let('%possiblefolds[%foldinsbefore].end', '[]') call ff.endif() call ff.call('add(%possiblefolds[%foldinsbefore].end,'. \ cf.get('foldend', '%foldtext', '%foldspec', \ '%foldend', '&foldlevel', "''").')') endif if opts.foldminlines>0 call ff.endif() endif endif if opts.foldcolumn call ff.leta('%foldlevels[%fclnr]', '&foldlevel') call ff.if('!has_key(%foldlevels, %foldend+1)') call ff.let('%foldlevels[%foldend+1]', '&foldlevel-1') call ff.endif() let self.__curvar='%closedfolds[%fclnr]' if !opts.persistentfdc if !opts.ignorefolds call ff.if('has_key(%closedfolds, %fclnr)') call ff.appendc('foldcolumn', '%fdcclosedtext', '%fcspec', '%fclnr', \ '&foldlevel') if opts.dosigns call ff.appendc('sign', '" "', '%foldspec', '%fclnr', 0,) endif if opts.dosomenr call ff.appendc('linenr',cf.getnrstr('%fclnr'), '%foldspec', '%fclnr') endif call ff.appendc('fold', 'foldtextresult(%fclnr)', '%foldspec', '%fclnr') call ff.appendc('lineend', 1, '%foldspec', '%fclnr', 0) call ff.endif() endif call ff.letcf('%foldcolumnstarts[%fclnr]', 'foldcolumn', '%fdcopenedtext', \ '%fcspec', '%fclnr', '&foldlevel') elseif !opts.ignorefolds call ff.if('has_key(%closedfolds, %fclnr)') call ff.appendc('%foldcolumns[&foldlevel][0]') if opts.dosigns call ff.appendc('sign', '" "', '%foldspec', '%fclnr', 0) endif if opts.dosomenr call ff.appendc('linenr', cf.getnrstr('%fclnr'), '%foldspec', '%fclnr') endif call ff.appendc('fold', 'foldtextresult(%fclnr)', '%foldspec', '%fclnr') call ff.appendc('lineend', 1, '%foldspec', '%fclnr', 0) call ff.endif() endif endif call ff.let('%fclnr', '%foldend') call ff.increment('%foldnumber') call ff.endif() call ff.increment('%fclnr') call ff.endwhile() call ff.increment('&foldlevel') call ff.endwhile() endif "▶2 Main cycle: processing lines call ff.while('%clnr<='.(elnr+((opts.dodiff)?(1):(0)))) call ff.letc('%curstr', "''") if opts.checkcline call ff.leta('%iscline', '%clnr=='.opts.cline) endif if has_key(opts.listchars, 'space') call ff.leta('%startedspace', 0) call ff.leta('%didstartedspace', 0) call ff.leta('%endedspace', 0) endif "▶3 Fold column support if opts.foldcolumn if opts.dodiff call ff.leta('%fillfoldlevel', '%foldlevel') endif call ff.if('has_key(%foldlevels, %clnr)') call ff.let('%fdchange', 1) call ff.leta('%foldlevel', '%foldlevels[%clnr]') call ff.else() call ff.let('%fdchange', 0) call ff.endif() endif "▶3 Progress bar support if opts.showprogress if opts.canresize call ff.leta('%barlen', 'winwidth(0)-'.((opts.showprogress==2)? \ ((len(elnr)*2)+10): \ ('8'))) endif call ff.increment('%linesprocessed') call ff.leta('%progress', '100*%linesprocessed/%linestoprocess') call ff.leta('%colnum', ((opts.canresize)? \ ('%barlen'): \ (opts.barlen)). \ '*%linesprocessed/%linestoprocess') if opts.showprogress!=2 call ff.if('%progress!=%oldprogress || '. \ '%colnum!='.((opts.canresize)? \ ('s:progress.'): \ ('%')).'oldcolnum') endif if opts.canresize call ff.leta('%bar', '''[''.repeat(''='',%colnum).''>''.'. \ 'repeat('' '',%barlen-%colnum).''] ''') else call ff.if('%colnum!=%oldcolnum') call ff.append('%barstart', 'repeat("=",%colnum-%oldcolnum)') call ff.let('%barend', '%barend[%colnum-%oldcolnum :]') call ff.endif() call ff.let('%bar', '%barstart.">".%barend') endif call ff.append('%bar', ((opts.showprogress==2)? \ ('repeat('' '','.opts.linenumlen.'-len(%clnr))'. \ '.%clnr.''/'.elnr.' ''.'): \ ('')). \ 'repeat('' '',3-len(%progress)).%progress.''%%''') call ff.call('setwinvar(0,''&statusline'',%bar)') call ff.do('redrawstatus') if opts.showprogress!=2 call ff.endif() endif call ff.let('%oldprogress', '%progress') call ff.leta(((opts.canresize)?('s:progress.'):('%')).'oldcolnum', '%colnum') if opts.canresize call ff.leta('s:progress.progress', '%progress') call ff.leta('s:progress.linesprocessed', '%linesprocessed') if opts.showprogress==2 call ff.leta('s:progress.clnr', '%clnr') endif endif endif "▶3 Processing deleted lines if opts.dodiff call ff.leta('%filler', 'diff_filler(%clnr)') call ff.if('%filler>0') call ff.letcf('%curstrstart', 'linestart', \ 2.((opts.collapsafter)? \ '+(%filler>='.opts.collapsafter.')': \ ''), '%fillspec', '%clnr') "▶4 Leading columns (fold, sign, number) if opts.foldcolumn call ff.append('%curstrstart', ((opts.persistentfdc)? \ ('%foldcolumns[%fillfoldlevel][2]'): \ (cf.get('foldcolumn', \ '%foldcolumns[%fillfoldlevel]', \ '%fcspec', '%clnr', \ '%fillfoldlevel', \ '%curstrstart')))) endif if opts.dosigns call ff.append('%curstrstart', ((opts.persistentsc)? \ ('%nosignsc'): \ (cf.get('sign', '" "', '%scspec', \ '%clnr', 0, '%curstrstart')))) endif if opts.dosomenr call ff.appendcf('%curstrstart', 'linenr',"''", '%nrspec', '%clnr') endif "▶4 Filler if !opts.persistentfiller if opts.collapsafter call ff.if('%filler<'.opts.collapsafter) endif call ff.let('%curfil', '%filler') call ff.while('%curfil') call ff.let('%curstr', '%curstrstart') call ff.appendc('difffiller', s:F.squote(opts.fillchars.diff), \ '%fillspec', '%clnr', '%curfil') call ff.appendc('lineend', 2, '%fillspec', '%clnr',0) call ff.call('add(%r,%curstr)') call ff.decrement('%curfil') call ff.endwhile() if opts.collapsafter call ff.else() call ff.let('%curstr', '%curstrstart') call ff.appendc('collapsedfiller', '%filler', '%fillspec', '%clnr') call ff.appendc('lineend', 3, '%fillspec', '%clnr', 0) call ff.call('add(%r,%curstr)') call ff.endif() endif else call ff.let('%curstr', '%curstrstart') call ff.appendc(s:F.squote(fillerstr)) call ff.appendc('lineend', 2, '%fillspec', '%clnr', 0) call ff.increment('%r', 'repeat([%curstr],%filler)') endif "▲4 call ff.let('%curstr', "''") call ff.endif() call ff.if('%clnr>'.elnr) call ff.break() call ff.endif() endif "▶3 Processing folds if !opts.ignorefolds && !opts.allfolds && !opts.foldcolumn "▶4 call ff.if('foldclosed(%clnr)!=-1') call ff.letcf('%curstr', 'linestart', 1, '%foldspec', '%clnr') if opts.dosigns call ff.appendc('sign', '" "', '%foldspec', '%clnr', 0) endif if opts.dosomenr call ff.appendc('linenr', cf.getnrstr('%clnr'), '%foldspec', '%clnr') endif call ff.appendc('fold', 'foldtextresult(%clnr)', '%foldspec', '%clnr') call ff.appendc('lineend', 1, '%foldspec', '%clnr', 0) call ff.call('add(%r,%curstr)') call ff.let('%clnr', 'foldclosedend(%clnr)+1') call ff.continue() call ff.else() elseif opts.allfolds || opts.foldcolumn "▶4 if opts.allfolds call ff.if('has_key(%possiblefolds,%clnr)') call ff.let('%pf', '%possiblefolds[%clnr]') if cf.has('foldend') call ff.if('has_key(%pf,''end'')') call ff.increment('%r', '%pf.end') call ff.endif() endif if cf.has('foldstart') call ff.if('has_key(%pf,''start'')') call ff.increment('%r', '%pf.start') call ff.endif() endif call ff.endif() endif if !opts.ignorefolds call ff.if('!empty(%closedfoldslist)&&%clnr==%closedfoldslist[0]') call ff.call('remove(%closedfoldslist,0)') call ff.call('add(%r,%closedfolds[%clnr])') if !opts.allfolds call ff.let('%clnr', 'remove(%closedfoldsends,0)+1') call ff.decrement('%foldlevel') call ff.continue() endif call ff.endif() endif endif "▶3 Processing regular lines "▶4 Initializing variables call ff.let('%linestr', 'getline(%clnr)') call ff.let('%linelen', 'len(%linestr)') " Indicates that this line differs call ff.leta('%diffattr', ((opts.dodiff)?('diff_hlID(%clnr,1)'):(0))) if opts.formatconcealed call ff.leta('%concealdiff', 0) endif if !empty(opts.existingspecials) call ff.leta('%specialcolumns', 'deepcopy(get(%opts.splcolumns,%clnr,{}))') endif if opts.dodiff "▶5 " XXX diffid is taken from beyond the end of line because inside the line there may be " differences in highlighting: overall line is highlighted in one color and parts that " differ in the other call ff.if('%diffattr') call ff.let('%diffid', 'diff_hlID(%clnr,%linelen+1)') call ff.let('%diffhlname', 'synIDattr(%diffid,''name'')') call ff.let('%dspec', cf.specstr(['''Normal''','%diffhlname'])) call ff.else() call ff.let('%diffid', 0) call ff.endif() endif "▲5 if has_key(opts.listchars, 'trail') call ff.leta('%trail', 'match(%linestr,''\v\s*$'')') call ff.let('%specialcolumns[%trail+1]', \ 'add(get(%specialcolumns,%trail+1,[]),[''trail'',0])') endif "▶4 Line start "▶5 Find sign if opts.dosigns call ff.leta('%sign', 'get(get(%opts.placedsigns,%clnr,[]),1,{})') endif "▶5 Determine line spec " Full rules are listed later. Rules used here: " diff > cursorline > signs > normal " Anything but normal is cancelled by highlighting with higher priority. let linehlnamestrbase='[''Normal'']' let linehlnamestr=linehlnamestrbase if opts.dosigns let linehlnamestr='(has_key(%sign,''linehl'')'. \ '?['.linehlnamestrbase[1:-2].',%sign.linehl]'. \ ':'.linehlnamestr.')' endif if opts.docline " XXX CursorLine highlight group is NOT controlled by &highlight option let linehlnamestr='(%iscline'. \ '?['.linehlnamestrbase[1:-2].',''CursorLine'']'. \ ':'.linehlnamestr.')' endif if opts.dodiff let linehlnamestr='(%diffattr'. \ '?['.linehlnamestrbase[1:-2].',%diffhlname]'. \ ':'.linehlnamestr.')' endif let linehlnamestrorig=linehlnamestr call ff.let('%linehlname', linehlnamestr) let linehlnamestr='%linehlname' if linehlnamestrorig is# linehlnamestrbase call ff.leta('%linespec', '%normalspec') else call ff.let( '%linespec', cf.specstr(linehlnamestr)) endif "▲5 call ff.appendc('linestart', 0, '%linespec', '%clnr') if opts.foldcolumn "▶5 if opts.persistentfdc call ff.appendc('%foldcolumns[%foldlevel][2-%fdchange]') else call ff.if('has_key(%foldcolumnstarts,%clnr)') call ff.appendc('%foldcolumnstarts[%clnr]') call ff.else() call ff.appendc('foldcolumn', '%foldcolumns[%foldlevel]', '%fcspec', '%clnr', \ '%foldlevel') call ff.endif() endif endif if opts.dosigns "▶5 call ff.if('!empty(%sign)') if opts.persistentsc call ff.appendc('%scols[%opts.placedsigns[%clnr][1].id]') else call ff.let('%sign', '%opts.placedsigns[%clnr][1]') call ff.let('%sspec', 'has_key(%sign,''texthl'')?'. \ cf.specstr(['%sign.texthl']).':%scspec') if s:whatterm is# 'gui' call ff.addif('has_key(%sign,''icon'')') call ff.appendc('sign', '%sign.icon', '%sspec', '%clnr', 2) call ff._up() endif call ff.addif('has_key(%sign,''text'')') call ff.appendc('sign', '%sign.text', '%sspec', '%clnr', 1) call ff.else() call ff.appendc('sign','" "', '%scspec', '%clnr', 1) call ff.endif() endif call ff.else() if opts.persistentsc call ff.appendc('%nosignsc') else call ff.appendc('sign', '" "', '%scspec', '%clnr', 0) endif call ff.endif() endif if opts.dosomenr "▶5 call ff.appendc('linenr', cf.getnrstr('%clnr'), \ ((opts.hascline)? \ ('(%iscline?'. \ '%nrclspec :'. \ '%nrspec)'): \ ('%nrspec')), '%clnr') endif "▶4 Processing line text "▶5 Initialize variables call ff.leta('%cstartcol', 0) call ff.leta('%oldid', hlID('Normal')) if opts.dodiff call ff.leta('%olddiffid', '%diffid') endif if opts.formatconcealed call ff.leta('%oldconcealinfo', '[0]') if opts.formatconcealed==1 call ff.leta('%concealed', 0) endif endif call ff.leta('%curcol', 1) if opts.dosigns call ff.leta('%hlnamebase', 'has_key(%sign,''linehl'')?[%sign.linehl]:[]') endif call ff.leta('%startcol', '1') "▲5 "▶5 Initialize lists " XXX Concealed list MUST be initialized in one loop as otherwise synconcealed()[-1] will report " different values on each loop run if opts.formatconcealed let synconcealedstr='map(range(1,%linelen),''synconcealed(''.%clnr.'',v:val)'')' if opts.formatconcealedcursor let synconcealedstr='%iscline?repeat([[0]],%linelen):'.synconcealedstr endif call ff.leta('%synconcealed', synconcealedstr) endif "▲5 call ff.while('%curcol<=%linelen') "▶5 Initialize variables "▶6 idstr " Note: collecting all IDs and concealinfo at once with map(range()) appears to be much slower. let idstr='synID(%clnr,%curcol,1)' if has_key(opts.listchars, 'trail') let idstr='%curcol>%trail'.(opts.dolinemergehl? \ '&&len(%linehlname)==1': \ ''). \ '?'.hlID(opts.highlight.SpecialKey). \ ':'.idstr endif "▶6 Concealed characters break condition, with hack if opts.formatconcealed let concealedbreakstr='(%concealinfo[0]||%oldconcealinfo[0])'. \ '&&%concealinfo!=#%oldconcealinfo' if opts.conceallevel==2 " XXX This is a hack to make the following work as displayed: " :syntax match Group /alpha/ conceal cchar=a " :syntax match Group1 /p/ contained containedin=Group " With the above syntax code text `alpha` is matched as below: " alpha " ^^^^^ Group " ^ Group1 " It is displayed as "a" (*single* a) when cole=2 (and "a-a" when cole=1 thus no hack needed " for that level). " %nocbreak variable is used later to omit updating %oldconcealinfo let nocbreakstr='%concealinfo[0]'. \ '&&%oldconcealinfo[0]'. \ '&&!empty(%oldconcealinfo[1])'. \ '&&empty(%concealinfo[1])' let concealedbreakstr='!%nocbreak&&('.concealedbreakstr.')' endif endif "▲6 let whilecond= \ '%curcol<=%linelen&&!empty(extend(l:,{''%id'':'.idstr. \ (opts.dodiff \ ? ',''%diffid'':diff_hlID(%clnr,%curcol)' \ : ''). \ (opts.formatconcealed \ ? ',''%concealinfo'':%synconcealed[%curcol-1]' \ : ''). \ (!empty(opts.existingspecials) \ ? ',''%spcol'':get(%specialcolumns,%curcol,[])' \ : '').'}))'. \ (opts.formatconcealed && opts.conceallevel==2 \ ? '&&!empty(extend(l:,{''%nocbreak'':'.nocbreakstr.'}))' \ : ''). \ (opts.formatconcealed \ ? '&&!empty(extend(l:,{''%concealedbreak'':'.concealedbreakstr.'}))' \ : ''). \ '&&%id==%oldid'. \ (!empty(opts.existingspecials) \ ? '&&empty(%spcol)' \ : ''). \ (opts.dodiff \ ? '&&%diffid==%olddiffid' \ : ''). \ (opts.formatconcealed \ ? '&&!%concealedbreak' \ : ''). \ '&&!empty(extend(l:,{''%curcol'':%curcol+1}))' call ff.while(whilecond) call ff.endwhile() "▶5 Process breaks "▶6 Process special columns if !empty(opts.existingspecials) call ff.call('map(%spcol,'. \ '''extend(l:,{%name2sptypemap[v:val[0]]:v:val[1]})'')') endif "▲6 "▶6 Highlighting breaks in any case call ff.if('%startcol<%curcol'.(opts.formatconcealed==1?'&&!%concealed': '')) call ff.leta('%cstr', '%linestr[%startcol-1:%curcol-2]') let hlnamestr='[synIDattr(%oldid,''name'')]' let hlnamestradd='[]' if opts.dosigns let hlnamestradd='%hlnamebase' endif if opts.docline " XXX CursorLine highlight group is NOT controlled by &highlight option let hlnamestradd='(%iscline'. \ '?[''CursorLine'']'. \ ':'.hlnamestradd.')' endif if opts.dodiff let hlnamestradd='(%diffattr'. \ '?[synIDattr(%olddiffid,''name'')]'. \ ':'.hlnamestradd.')' endif if opts.domatches let hlnamestradd='(%oldmatchhlname is 0'. \ '?'.hlnamestradd. \ ' :[%oldmatchhlname])' endif if opts.dospell let hlnamestradd='(%oldspellhlname is 0'. \ '?[]'. \ ':["!".%oldspellhlname])+'.hlnamestradd endif if hlnamestradd isnot# '[]' let hlnamestr.='+'.hlnamestradd endif if has_key(opts.listchars, 'trail') call ff.if('%startcol-1>=%trail') call ff.let('%cstr', 'substitute(%cstr,'' '','. \ escape(s:F.squote(opts.listchars.trail),'\&~').',''g'')') if opts.domatches let trailhlnamestr='(%oldmatchhlname is 0'. \ '?('.hlnamestr.')'. \ ':[%oldmatchhlname])' call ff.leta('%curspec', cf.specstr(trailhlnamestr)) if has_key(opts.listchars, 'space') call ff._up() else call ff.else() endif else if has_key(opts.listchars, 'space') call ff.leta('%curspec', cf.specstr(hlnamestr)) endif call ff._up() endif endif if has_key(opts.listchars, 'space') call ff.addif('%endedspace||%didstartedspace') let sphlnamestr=s:F.getsphlnamestr(opts, hlnamestr, \ linehlnamestrorig, 1) call ff.let('%curspec', cf.specstr(sphlnamestr)) call ff.leta('%didstartedspace', '!%endedspace') call ff.leta('%endedspace', 0) call ff.leta('%cstr', 'substitute(%cstr,'' '','. \ escape(s:F.squote(opts.listchars.space),'\&~').',''g'')') call ff.elseif('%startedspace') call ff.leta('%startedspace', 0) call ff.leta('%didstartedspace', 1) call ff.leta('%curspec', cf.specstr(hlnamestr)) call ff.else() endif call ff.leta('%curspec', cf.specstr(hlnamestr)) if (has_key(opts.listchars, 'trail') && opts.domatches) || has_key(opts.listchars, 'space') call ff.endif() endif call ff.appendc('line', '%cstr', '%curspec', '%clnr', '%startcol') call ff.endif() "▲6 "▶6 Update oldid and startcol call ff.leta('%oldid', '%id') call ff.leta('%startcol', '%curcol') "▲6 "▶6 Tag/link breaks for type in (opts.dotags?['tag']:[])+(opts.dolinks?['link']:[]) for key in ['start', 'end'] if cf.has(type.key) let varname='%'.key.'ed'.type " startedtag/endedlink/… call ff.if('exists('.string(varname).')') call ff.appendc(type.key, varname, 0, '%clnr', '%curcol') call ff.unlet(varname) call ff.endif() unlet varname endif unlet key endfor unlet type endfor "▲6 "▶6 Concealed character breaks if opts.formatconcealed call ff.addif('%concealedbreak') let ccstrstr='%oldconcealinfo[1]' let ccstrlendiffstr='-!empty(%ccstr)' if opts.conceallevel==1 let ccstrstr='empty('.ccstrstr.')'. \ '?'.s:F.squote(get(opts.listchars,'conceal',' ')). \ ':('.ccstrstr.')' let ccstrlendiffstr='-1' elseif opts.conceallevel==3 let ccstrlendiffstr='' endif let concealdiffstr=s:strdisplaywidthstr.'('. \ '%linestr[%cstartcol-1:%curcol-2],'. \ '%cstartcol==1'. \ '?0'. \ ':'.s:strdisplaywidthstr.'(%linestr[:%cstartcol-2]))'. \ ccstrlendiffstr if opts.formatconcealed==1 call ff.if('%oldconcealinfo[0]') call ff.let('%concealed', 0) if opts.conceallevel!=3 call ff.leta('%ccstr', ccstrstr) if opts.conceallevel==2 call ff.if('!empty(%ccstr)') endif call ff.appendc('line', '%ccstr','%conspec','%clnr','%cstartcol') if opts.conceallevel==2 call ff.endif() endif endif call ff.increment('%concealdiff', concealdiffstr) call ff.endif() call ff.if('%concealinfo[0]') call ff.let('%concealed', 1) call ff.leta('%cstartcol', '%curcol') call ff.endif() " TODO elseif opts.formatconcealed==2 if cf.has('concealedend') call ff.if('%oldconcealinfo[0]') call ff.let('%ccstr', ccstrstr) call ff.appendc('concealedend','%ccstr','%conspec','%clnr','%curcol') call ff.increment('%concealdiff', concealdiffstr) call ff.endif() endif call ff.if('%concealinfo[0]') call ff.let('%cstartcol', '%curcol') if cf.has('concealedstart') call ff.appendc('concealedstart', \ substitute(ccstrstr, 'old', '', 'g'), \ '%conspec', '%clnr', '%curcol') endif call ff.endif() endif call ff.endif() endif "▲6 "▶6 Special characters if has_key(opts.existingspecials, 'special') call ff.if('exists(''%specialchar'')'. \ (opts.formatconcealed==1?'&&!%concealed': '')) if has_key(opts.listchars, 'tab') call ff.if("%specialchar is#'\t'") call ff.let('%virtstartcol', \ '%curcol==1?0:'. \ s:strdisplaywidthstr.'(%linestr[:(%curcol-2)])') let ival=(&tabstop-1).'-%virtstartcol%%'.&tabstop if opts.formatconcealed let cival='%concealdiff+'.ival if opts.formatconcealed==1 let ival=cival endif endif let lcstabfirst=matchstr(opts.listchars.tab, '\v^.') let lcstabnext=opts.listchars.tab[len(lcstabfirst):] call ff.let('%cstr', s:F.squote(lcstabfirst).'.'. \ 'repeat('.s:F.squote(lcstabnext).','.ival.')') if opts.list let tabhlnamestr='[''SpecialKey'']' if opts.dolinemergehl let tabhlnamestr='(len(%linehlname)>1?'. \ substitute(substitute(linehlnamestrorig, \ '''Normal''', 'synIDattr(%id,''name'')', 'g'), \ '%diffhlname', 'synIDattr(%diffid,''name'')', 'g'). \ ':'.tabhlnamestr.')' endif if opts.domatches let tabhlnamestr='(%matchhlname is 0'. \ '?'.tabhlnamestr. \ ':[%matchhlname])' endif " FIXME Still not completely correct else let tabhlnamestr=substitute(hlnamestr, 'old', '', 'g') endif call ff.let('%tabspec', cf.specstr(tabhlnamestr)) if opts.formatconcealed==2 call ff.let('%ccstr', s:F.squote(lcstabfirst).'.'. \ 'repeat('.s:F.squote(lcstabnext).','.cival.')') if cf.has('concealedstart') call ff.if('%ccstr isnot#%cstr') call ff.appendc('concealedstart', '%ccstr', '%tabspec', '%clnr', \ '%curcol') call ff.endif() endif " TODO Test narrowing down tabs when line is hovered endif call ff.appendc('line', '%cstr', '%tabspec', '%clnr', '%curcol') if opts.formatconcealed==2 if cf.has('concealedend') call ff.if('%ccstr isnot#%cstr') call ff.appendc('concealedend', '%ccstr', '%tabspec', '%clnr', \ '%curcol') call ff.endif() endif endif if opts.formatconcealed call ff.let('%concealdiff', 0) endif call ff.else() endif let sphlnamestr=s:F.getsphlnamestr(opts, hlnamestr, \ linehlnamestrorig, 0) let spcharstr='strtrans(%specialchar)' if has_key(opts.listchars, 'nbsp') let spcharstr='(%specialchar is#'' '''. \ '?'.s:F.squote(opts.listchars.nbsp). \ ':'.spcharstr.')' endif call ff.let('%spspec', cf.specstr(sphlnamestr)) call ff.appendc('line', spcharstr, '%spspec', '%clnr', '%curcol') if has_key(opts.listchars, 'tab') call ff.endif() endif call ff.leta('%oldid', '-1') call ff.leta('%startcol', '%curcol+len(%specialchar)') call ff.let('%curcol', '%startcol-1') call ff.unlet('%specialchar') if opts.formatconcealed==1 call ff.elseif('exists(''%specialchar'')') call ff.unlet('%specialchar') endif call ff.endif() endif "▲6 "▶6 Record new variable values if opts.dodiff call ff.leta('%olddiffid', '%diffid') endif if opts.formatconcealed if opts.conceallevel==2 call ff.if('!%nocbreak') endif call ff.leta('%oldconcealinfo', '%concealinfo') if opts.conceallevel==2 call ff.endif() endif endif if opts.domatches call ff.leta('%oldmatchhlname', '%matchhlname') endif if opts.dospell call ff.leta('%oldspellhlname', '%spellhlname') endif "▲6 "▲5 "▶5 Finish cycle call ff.increment('%curcol') call ff.endwhile() "▲5 "▲4 "▶4 Line end "▶5 Concealed characters: formatconcealed=1 if opts.formatconcealed==1 call ff.if('%concealed') call ff.leta('%ccstr', ccstrstr) if opts.conceallevel!=3 if opts.conceallevel==2 call ff.if('!empty(%ccstr)') endif call ff.appendc('line', '%ccstr', '%conspec', '%clnr', '%cstartcol') if opts.conceallevel==2 call ff.endif() endif endif call ff._up() endif "▲5 "▶5 Regural text call ff.addif('%startcol<=%linelen') call ff.leta('%cstr', '%linestr[%startcol-1:]') if has_key(opts.listchars, 'trail') call ff.if('%startcol-1>=%trail') call ff.let('%cstr', 'substitute(%cstr,'' '','. \ escape(s:F.squote(opts.listchars.trail),'\&~').',''g'')') if opts.domatches call ff.leta('%curspec', cf.specstr(trailhlnamestr)) if has_key(opts.listchars, 'space') call ff._up() else call ff.else() endif else if has_key(opts.listchars, 'space') call ff.leta('%curspec', cf.specstr(hlnamestr)) endif call ff._up() endif endif if has_key(opts.listchars, 'space') call ff.addif('(%startedspace||%didstartedspace)'. \ '&&!empty(filter(copy(get(%specialcolumns,%linelen+1,[])),'. \ '''v:val[0]is#"endedspace"''))') let sphlnamestr=s:F.getsphlnamestr(opts, hlnamestr, \ linehlnamestrorig, 1) call ff.let('%curspec', cf.specstr(sphlnamestr)) call ff.leta('%didstartedspace', 0) call ff.leta('%startedspace', 0) call ff.leta('%endedspace', 0) call ff.leta('%cstr', 'substitute(%cstr,'' '','. \ escape(s:F.squote(opts.listchars.space),'\&~').',''g'')') call ff.else() endif call ff.leta('%curspec', cf.specstr(hlnamestr)) if (has_key(opts.listchars, 'trail') && opts.domatches) || has_key(opts.listchars, 'space') call ff.endif() endif call ff.appendc('line', '%cstr', '%curspec', '%clnr', '%startcol') call ff.endif() "▲5 "▶5 Concealed characters: formatconcealed=2 if opts.formatconcealed==2 && cf.has('concealedend') call ff.if('%oldconcealinfo[0]') call ff.appendc('concealedend', ccstrstr, '%conspec', '%clnr', '%curcol') call ff.endif() endif "▲5 "▶5 Special columns if opts.domatches || opts.dotags || opts.dospell call ff.if('has_key(%specialcolumns,%linelen+1)') call ff.call('map(%specialcolumns[%linelen+1],'. \ '''extend(l:,{%name2sptypemap[v:val[0]]:v:val[1]})'')') if opts.domatches call ff.let('%oldmatchhlname', '%matchhlname') endif if opts.dospell call ff.let('%oldspellhlname', '%spellhlname') endif call ff.endif() endif "▲5 "▶5 Tags/links for type in (opts.dotags?['tag']:[])+(opts.dolinks?['link']:[]) if cf.has(type.'start') call ff.if('exists(''%started'.type.''')') call ff.unlet('%started'.type) call ff.endif() endif if cf.has(type.'end') call ff.if('exists(''%ended'.type.''')') call ff.appendc(type.'end', '%ended'.type, 0, '%clnr', '%curcol') call ff.unlet('%ended'.type) call ff.endif() endif unlet type endfor "▲5 "▶5 Processing EOL if has_key(opts.listchars, 'eol') || !empty(opts.matchespasteol) if !empty(opts.matchespasteol) call ff.if('has_key(%opts.matchespasteol,%clnr)') call ff.leta('%savedmatchhlname', '%matchhlname') call ff.leta('%matchhlname', '%opts.matchespasteol[%clnr]') if has_key(opts.listchars, 'eol') call ff.endif() else call ff.letabreak() endif endif let eolhlnamestr=linehlnamestrorig let eolhlnamestr=substitute(eolhlnamestr, \ '''Normal''',string(opts.highlight.NonText),'g') if opts.dolinemergehl let eolhlnamestr='(len(%linehlname)>1?%linehlname :'.eolhlnamestr.')' endif if opts.domatches let eolhlnamestr='(%matchhlname is 0'. \ '?'.eolhlnamestr. \ ':[''Normal'',%matchhlname])' endif call ff.leta('%eolspec', cf.specstr(eolhlnamestr)) call ff.appendc('line', s:F.squote(get(opts.listchars, 'eol', ' ')), \ '%eolspec', '%clnr', '%curcol+1') if !empty(opts.matchespasteol) if has_key(opts.listchars, 'eol') call ff.if('exists(''%savedmatchhlname'')') endif call ff.let('%matchhlname', '%savedmatchhlname') call ff.unlet('%savedmatchhlname') call ff.endif() endif endif call ff.appendc('lineend', 0, '%linespec', '%clnr', '%curcol') "▲4 "▶4 Rules " Order: colorcolumn > matches > diff > cursorline > signs > spell > special > syntax " Rules: " (colorcolumn = colorcolumn and cursorcolumn) " (signs = sign linehl) " (matches = matches themselves and search) " (special = highlighting of special characters defined by listchars (nbsp, tab, trail) and " non-printable characters highlighting) " (cancel = fg, bg and other attributes are ignored) " (cleared = after “hi clear …” or “hi link … Normal”) " - signs cancels special " - cursorline cancels signs and special " - matches cancel cursorline, signs and special " - colorcolumn does NOT cancel ANY highlighting " - If special is cancelled first character (applicable for tabs and non-printable characters, " otherwise read “all characters”) is highligted using syntax (if special is not cancelled " syntax is ignored) " - cleared diff cancels signs and cursorline and enables special " - cleared cursorline cancels signs and enables special " - cleared signs does not affect highlighting " - cleared matches does not affect highlighting " - cleared colorcolumn does not affect highlighting " - cleared diff makes match at the EOL extend past the EOL " - cursorline makes 'showbreak' characters underlined, but does not alter their highlighting " " Possible vim bugs: " - If special is cancelled first character (applicable for tabs and non-printable characters, " otherwise read “all characters”) is highligted using syntax (if special is not cancelled " syntax is ignored) " (bug candidate is “first character” part) " - cleared diff makes match at the EOL extend past the EOL " " FIXME: When using search beware that Search linked to Normal group or with cleared " highlighting does not affect highlighting, while Search group with defined or linked to " a group with defined highlighting for one of fg/bg (but not both) merges with " underlying group. " Search integration with other highlighting attributes needs more investigation. "▲4 "▲3 if !opts.ignorefolds && !opts.allfolds && !opts.foldcolumn call ff.endif() endif call ff.call('add(%r,%curstr)') call ff.increment('%clnr') call ff.endwhile() "▶2 Beginning and end if opts.allfolds call ff.if('has_key(%possiblefolds,%clnr)') call ff.let('%pf', '%possiblefolds[%clnr]') call ff.if('has_key(%pf,''end'')') call ff.increment('%r', '%pf.end') call ff.endif() call ff.endif() endif if !sbsd if cf.has('begin') call ff.call('insert(%r,%cformat.begin.f(%normalspec,'''',%opts,%cf.stylestr,%cf))') endif if cf.has('end') call ff.call('add(%r,%cformat.end.f(%normalspec,'.elnr.','''',%opts,%cf.stylestr,%cf))') endif endif call ff.do('return '.cf.getvar('r')) "▲2 let f = ['function d.compiledformat(cf, cformat, opts)'] \ +ff._tolist(opts.minimizefunc)+ \['endfunction'] let d={} execute join(f, "\n") call cf.savefunc('compiledformat', f, d.compiledformat) call cf.writefunc() if opts.profiling || opts.debugging call add(s:profiled, d.compiledformat) endif "▶2 r let r=d.compiledformat(cf, cformat, opts) "▶2 s:progress if opts.showprogress && opts.canresize let s:progress.opts.showprogress=0 endif "▶2 finish if sbsd is active if sbsd return r endif "▶2 cformat.nolf if cformat.nolf let r=[join(r, '')] endif "▶2 cformat.haslf if cformat.haslf let oldr=r let r=[] for item in oldr let r+=split(item, "\n", 1) endfor endif "▲2 return r endfunction "▶2 wrap function let s:F.format=s:_f.wrapfunc({'function': s:F.format, \ '@altervars': [['+window'], \ ['&l:laststatus'], \ ['&l:statusline'], \ ['&l:foldminlines'],]}) "▶1 addformat function s:F.addformat(type, format) let s:formats[a:type]=deepcopy(a:format) return 1 endfunction "▶1 delformat function s:F.delformat(type) if has_key(s:formats, a:type) unlet s:formats[a:type] return 1 endif return 0 endfunction "▶1 cmd let s:cmd={} "▶2 s:cmd.@FWC let s:filcomprefs= \ ' columns :=(-1) |earg range -1 inf '. \ '? to path W '. \ '? starttagreg :=(0) isreg'. \ '? endtagreg :=(0) isreg'. \ '! cursor :=(-1) '. \ '! number :=(-1) '. \ '! relativenumber :=(-1) '. \ '! list :=(-1) '. \ '! diff :=(-1) '. \ '!+1 tags :=(-1) in [local all] '. \ '!+1 foldcolumn :=(-2) |earg range -1 inf '. \ '! folds :=(-1) '. \ '! signs :=(-1) '. \ '! spell :=(-1) '. \ '!+1 concealed :=(-1) in [shown both] '. \ '!+1 progress :=(-1) in [percent lines] '. \ '!+1 matches :=(-1) in [search matches all] '. \ '! links :=(-1) ' let s:filformats='[:*_f.getoption("DefaultFormat") key formats~start]' let s:cmd['@FWC']=[ \'-onlystrings _ _ '. \'', 'filter'] unlet s:filformats s:filcomprefs "▲2 function s:cmd.function(slnr, elnr, action, ...) let action=a:action "▶2 Действия if action is# 'format' || action is# 'diffformat' let result=call(s:F.format, [a:1, a:slnr, a:elnr, a:2, \ (action is# 'diffformat')], {}) if has_key(a:2, 'to') if &encoding isnot# 'utf-8' && exists('iconv()') let result = map(copy(result), 'iconv(v:val, &enc, "utf-8")') endif call writefile(result, a:2.to, 1) else new ++enc=utf-8 call setline(1, result) endif return 1 elseif action is# 'list' echo join(keys(s:formats), "\n") return 1 endif "▲2 return 0 endfunction "▶1 cmd completion let s:cmpcomprefs= 'columns in ["-1" "80" =string(&co) '. \ '=string(winwidth())] '. \ '? to path W '. \ '? starttagreg _ '. \ '? endtagreg _ '. \ '! number '. \ '! relativenumber '. \ '! list '. \((has('spell'))? \ ('! spell ' ):('')). \((has('diff'))? \ ('! diff ' ):('')). \ '!+1 tags in [local all] '. \((has('folding'))? \ ('!+1 foldcolumn _ '. \ '! folds ' ):('')). \((has('signs'))? \ ('! signs' ):('')). \((has('conceal'))? \ ('!+1 concealed in [shown both] ' ):('')). \((has('statusline'))? \ ('!+1 progress in [percent lines] '):('')). \ '!+1 matches in [all matches '. \ (has('extra_search') \ ?('search') \ :('')).']'. \ '! links ' let s:cmpformats='[key formats]' let s:cmdcomplete='<'. \((has('diff'))? \ ('diffformat ('.s:cmpformats.'{'.s:cmpcomprefs.'})'): \ ('')). \ ' format ('.s:cmpformats.'{'.s:cmpcomprefs. \ ((has('diff'))?( '!+1 collapsfiller _ '):('')). \ ((has('folding'))?('! allfolds ' ):('')).'})'. \ ' list - '. \ '>' "▶1 cmd definition let s:_aufunctions.cmd=s:cmd let s:_aufunctions.comp={ \'function': s:_f.fwc.compile(s:cmdcomplete, 'complete')[0], \} unlet s:cmd unlet s:cmpformats s:cmpcomprefs unlet s:cmdcomplete "▶1 format feature let s:format={} "▶2 format.add :: {f}, name, dict → _ + fdict, s:formats function s:format.add(plugdict, fdict, name, dict) let s:formats[a:name]=extend(deepcopy(a:dict), {'id': a:name, \ 'plid': a:plugdict.id,}) let a:fdict[a:name]=s:formats[a:name] endfunction let s:format.add=s:_f.wrapfunc({'function': s:format.add, \'@FWC':['_ _ '. \ '(#exists not key formats) '. \ '(haskey line '. \ 'dict {?in keylist type string '. \ 'tagproc isfunc 1 '. \ 'strlen isfunc 1 '. \ 'addoptsfun isfunc 1 '. \ 'strescape type string '. \ 'haslf bool '. \ 'nolf bool '. \ 'addopts dict {/^[a-zA-Z0-9]\w*$/ _}'. \ 'state dict {/^[a-zA-Z0-9]\w*$/ _}'. \ 'sbsdstate dict {/^[a-zA-Z0-9]\w*$/ _}'. \ '})', 'check'], \}) "▶2 formatunload :: {f} → + fdict, s:formats function s:F.formatunload(plugdict, fdict) call map(keys(a:fdict), 's:F.delformat(v:val)') endfunction "▶2 Register feature call s:_f.newfeature('format', {'cons': s:format, \ 'unload': s:F.formatunload,}) unlet s:format "▶1 call frawor#Lockvar(s:, 'formats,progress,profiled,found_badly_spelled_word') " vim: ft=vim:ts=8:fdm=marker:fenc=utf-8:fmr=▶,▲:tw=100

Should clarify license notation more clearly

LICENSE file says:

To the extent possible under law, Yukihiro Nakadaira has waived all copyright and 
related or neighboring rights to VimL parser.

This says attribution to Yuhikiro Nakadaira and Public Domain but not says problems caused by copy of source code. I suggest to write "Your Own Risk" into the doc..

[converter]Separate let and define

function! s:foo()
  if v:true
    let s = 1
  else
    let s = 2
  endif
endfunction

This script is translated to

(function (s:foo)
  (if v:true
    (let = s 1)
   else
    (let = s 2)))

But this should be

(function (s:foo)
  (let = s 0) ;;; define
  (if v:true
    (let = s 1) ;;; let
   else
    (let = s 2))) ;;; let

This can be fixes with traversing let nodes, maybe.

#55

Check missing/added Ex commands in neovim *by script*

Currently

  • s:VimLParser.neovim_additional_commands
  • s:VimLParser.neovim_removed_commands

is managed manually.
neovim supports nvim_get_commands() listing available commands.

So it can automate by adding script diffing command list.

Ref

#118 (comment)

However, nvim_get_commands exists already (although only for non-builtin). Might be useful in general, but only for "dynamic" linting/parsing (i.e. not for vint, vimlparser.py etc, which need the list already).

Incorrect E477

In a function in my vimrc, I use Gdiff!. Using vint --json .vimrc, I get:

[{"severity": "error", "file_path": ".vimrc", "line_number": 588, "reference": "ynkdir/vim-vimlparser", "column_number": 3, "description": "E477: No ! allowed", "policy_name": "SyntaxError"}]

Behavior of echo :

:echo :

Vim throw two errors for this statement.

E121: Undefined variable: :
E15: Invalid expression: :

elseif 0 && (token.type == s:TOKEN_COLON || token.type == s:TOKEN_SHARP)

If modify this part to 1, error E121 is returned. We should design behavior what error should be returned.

Commands after "endfunction | ..." were ignored

endfunction の後の行の解析が捨てられているようです.

function! Hoo() abort
    echo 3
endfunction | echo 2345
if 1
  echo 4
endif | echo 456

なファイルを対象に vimlparser#test() すると、以下が出力され, (echo 2345) がありません

(function (Hoo)
 (echo 3))
(if 1
  (echo 4))
 (echo 456) 

以下の部分と思います

call self.reader.getn(-1)

Cannot parse inline python

vim-vimlparse cannot parse the script, but Vim can run it well:

let s:inline_python = 'python << EOF'
exec s:inline_python
import os
EOF

And it raises the following error:

vimlparser: E492: Not an editor command: import os: line 3 col 1

Unknown command in neovim-mode: rshada

I think it would be nice if the list of commands could be queried from/through the neovim Python package, either on runtime or semi-automatically to maintain the internal list used by vimlparser.

Add support for runtimepath adjustment

vint cannot lint py/pycompiler.vim / js/jscompiler.vim properly, because they are using a hack to import the s: scope of another file (call extend(s:, vimlparser#import())).

vimlint is able to parse this, since it adds $PWD to the runtimepath (https://github.com/syngan/vim-vimlint/blob/c8b9cd9d8a0fb6dc69667d32819aeef503cff55c/bin/vimlint.sh#L33).

I think this should somehow get supported / done automatically by vimlparser instead - probably based on the file's path, i.e. it should add foo to &rtp in case foo/autoload/bar/baz.vim is parsed.

Leading backslash causes wrong node.pos.i

via vim-jp/go-vimlparser#43

I had created an issue on go-vimlparser, but this problem seems vim-vimlparser problem.

I want to extract :function ~ :endfunction block from Vim script source code,
but if the source code includes leading backslash, node.pos.i (index of original source code?) becomes wrong.

The following code is what I converted from go-vimlparser issue, which was written in Go, to Vim script.

let s:vimlparser = vimlparser#import()

function! s:run()
  let src = [
  \  'function! s:load_on()',
  \  '  imap <expr> <Tab> neosnippet#expandable_or_jumpable() ?',
  \  '\ "\<Plug>(neosnippet_expand_or_jump)" : "\<Tab>"',
  \  'endfunction',
  \]
  let r = s:vimlparser.StringReader.new(src)
  let p = s:vimlparser.VimLParser.new(0)
  let c = s:vimlparser.Compiler.new()
  let toplevel = p.parse(r)
  let func = toplevel.body[0]
  let body = s:extract_body(func, src)
  echo body
endfunction

function! s:extract_body(func, src)
  let pos = a:func.pos

  let endpos = a:func.endfunction.pos
  let endfunc = a:func.endfunction.ea
  let cmdlen = endfunc.argpos.i - endfunc.cmdpos.i
  let endpos.i += cmdlen

  return join(a:src, "\n")[pos.i : endpos.i]
endfunction

call s:run()

Output (the end of n is missing):

function! s:load_on()
  imap <expr> <Tab> neosnippet#expandable_or_jumpable() ?
\ "\<Plug>(neosnippet_expand_or_jump)" : "\<Tab>"
endfunctio

Specifying source code which does not include leading backslash was successful.

  let src = [
  \  'function! s:hello()',
  \  '  echo "hello"',
  \  'endfunction',
  \]
function! s:hello()
  echo "hello"
endfunction

What I concern a little

The result was slightly different from original go-vimlparser behavior.
By go-vimlparser, the end of on (2 bytes) was missing.

function! s:load_on()
  imap <expr> <Tab> neosnippet#expandable_or_jumpable() ?
\ "\<Plug>(neosnippet_expand_or_jump)" : "\<Tab>"
endfuncti

"pos" is different (not a dict) with literal dicts

Given t1.vim:

let mydict = #{one: 0}

Given t2.vim:

let mydict = {"one": 0}

Printing node in parse_expr9 shows:

{'type': 83, 'pos': {'i': 13, 'lnum': 1, 'col': 14, 'offset': 13}, 'value': [[{'type': 81, 'pos': 15, 'value': "'one'"}, {'type': 80, 'pos': {'i': 20, 'lnum': 1, 'col': 21, 'offset': 20}, 'value': '0'}]]} {'i': 13, 'lnum': 1, 'col': 14, 'offset': 13}

vs

{'type': 83, 'pos': {'i': 13, 'lnum': 1, 'col': 14, 'offset': 13}, 'value': [[{'type': 81, 'pos': {'i': 14, 'lnum': 1, 'col': 15, 'offset': 14}, 'value': '"one"'}, {'type': 80, 'pos': {'i': 21, 'lnum': 1, 'col': 22, 'offset': 21}, 'value': '0'}]]} {'i': 13, 'lnum': 1, 'col': 14, 'offset': 13}

Note that "pos" for the key is a dict in the second case, but only a number in the first one.

Related code:

function! s:ExprParser.parse_dict_literal_key() abort
let node = s:Node(s:NODE_STRING)
let node.pos = self.reader.tell()
let node.value = "'" . self.tokenizer.get_dict_literal_key() . "'"
return node
endfunction

This causes an error in Vint then, which expects a dict there: Vimjas/vint#339.

/cc @tyru (via #110)

Option to support tnoremap

Thanks for writing this great extension.
As you know today many vim users are migrating to nvim, it'd be great if you could update it to support nvim too. I have a huge vimrc file (around 750 lines) the only issue I see is that vimlparser is not currently supporting nvim's tnoremap as mentioned here https://github.com/syngan/vim-vimlint/issues/95. Could you please add support for this token?

line-continuation makes incorrect lnum

let g:a = deepcopy(
      \ g:v["a"])
            ^

The lnum of the node "a" is not correct (i and col are correct).
In this example, the lnum is 2 but correct lnum is 3.

Move s:viml_builtin_functions to shared file?

Currently s:viml_builtin_functions is defined twice in both py/pycompiler.vim (

let s:viml_builtin_functions = ['abs', 'acos', 'add', 'and', 'append', 'append', 'argc', 'argidx', 'argv', 'argv', 'asin', 'atan', 'atan2', 'browse', 'browsedir', 'bufexists', 'buflisted', 'bufloaded', 'bufname', 'bufnr', 'bufwinnr', 'byte2line', 'byteidx', 'call', 'ceil', 'changenr', 'char2nr', 'cindent', 'clearmatches', 'col', 'complete', 'complete_add', 'complete_check', 'confirm', 'copy', 'cos', 'cosh', 'count', 'cscope_connection', 'cursor', 'cursor', 'deepcopy', 'delete', 'did_filetype', 'diff_filler', 'diff_hlID', 'empty', 'escape', 'eval', 'eventhandler', 'executable', 'exists', 'extend', 'exp', 'expand', 'feedkeys', 'filereadable', 'filewritable', 'filter', 'finddir', 'findfile', 'float2nr', 'floor', 'fmod', 'fnameescape', 'fnamemodify', 'foldclosed', 'foldclosedend', 'foldlevel', 'foldtext', 'foldtextresult', 'foreground', 'function', 'garbagecollect', 'get', 'get', 'getbufline', 'getbufvar', 'getchar', 'getcharmod', 'getcmdline', 'getcmdpos', 'getcmdtype', 'getcwd', 'getfperm', 'getfsize', 'getfontname', 'getftime', 'getftype', 'getline', 'getline', 'getloclist', 'getmatches', 'getpid', 'getpos', 'getqflist', 'getreg', 'getregtype', 'gettabvar', 'gettabwinvar', 'getwinposx', 'getwinposy', 'getwinvar', 'glob', 'globpath', 'has', 'has_key', 'haslocaldir', 'hasmapto', 'histadd', 'histdel', 'histget', 'histnr', 'hlexists', 'hlID', 'hostname', 'iconv', 'indent', 'index', 'input', 'inputdialog', 'inputlist', 'inputrestore', 'inputsave', 'inputsecret', 'insert', 'invert', 'isdirectory', 'islocked', 'items', 'join', 'keys', 'len', 'libcall', 'libcallnr', 'line', 'line2byte', 'lispindent', 'localtime', 'log', 'log10', 'luaeval', 'map', 'maparg', 'mapcheck', 'match', 'matchadd', 'matcharg', 'matchdelete', 'matchend', 'matchlist', 'matchstr', 'max', 'min', 'mkdir', 'mode', 'mzeval', 'nextnonblank', 'nr2char', 'or', 'pathshorten', 'pow', 'prevnonblank', 'printf', 'pumvisible', 'pyeval', 'py3eval', 'range', 'readfile', 'reltime', 'reltimestr', 'remote_expr', 'remote_foreground', 'remote_peek', 'remote_read', 'remote_send', 'remove', 'remove', 'rename', 'repeat', 'resolve', 'reverse', 'round', 'screencol', 'screenrow', 'search', 'searchdecl', 'searchpair', 'searchpairpos', 'searchpos', 'server2client', 'serverlist', 'setbufvar', 'setcmdpos', 'setline', 'setloclist', 'setmatches', 'setpos', 'setqflist', 'setreg', 'settabvar', 'settabwinvar', 'setwinvar', 'sha256', 'shellescape', 'shiftwidth', 'simplify', 'sin', 'sinh', 'sort', 'soundfold', 'spellbadword', 'spellsuggest', 'split', 'sqrt', 'str2float', 'str2nr', 'strchars', 'strdisplaywidth', 'strftime', 'stridx', 'string', 'strlen', 'strpart', 'strridx', 'strtrans', 'strwidth', 'submatch', 'substitute', 'synID', 'synIDattr', 'synIDtrans', 'synconcealed', 'synstack', 'system', 'tabpagebuflist', 'tabpagenr', 'tabpagewinnr', 'taglist', 'tagfiles', 'tempname', 'tan', 'tanh', 'tolower', 'toupper', 'tr', 'trunc', 'type', 'undofile', 'undotree', 'values', 'virtcol', 'visualmode', 'wildmenumode', 'winbufnr', 'wincol', 'winheight', 'winline', 'winnr', 'winrestcmd', 'winrestview', 'winsaveview', 'winwidth', 'writefile', 'xor']
) and js/jscompiler.vim.

I think this should be shared, probably in s:VimLParser.builtin_functions.

It is likely also out of date. It would be good if this could be used through some Vim function directly.

Literal carriage returns break parser under Python 3.x

This chunk from my .vimrc causes python3 py/vimlparser.py ~/.vimrc to error out on my system.

Since this didn't show up in my initial search for VimL parsers for Python, I started work on a VimL parser of my own and I know why it happens.

The literal <CR>s in this piece of my .vimrc (the ^Ms)...

image

...get converted to \n by the universal newline support in Python 3.x's text mode. (I assume because a bare \r is the old Classic MacOS line terminator and Python's universal newlines mode is converting all potential newlines rather than limiting itself to whichever kind of newline it encounters first like Vim itself does.)

The quick hack I came up with to make the parser robust was to open the file in binary mode and then manually decode and split lines:

with open(path, 'rb') as fobj:
    process_group(fobj.read().decode('utf8').split('\n'))

Separate classes to each autoload script

vimlparser has RegexpParser, but it's experimental class object.
And autoload/vimlparser.vim has 6000+ lines of code.
The each class should be moved to each autoload script.

  • Keep vimlparser#import() to load all autoload files for compatibility
  • User should be able to import and use each class by more modularized way

Consider using CC0 Public Domain declaration

As some jurisdictions don't recognize the right to prematurely put your creation into the public domain, it'd be a good idea to use the Creative Commons CC0 public domain declaration rather than writing your own.

It contains an "If your jurisdiction doesn't allow this, then, instead, I license it to you under equivalent terms" clause.

Otherwise, people in those jurisdictions get the default "All Rights Reserved".

...or, if you don't like that for some reason, there's also The Unlicense as a way to put something in the public domain which GitHub likes enough to list in choosealicense.com's list of more options.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.