ZyX_I avatar ZyX_I committed 6b8e41e

@/commands: Implemeted `command' feature

Comments (0)

Files changed (2)

             command -nargs=? Foo echo "Bar"
             let s:_commands+=["Foo"]
 
+command.add : function( cid, cstr, copts )              *frawor-f-command.add*
+            + unload
+        Creates a command named {cid}. {cstr} describes replacement string and 
+        is one of the following:
+        1. String. It will be added to the |:command| call directly. Note that 
+           command will be defined in the context of plugin/frawor/commands 
+           module, so you can't use script-local variables here.
+        2. Function reference.
+        3. Dictionary. This variant provides a way to delay loading of the 
+           plugin until command is called: when defined command is called 
+           frawor will load the plugin, use |frawor-f-wrapfunc| to create 
+           a function out of the given dictionary (note: you must have 
+           plugin/frawor/functions module in dependencies) and only then call 
+           given function.
+        {copts} is a dictinary that describes some additional options:
+        Key      Description ~
+        nargs     String or 0. Defines number of arguments, see 
+                  |:command-nargs|. Use 0 to skip this argument. Default: "0".
+        range     0, 1, "%" or Number written as string. Defines default 
+                  range, see |:command-range|. 1 means `add -range without 
+                  argument'. Default: 0.
+        count     0, 1 or Number written as string. Just the same as range, 
+                  but for |:command-count|. Default: 0.
+        bang, bar, register
+                  0 or 1. See |:command-bang|, |:command-bar| and 
+                  |:command-register|. Default: 1 for `bar', 0 for others.
+        buffer    0 or 1. See |:command-buffer|. Default: 0.
+        complete  Defines completion for the command. Possible values:
+                  1. String, see |:command-complete|.
+                  2. Function reference. Anonymous and script-local functions 
+                     are also allowed. See |:command-completion-customlist|.
+                  3. Dictionary. In this case plugin will be loaded when 
+                     completion is called and then this dictionary will be 
+                     passed to |frawor-f-wrapfunc|. You must have 
+                     plugin/frawor/functions module in dependencies. This 
+                     variant enables you to delay defining completion function 
+                     until required.
+                  4. List with one or two strings. In this case 
+                     |frawor-f-fwc.compile| will be used to compile completion 
+                     function. If list contains one string, then 
+                     |FWC-o-onlystrings| option will be enabled and "complete" 
+                     string will be added to list. If list contains two 
+                     strings, then it will be passed to fwc.compile 
+                     unmodified.
+        splitfunc, rsplitfunc
+                  Defines function that will split command-line into a list of 
+                  arguments. `splitfunc' is for completion, `rsplitfunc' is 
+                  for running. Latter is valid only if {cstr} is not a string.
+
+command.del : function( [ cid ] )                       *frawor-f-command.del*
+        Delete command named {cid} if it was defined by this plugin. If no 
+        {cid} was specified, deletes all commands defined by caller.
+
 ------------------------------------------------------------------------------
 3.11. plugin/frawor/resources features             *frawor-f-frawor/resources*
 
 5. Options                                                    *frawor-options*
                                                              *g:fraworOptions*
 
-Without modules frawor provides only one option: |frawor-o-_donotload|. All 
-other options are defined into various modules.
-                                                         *g:frawor__donotload*
-_donotload                                               *frawor-o-_donotload*
-        Prevents frawor plugin from loading. Works if either key _donotload is 
-        present in dictionary g:fraworOptions or g:frawor__donotload 
-        variable is defined (its content does not matter).
+All frawor options are defined into various modules.
 
 ------------------------------------------------------------------------------
 5.1. Mapping options                                       *frawor-mapoptions*

plugin/frawor/commands.vim

 "▶1 Header
 scriptencoding utf-8
-execute frawor#Setup('0.0', {}, 1)
+execute frawor#Setup('0.0', {'@/functions': '0.0',
+            \             '@/autocommands': '0.0',}, 1)
 "▶1 Define messages
 if v:lang=~?'ru'
     let s:_messages={
-                \
+                \   'cidnstr': 'Ошибка создания команды для дополнения %s: '.
+                \              'имя команды не является строкой',
+                \    'invcid': 'Ошибка создания команды для дополнения %s: '.
+                \              'строка «%s» не может являться именем команды',
+                \  'dcidnstr': 'Ошибка удаления команды для дополнения %s: '.
+                \              'имя команды не является строкой',
+                \   'nowncid': 'Ошибка удаления команды для дополнения %s: '.
+                \              'команда «%s» не была определена этим '.
+                \              'дополнением',
             \}
+    call extend(s:_messages, map({
+                \    'ciddef': 'команда уже определена дополнением %s',
+                \   'cidedef': 'команда уже определена',
+                \ 'coptsndct': 'второй аргумент не является словарём',
+                \  'invrange': '«%s» не является правильным диапозоном',
+                \  'invcount': '«%s» не является числом',
+                \    'invkey': 'ключ %s не принимает никаких аргументов',
+                \  'invnargs': '%s не является правильным описанием числа '.
+                \              'параметров',
+                \   'invclen': 'неправильное количество аргументов',
+                \     '1nstr': 'аргумент не является строкой на языке FWC',
+                \     'ucomp': 'непонятное описание функции автодополнения',
+                \   'invsreg': '«%s» не является правильным регулярным '.
+                \              'выражением: %s',
+                \     'invsp': 'неверное значение ключа «%ssplitfunc»',
+                \'nowrapfunc': 'отсутствует функция _f.wrapfunc '.
+                \              '(дополнение должно зависеть от '.
+                \               'plugin/frawor/functions)',
+                \     'urepl': 'непонятное описание команды',
+            \}, '"Ошибка создания команды %s для дополнения %s: ".v:val'))
 else
     let s:_messages={
-                \
+                \   'cidnstr': 'Error while creating command for plugin %s: '.
+                \              'command name is not a String',
+                \    'invcid': 'Error while creating command for plugin %s: '.
+                \              '`%s'' is not a valid command name',
+                \  'dcidnstr': 'Error while deleting command for plugin %s: '.
+                \              'command name is not a String',
+                \   'nowncid': 'Error while deleting command for plugin %s: '.
+                \              '`%s'' was not defined by this plugin',
             \}
+    call extend(s:_messages, map({
+                \    'ciddef': 'command was already defined by plugin %s',
+                \   'cidedef': 'command was already defined',
+                \ 'coptsndct': 'second argument is not a Dictionary',
+                \  'invrange': '`%s'' is not a valid range',
+                \  'invcount': '`%s'' is not a valid count',
+                \    'invkey': 'key %s does not accept any arguments',
+                \  'invnargs': '%s is not a valid parameter number description',
+                \   'invclen': 'invalid number of arguments',
+                \     '1nstr': 'expected FWC string here',
+                \     'ucomp': 'invalid completion description',
+                \   'invsreg': '`%s'' is not a valid regular expression: %s',
+                \     'invsp': 'invalid value of `%ssplitfunc'' key',
+                \'nowrapfunc': 'function _f.wrapfunc is absent (plugin '.
+                \              'must depend on plugin/frawor/functions)',
+                \     'urepl': 'invalid command description',
+            \}, '"Error while creating command %s for plugin %s: ".v:val'))
 endif
+"▶1 rewritefname    :: sid, Funcref → funcname
+function s:F.rewritefname(sid, Fref)
+    if type(a:Fref)==2
+        let fstr=string(a:Fref)[10:-3]
+    else
+        let fstr=a:Fref
+    endif
+    if fstr[:1] is# 's:'
+        let fstr='<SNR>'.a:sid.'_'.fstr[2:]
+    elseif fstr[:4] is# '<SID>'
+        let fstr='<SNR>'.a:sid.'_'.fstr[5:]
+    endif
+    return fstr
+endfunction
 "▶1 add_commands :: {f} → + p:_commands
 function s:F.add_commands(plugdict, fdict)
     if !has_key(a:plugdict.g, '_commands') ||
 call s:_f.newfeature('delcommands', {'unloadpre': s:F.delcommands,
             \                         'register': s:F.add_commands,
             \                       'ignoredeps': 1})
+"▶1 splitfunc    :: arglead, cmdline, curpos[, cmdsplit] → cmddict
+" cmdsplit :: Fref | Regex
+"  cmddict :: {range: String, command: String, bang: Bool,
+"              curargs: [String], args: [String],
+"              arglead: String, position: UInt, cmdline: String}
+let s:argsplitregex='\(\\\@<!\(\\.\)*\\\)\@<! '
+let s:rangeregex='\m^\(%\|'.
+            \         '\('.
+            \           '\(\d\+\|'.
+            \             '[.$]\|'.
+            \             '''.\|'.
+            \             '\\[/?&]\|'.
+            \             '/\([^\\/]\|\\.\)\+/\=\|'.
+            \             '?\([^\\?]\|\\.\)\+?\='.
+            \           '\)'.
+            \           '\([+-]\d\+\)\='.
+            \           '[;,]\='.
+            \         '\)*'.
+            \       '\)\=\s*'
+function s:F.splitfunc(arglead, cmdline, curpos, ...)
+    let r={}
+    let r.arglead=a:arglead
+    let r.position=a:curpos
+    let r.cmdline=a:cmdline
+    let r.range=matchstr(a:cmdline, s:rangeregex)
+    let r.command=matchstr(a:cmdline[len(r.range):], '\v^(\u[[:alnum:]_]*)!?')
+    let d={}
+    let d.split=get(a:000, 0, s:argsplitregex)
+    if type(d.split)==type('')
+        let r.args=split(a:cmdline[(len(r.range)+len(r.command)):], d.split)
+        let r.curargs=split(a:cmdline[(len(r.range)+len(r.command)):(a:curpos)],
+                    \       d.split)
+    else
+        let r.args=d.split(a:cmdline[(len(r.range)+len(r.command)):])
+        let r.curargs=d.split(a:cmdline[len(r.range)+len(r.command):(a:curpos)])
+    endif
+    let r.bang=(r.command[-1:] is# '!')
+    return r
+endfunction
+"▶1 cmdsplit     :: cmdline[, cmdsplit] → [String]
+function s:F.cmdsplit(cmdline, ...)
+    let d={}
+    let d.split=get(a:000, 0, s:argsplitregex)
+    if type(d.split)==type('')
+        return split(a:cmdline, d.split)
+    else
+        return d.split(a:cmdline)
+    endif
+endfunction
+"▶1 command feature
+let s:F.command={}
+let s:commands={}
+"▶2 loadplugin   :: cmd → + FraworLoad(), :au!
+function s:F.loadplugin(cmd)
+    call FraworLoad(a:cmd.plid)
+    if !empty(a:cmd.augs)
+        call map(remove(a:cmd.augs, 0, -1), 's:_f.augroup.del(v:val)')
+    endif
+    if !empty(a:cmd.funs)
+        call map(remove(a:cmd.funs, 0, -1),
+                    \'s:_f.addextfunctions({v:val[0]:v:val[1]})')
+    endif
+endfunction
+"▶2 runcmd       :: cmd → + ?
+function s:F.runcmd(cmd, args)
+    if type(a:cmd.f)==type({})
+        call s:F.loadplugin(a:cmd)
+        let a:cmd.f=call(a:cmd.wrapfunc, [a:cmd.f], {})
+    endif
+    return call(a:cmd.f, a:args, {})
+endfunction
+"▶2 command.add  :: {f}, cid, cstr, copts → + :command, …
+"▶3 getspfunc    :: plid, cid, copts, spref → Maybe sp
+function s:F.getspfunc(plid, cid, copts, spref)
+    let key=a:spref.'splitfunc'
+    if !has_key(a:copts, key)
+        return 0
+    elseif type(a:copts[key])==type('')
+        try
+            call matchstr('', a:copts[key])
+        catch
+            call s:_f.throw('invsreg', a:cid, a:plid, a:copts[key], v:exception)
+        endtry
+    elseif !exists('*a:copts[key]') && a:copts[key] isnot 0
+        call s:_f.throw('invsp', a:cid, a:plid, a:spref)
+    endif
+    return a:copts[key]
+endfunction
+"▲3
+let s:cmddefaults={
+            \   'nargs': '0',
+            \'complete':  0,
+            \   'range':  0,
+            \   'count':  0,
+            \    'bang':  0,
+            \     'bar':  1,
+            \'register':  0,
+            \  'buffer':  0,
+        \}
+let s:compaugprefix='LoadCompFunc_'
+function s:F.command.add(plugdict, fdict, cid, cstr, copts)
+    "▶3 Checking arguments
+    if type(a:cid)!=type('')
+        call s:_f.throw('cidnstr', a:plugdict.id)
+    elseif a:cid!~#'^\u\w*$'
+        call s:_f.throw('invcid', a:plugdict.id, a:cid)
+    elseif has_key(s:commands, a:cid)
+        call s:_f.throw('ciddef', a:cid, a:plugdict.id, s:commands[a:cid].plid)
+    elseif exists(':'.a:cid)
+        call s:_f.throw('cidedef', a:cid, a:plugdict.id)
+    elseif type(a:copts)!=type({})
+        call s:_f.throw('coptsndct', a:cid, a:plugdict.id)
+    endif
+    "▲3
+    let cmd   =   {'id': a:cid,
+                \'plid': a:plugdict.id,
+                \'augs': [],
+                \'funs': [],
+                \}
+    let cmdstring=''
+    "▶3 Create :command -options
+    for [key, value] in items(s:cmddefaults)
+        if has_key(a:copts, key)
+            "▶4 Completion
+            if key is# 'complete'
+                let d={}
+                let d.complete=a:copts.complete
+                let tcomplete=type(d.complete)
+                let cmd.compfname='s:Complete'.cmd.id
+                "▶5 Use function
+                if tcomplete==2
+                    let fname=s:F.rewritefname(a:copts.complete)
+                    if fname=~#'^\d'
+                        let cmd.compfunc=d.complete
+                        let intfname='s:commands.'.cmd.id.'.compfunc'
+                        execute      "function ".cmd.compfname."(...)\n".
+                                    \"    return call(".intfname.",a:000,{})\n".
+                                    \"endfunction"
+                    else
+                        let cmd.compfname=fname
+                    endif
+                    let cmdstring.='-complete=customlist,'.(cmd.compfname).' '
+                "▶5 Use function described by dictionary
+                elseif tcomplete==type({})
+                    let fpattern='*'.(s:_sid).'_'.(cmd.compfname[2:])
+                    if a:plugdict.status!=2
+                        let augname=s:compaugprefix.(cmd.id)
+                        call s:_f.augroup.add(augname,
+                                    \         ['FuncUndefined', fpattern, 0,
+                                    \          [s:F.loadplugin, cmd]])
+                        call add(cmd.augs, augname)
+                        call add(cmd.funs, [cmd.compfname, d.complete])
+                    else
+                        call s:_f.addextfunctions({cmd.compfname : d.complete})
+                    endif
+                    let cmdstring.='-complete=customlist,'.(cmd.compfname).' '
+                "▶5 Use FWC string
+                elseif tcomplete==type([])
+                    let lcomp=len(d.complete)
+                    if lcomp==1
+                        if type(d.complete[0])!=type('')
+                            call s:_f.throw('1nstr', a:cid, a:plugdict.id)
+                        endif
+                        let compargs=['-onlystrings '.d.complete[0], 'complete']
+                    elseif lcomp==2
+                        let compargs=d.complete
+                    else
+                        call s:_f.throw('invclen', a:cid, a:plugdict.id)
+                    endif
+                    let compargs+=[a:plugdict.g]
+                    let [cmd.compfunc, cmd.FWCid]=
+                                \call(s:_f.fwc.compile, compargs, {})
+                    let intfname = 's:commands.'.cmd.id.'.compfunc'
+                    let args     = 's:commands.'.cmd.id.'.splitfunc(a:000)'
+                    execute      "function ".cmd.compfname."(...)\n".
+                                \"    return call(".intfname.",".args.",{})\n".
+                                \"endfunction"
+                    let cmdstring.='-complete=customlist,'.(cmd.compfname).' '
+                "▶5 Use something else
+                elseif tcomplete==type('')
+                    let cmdstring.='-complete='.(d.complete).' '
+                "▶5 Fail
+                else
+                    call s:_f.throw('ucomp', a:cid, a:plugdict.id)
+                endif
+            "▶4 Other options
+            else
+                " TODO: Make unoverridable buffer=1 for filetype plugins
+                if key is# 'range' && index([0, 1, '%'], a:copts[key])==-1
+                    call s:_f.throw('invrange', a:cid, a:plugdict.id,
+                                \               string(a:copts[key]))
+                elseif key is# 'count' &&
+                            \!(a:copts[key] is 0 || a:copts[key] is 1 ||
+                            \  (type(a:copts[key])==type('') &&
+                            \   a:copts[key]=~#'^\d\+$'))
+                    call s:_f.throw('invcount', a:cid, a:plugdict.id,
+                                \   string(a:copts[key]))
+                elseif (key is# 'bang' || key is# 'bar' || key is# 'register' ||
+                            \key is# 'buffer') && !(a:copts[key] is 0 ||
+                            \                       a:copts[key] is 1)
+                    call s:_f.throw('invkey', a:cid, a:plugdict.id, key)
+                elseif key is# 'nargs' && index(['0', '1', '*', '?', '+'],
+                            \                   a:copts[key])==-1
+                    call s:_f.throw('invnargs', a:cid, a:plugdict.id,
+                                \               string(a:copts[key]))
+                endif
+                let value=a:copts[key]
+            endif
+            "▲4
+        endif
+        if value is 1
+            let cmdstring.='-'.key.' '
+        elseif type(value)==type('')
+            let cmdstring.='-'.key.'='.value.' '
+        endif
+        unlet value
+    endfor
+    "▶3 Process *splitfunc
+    let cmd.sp=s:F.getspfunc(a:plugdict.id, a:cid, a:copts, '')
+    let cmd.rsp=s:F.getspfunc(a:plugdict.id, a:cid, a:copts, 'r')
+    "▲3
+    let cmdstring.=cmd.id.' '
+    "▶3 Process replacement
+    if type(a:cstr)==type('')
+        let cmdstring.=(a:cstr)
+    else
+        if type(a:cstr)==type({})
+            if !exists('*a:plugdict.g._f.wrapfunc')
+                call s:_f.throw('nowrapfunc', a:cid, a:plugdict.id)
+            endif
+            let cmd.wrapfunc=a:plugdict.g._f.wrapfunc
+            let cmd.f=a:cstr
+        elseif exists('*a:cstr')
+            let cmd.f=a:cstr
+        else
+            call s:_f.throw('urepl', a:cid, a:plugdict.id)
+        endif
+        let cmdstring.='call s:F.runcmd(s:commands.'.a:cid.', '
+        if cmd.rsp is 0
+            let cmdstring.='[<f-args>]'
+        elseif type(cmd.rsp)==type('')
+            let cmstring.='s:F.cmdsplit(<q-args>, s:commands.'.a:cid.'.rsp)'
+        else
+            let cmdstring.='s:commands.'.a:cid.'.rsp(<q-args>)'
+        endif
+        let cmdstring.=')'
+    endif
+    "▲3
+    let s:commands[cmd.id]=cmd
+    let a:fdict[cmd.id]=cmd
+    execute 'command' cmdstring
+endfunction
+"▶2 command.del  :: {f}[, cid] → + :delcommand, s:commands
+function s:F.command.del(plugdict, fdict, ...)
+    let todel=keys(a:fdict)
+    if a:0
+        "▶3 Проверка аргумента
+        if type(a:1)!=type('')
+            call s:_f.throw('dcidnstr', a:plugdict.id)
+        elseif !has_key(a:fdict, a:1)
+            call s:_f.throw('nowncid', a:plugdict.id, a:1)
+        endif
+        "▲3
+        let todel=[a:1]
+    endif
+    for cid in todel
+        if has_key(a:fdict[cid], 'FWCid')
+            call s:_f.fwc.del(a:fdict[cid].FWCid)
+        endif
+        unlet s:commands[cid]
+        unlet a:fdict[cid]
+        execute 'delcommand' cid
+    endfor
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('command', {'cons': s:F.command,
+            \                  'unload': s:F.command.del,})
 "▶1
-call frawor#Lockvar(s:, '')
+call frawor#Lockvar(s:, 'commands')
 " vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.