Commits

ZyX_I committed 78c9f9a

@/fwc: Added `path' internal function completion

Comments (0)

Files changed (8)

          Key    Description ~
          p      `s:' dictionary of your plugin (really the last argument to 
                 |FraworRegister()|)
-         os     |frawor-r-os| resource
 
                                                                  *FWC-{float}*
 Integer and floating-point numbers format in FWC is more permissive then vim 
     Completer: completes to nothing, breaking completion.
 
 if {arg} {arg} {arg}   (|FWC-{arg}|)                                *FWC-f-if*
-    Filter: if argument matches first {arg}, then it is processed using the 
-            second {arg}, otherwise it is processed using the third {arg}.
+    Filter/completer: if argument matches first {arg}, then it is processed 
+                      using the second {arg}, otherwise it is processed using 
+                      the third {arg}.
+                      Note that first {arg} must have no side-effects and be 
+                      efficient enough if you want to use it inside completion 
+                      function.
 
 run {var}   (|FWC-{var}|)                                          *FWC-f-run*
     Filter: calls current argument with {var} as an arguments list and 
             a new empty dictionary for this call.
 
 earg                                                             *FWC-f-earg*
-    Filter: replaced argument with the result of evaluating itself.
+    Filter: replaces argument with the result of evaluating itself.
 
 not {arg}    (|FWC-{arg}|)                                        *FWC-c-not*
     Checker: fails if processing argument using {arg} succeeds.
         Returns an absolute path.
 os.path.realpath :: path + FS -> path                *frawor-os-path-realpath*
         Returns an absolute path with all symbolic links resolved.
+os.path.normpath :: path -> path                     *frawor-os-path-normpath*
+        Normalize path by removing duplicate path separators.
 os.path.basename :: path -> component                *frawor-os-path-basename*
         Returns the final component of a path.
 os.path.dirname :: path -> path                       *frawor-os-path-dirname*

plugin/frawor/fwc/compiler.vim

             \                '@/fwc/constructor'  : '0.0',
             \                '@/fwc/intfuncs'     : '0.0',
             \                '@/fwc/topconstructs': '0.0',
-            \                '@/os'               : '0.0',
             \                '@/decorators'       : '0.0'}, 1)
 let s:compiler={}
 let s:cf='CHECKFAILED'
 function s:compiler.getlastsub()
     return self.getsub(self.subs[-1])
 endfunction
+"▶1 getfunstatvar   :: varname, varinit[, id] + self → varstr + self
+function s:compiler.getfunstatvar(name, init, ...)
+    if !has_key(self.vids, a:name)
+        let self.vids[a:name]=0
+        let self.vars[a:name]={}
+    endif
+    if a:0 && a:1!~#'\v^\x*$' && ((has_key(self.vars[a:name], a:1))?
+                \                   (self.vars[a:name][a:1] is a:init):
+                \                   (1))
+        let id=a:1
+    else
+        let id=printf('%x', self.vids[a:name])
+        let self.vids[a:name]+=1
+    endif
+    let self.vars[a:name][id]=a:init
+    return '@%@'.self.getsubs([a:name, id])
+endfunction
 "▶1 getfunc         :: funccontext, split[, addarg, ...] + self → String
 function s:compiler.getfunc(func, split, ...)
     if a:split
     if !has_key(s:_r.FWC_intfuncs[mname], 'matcher')
         call s:_f.throw('umatcher', mname)
     endif
-    let r=self.getfunstatvar('matchers', s:_r.FWC_intfuncs[mname].matcher)
+    let r=self.getfunstatvar('matchers', s:_r.FWC_intfuncs[mname].matcher,mname)
                 \.'('.a:ldstr.', '.a:strstr
     if len(a:matcher[1])>3
         let curargstr=self.argstr()
     endif
     return ((splitsubs)?(r):(r[0].((len(r)>1)?(self.getsubs(r[1])):(''))))
 endfunction
-"▶1 getfunstatvar   :: varname, varinit + self → varstr + self
-function s:compiler.getfunstatvar(name, init)
-    if !has_key(self.vids, a:name)
-        let self.vids[a:name]=0
-        let self.vars[a:name]={}
-    endif
-    let id=printf('%x', self.vids[a:name])
-    let self.vids[a:name]+=1
-    let self.vars[a:name][id]=a:init
-    return '@%@'.self.getsubs([a:name, id])
-endfunction
 "▶1 getlvarid       :: varname + self → varname
 function s:compiler.getlvarid(v)
     return printf('@$@%s%X', a:v, len(self.stack))
                 \ 'F': {'warn': s:_f.warn, 'throw': s:_f.throw},
                 \ 'p': a:gdict,
                 \ 'm': {'types': s:_messages._types},
-                \'os': s:_r.os,
             \}
 endfunction
 "▶1 makefunc        :: {f}, String, type[, gdict] → Fref + s:lastid, s:vars

plugin/frawor/fwc/intfuncs.vim

             \exists('g:frawor__donotload')
     finish
 endif
-execute frawor#Setup('0.0', {'@/resources': '0.0'}, 1)
+execute frawor#Setup('0.0', {'@/resources': '0.0',
+            \                '@/os':        '0.0'}, 1)
 let s:r={}
 let s:cf='CHECKFAILED'
 let s:cfstr=string(s:cf)
 let s:cfreg='\v^'.s:cf.'$'
+"▲1
+"Completion  -------------------------------------------------------------------
+"▶1 Path
+"▶2 getfiles
+function s:F.getfiles(arglead, filter, forcefilter)
+    let fragments=s:_r.os.path.split(a:arglead)
+    let globstart=''
+    if a:arglead[0] is s:_r.os.sep
+        let globstart=s:_r.os.sep
+    endif
+    if a:arglead[-1:] is s:_r.os.sep && get(fragments, -1, 0) isnot ''
+        call add(fragments, '')
+    endif
+    while len(fragments)>1 && (fragments[0] is '.' || fragments[0] is '..')
+        let globstart.=remove(fragments, 0).s:_r.os.sep
+    endwhile
+    let startswithdot = a:arglead[0] is '.'
+    if empty(fragments)
+        call add(fragments, '')
+    endif
+    let files=s:F.recdownglob(globstart, fragments, len(fragments)-1)
+    let r=files
+    if !empty(a:filter)
+        let newfiles=[]
+        for f in files
+            let file=s:_r.os.path.abspath(f)
+            if s:_r.os.path.isdir(file) || eval(a:filter)
+                call add(newfiles, f)
+            endif
+        endfor
+        if !empty(newfiles) || a:forcefilter
+            let r=newfiles
+        endif
+    endif
+    if !startswithdot
+        call map(r, 's:_r.os.path.join(filter(s:_r.os.path.split(v:val), '.
+                    \                        '"v:val isnot ''.''"))')
+    endif
+    call map(r, 's:_r.os.path.isdir(v:val)?(v:val.s:_r.os.sep):(v:val)')
+    return r
+endfunction
+"▶2 recdownglob
+function s:F.recdownglob(globstart, fragments, i)
+    if a:i<0
+        return []
+    endif
+    let dotfragment=(a:fragments[a:i]==#'.' || a:fragments[a:i]==#'..')
+                \   && (a:i<len(a:fragments)-1)
+    let glist=[]
+    if dotfragment
+        let dir=s:_r.os.path.join(a:fragments[:(a:i)])
+        if s:_r.os.path.isdir(dir)
+            let glist=[dir]
+        endif
+    else
+        let curdir=a:globstart.
+                    \    ((a:i)?
+                    \       (s:_r.os.path.join(a:fragments[:(a:i-1)])):
+                    \       (''))
+        if s:_r.os.path.isdir(curdir)
+            let fcur=a:fragments[a:i]
+            let dircontents=s:_r.os.listdir(curdir)
+            let glist=s:r.smart.matcher(dircontents, fcur, 2)
+            if !empty(curdir)
+                call map(glist, 's:_r.os.path.join(curdir, v:val)')
+            endif
+        endif
+    endif
+    if empty(glist)
+        return s:F.recdownglob(a:globstart, a:fragments, a:i-1)
+    elseif a:i==len(a:fragments)-1
+        return glist
+    endif
+    return s:F.recupglob(filter(glist, 's:_r.os.path.isdir(v:val)'),
+                \        a:fragments, a:i+1)
+endfunction
+"▶2 recupglob
+function s:F.recupglob(files, fragments, i)
+    let dotfragment=(a:fragments[a:i]==#'.' || a:fragments[a:i]==#'..')
+    let glist=[]
+    if dotfragment
+        let glist=[join(a:fragments[:(a:i)], s:g.plug.file.pathseparator)]
+    endif
+    let fcur=a:fragments[a:i]
+    let directories={}
+    "▶3 Variables for smartfilters
+    let str=fcur
+    let lowstr=tolower(fcur)
+    let lstr=len(fcur)-1
+    let estr=escape(fcur, '\')
+    let reg='\V'.join(split(estr, '\v[[:punct:]]@<=|[[:punct:]]@='), '\.\*')
+    let reg2='\V'.join(map(split(fcur,'\v\_.@='), 'escape(v:val,"\\")'), '\.\*')
+    "▲3
+    for filter in s:smartfilters
+        for file in a:files
+            let curdir=file
+            if has_key(directories, curdir)
+                let dircontents=directories[curdir]
+            else
+                let dircontents=s:_r.os.listdir(curdir)
+                let directories[curdir]=dircontents
+            endif
+            let tmpglist=filter(copy(dircontents), filter)
+            if !empty(tmpglist)
+                if !empty(curdir)
+                    call map(tmpglist, 's:_r.os.path.join(curdir, v:val)')
+                endif
+                let glist+=tmpglist
+            endif
+        endfor
+        if !empty(glist)
+            break
+        endif
+    endfor
+    if a:i==len(a:fragments)-1 || empty(glist)
+        return glist
+    endif
+    return s:F.recupglob(filter(glist, 's:_r.os.path.isdir(v:val)'),
+                \        a:fragments, a:i+1)
+endfunction
+"▲1
+"Filters/checkers --------------------------------------------------------------
 "▶1 `func', `eval'
 let s:r.func={'args': ['func'], 'breakscomp': 1}
 " Checks whether result of running {func}({argument}) isnot 0
 "▶1 `path'
 " Checks whether {argument} is a path matching given specification
 let s:r.path={'args': ['get']}
-"▶2 addpathp       :: idx + self → self + self
+"▶2 inwpath    :: path → Bool
+function s:r.path.inwpath(path)
+    if s:_r.os.path.exists(a:path)
+        return 0
+    endif
+    let components=s:_r.os.path.split(a:path)
+    if empty(components)
+        return filewritable('.')==2
+    endif
+    let curpath=remove(components, 0)
+    for component in components
+        let curpath=s:_r.os.path.join(curpath, component)
+        if filewritable(curpath)==2
+            return 1
+        elseif s:_r.os.path.exists(curpath)
+            return 0
+        endif
+    endfor
+    return 0
+endfunction
+"▶2 addpathp   :: idx + self → self + self
 function s:r.path.addpathp(idx)
     let curargstr=self.argstr()
     let dirnamestr=self.getlvarid('dirname')
     let prevdirstr=self.getlvarid('prevdir')
     let foundstr=self.getlvarid('found')
-    call self.addif('!@%@.os.path.exists('.curargstr.')')
-                \.let(dirnamestr, '@%@.os.path.normpath('.curargstr.')')
+    let normpathstr=self.getfunstatvar('os', s:_r.os.path.normpath, 'normpath')
+    let existsstr=self.getfunstatvar('os', s:_r.os.path.exists, 'exists')
+    let osdirnamestr=self.getfunstatvar('os', s:_r.os.path.dirname, 'dirname')
+    call self.addif('!'.existsstr.'('.curargstr.')')
+                \.let(dirnamestr, normpathstr.'('.curargstr.')')
                 \.let(prevdirstr, '""')
                 \.let(foundstr, 0)
                 \.while(dirnamestr.' isnot '.prevdirstr)
                     \.addif('filewritable('.dirnamestr.')==2')
                         \.let(foundstr, 1)
                         \.break().up()
-                    \.addif('@%@.os.path.exists('.dirnamestr.')')
+                    \.addif(existsstr.'('.dirnamestr.')')
                         \.break().up()
                     \.let(prevdirstr, dirnamestr)
-                    \.let(dirnamestr, '@%@.os.path.dirname('.dirnamestr.')')
+                    \.let(dirnamestr, osdirnamestr.'('.dirnamestr.')')
                 \.up()
                 \.nextthrow('!'.foundstr, 'nowrite', a:idx, curargstr)
             \.up().up()
 "▶2 check
 function s:r.path.check(desc, idx, type)
     let curargstr=self.argstr()
+    let existsstr=self.getfunstatvar('os', s:_r.os.path.exists, 'exists')
+    let dirnamestr=self.getfunstatvar('os', s:_r.os.path.dirname, 'dirname')
+    let isdirstr=self.getfunstatvar('os', s:_r.os.path.isdir, 'isdir')
     call self.addtypecond([type('')], a:idx)
     let spec=a:desc[1]
     if spec[0] is 'd'
             let spec=spec[1:]
         elseif spec[0] is 'W'
             call self.nextthrow('filewritable('.curargstr.')!=2 &&'.
-                        \      '(@%@.os.path.exists('.curargstr.')'.
-                        \       '|| filewritable('.
-                        \               '@%@.os.path.dirname('.
-                        \                        curargstr.'))!=2)',
+                        \      '('.existsstr.'('.curargstr.')'.
+                        \       '|| filewritable('.dirnamestr.'('.
+                        \                                    curargstr.'))!=2)',
                         \      'nowrite', a:idx, curargstr)
             let spec=spec[1:]
         elseif spec[0] is 'p'
             call call(s:r.path.addpathp, [a:idx], self)
-                        \.nextthrow('!@%@.os.path.isdir('.
-                        \                             curargstr.')',
+                        \.nextthrow('!'.isdirstr.'('.curargstr.')',
                         \           'isdir', a:idx, curargstr)
             let spec=spec[1:]
         else
-            call self.nextthrow('!@%@.os.path.isdir('.curargstr.')',
+            call self.nextthrow('!'.isdirstr.'('.curargstr.')',
                         \       'isdir', a:idx, curargstr)
         endif
     else
                             \       'nread', a:idx, curargstr)
                 let spec=spec[1:]
             elseif spec[-1:] isnot 'x'
-                call self.nextthrow('@%@.os.path.isdir('.
-                            \                         curargstr.')',
+                call self.nextthrow(isdirstr.'('.curargstr.')',
                             \       'isfile', a:idx, curargstr)
             endif
         endif
             let spec=spec[1:]
         elseif spec[0] is 'W'
             call self.nextthrow('!filewritable('.curargstr.') &&'.
-                        \      '(@%@.os.path.exists('.curargstr.')'.
+                        \      '('.existsstr.'('.curargstr.')'.
                         \       '|| filewritable('.
-                        \               '@%@.os.path.dirname('.
-                        \                        curargstr.'))!=2)',
+                        \                     dirnamestr.'('.curargstr.'))!=2)',
                         \      'nowrite', a:idx, curargstr)
             let spec=spec[1:]
         elseif spec[0] is 'p'
     endif
     return self
 endfunction
+"▶2 complete
+function s:r.path.complete(desc, idx, type)
+    let spec=a:desc[1]
+    " TODO add filter based on previous/next checks
+    let filter=''
+    if spec[0] is 'd'
+        let spec=spec[1:]
+        if spec[0] is 'w'
+            let filter='filewritable(file)==2'
+            let spec=spec[1:]
+        elseif spec[0] is 'W'
+            let filter='(filewritable(file)==2 || '.
+                        \'(!s:_r.os.path.exists(file) && '.
+                        \ 'filewritable(dirnamestr)==2))'
+            let spec=spec[1:]
+        elseif spec[0] is 'p'
+            let filter='(s:_r.os.path.exists(file)? '.
+                        \   's:_r.os.path.isdir(file): '.
+                        \   's:r.path.inwpath(file))'
+            let spec=spec[1:]
+        else
+            let filter='s:_r.os.path.isdir(file)'
+        endif
+    else
+        let fileonly=0
+        if spec[0] is 'f'
+            let spec=spec[1:]
+            let fileonly=1
+        endif
+        if fileonly
+            if spec[0] is 'r'
+                let filter='filereadable(file)'
+                let spec=spec[1:]
+            elseif spec[-1:] isnot 'x'
+                " There is no way to exclude directories
+            endif
+        endif
+        if spec[0] is 'w'
+            let filter='filewritable(file)'
+            let spec=spec[1:]
+        elseif spec[0] is 'W'
+            if !empty(filter)
+                let filter.='? 1: '
+            endif
+            let filter.='(!s:_r.os.path.exists(file) && '.
+                        \ 'filewritable(s:_r.os.path.dirname(file))==2)'
+            let spec=spec[1:]
+        elseif spec[0] is 'p'
+            if !empty(filter)
+                let filter.='? 1: '
+            endif
+            let filter.='s:_r.os.path.inwpath(file)'
+            let spec=spec[1:]
+        endif
+        if spec is 'x'
+            if !empty(filter)
+                let filter='('.filter.') && '
+            endif
+            let filter.='executable(file)'
+        endif
+    endif
+    let getfilesstr=self.getfunstatvar('completers', s:F.getfiles, 'path')
+    return self.addmatches(getfilesstr.'('.self.comparg.', '.
+                \                          self.string(filter).', 1)', type([]))
+endfunction
 "▶1 `type'
 " Checks whether {argument} has one of given types
 let s:r.type={'args': ['*get']}
 endfunction
 let s:r._=s:r.any
 "▲1
-"Matchers ---------------------------------------------------------------------
+"Matchers ----------------------------------------------------------------------
 "▶1 `func'
 " Uses some other function as matcher. Function must accept a String (dot 
 " argument, can be overriden by explicitely supplying a list of arguments) and 
 " match in sorted list will be taken)
 let s:r.smart={'args': []}
 let s:smartfilters=[
-            \'v:val==?a:str',
-            \'v:val[:(lstr)] is a:str',
-            \'v:val[:(lstr)]==?a:str',
-            \'stridx(v:val, a:str)!=-1',
+            \'v:val==?str',
+            \'v:val[:(lstr)] is str',
+            \'v:val[:(lstr)]==?str',
+            \'stridx(v:val, str)!=-1',
             \'stridx(tolower(v:val), lowstr)!=-1',
             \'v:val=~#reg',
             \'v:val=~?reg',
         let list=filter(copy(a:ld), 'type(v:val)=='.type(''))
     endif
     let lowstr=tolower(a:str)
+    let str=a:str
     let lstr=len(a:str)-1
     let estr=escape(a:str, '\')
     let reg='\V'.join(split(estr, '\v[[:punct:]]@<=|[[:punct:]]@='), '\.\*')

plugin/frawor/os.vim

 function s:os.path.abspath(path)
     let path=fnamemodify(a:path, ':p')
     " Purge trailing path separator
-    return ((s:os.path.isdir(path) && len(path)>1)?(path[:-2]):(path))
+    return ((isdirectory(path) && len(path)>1)?(path[:-2]):(path))
 endfunction
 "▶3 os.path.realpath  :: path + FS → path
 function s:os.path.realpath(path)
         let oldpath=path
         let path=s:os.path.dirname(path)
     endwhile
-    if empty(r[0])
+    if !empty(r) && empty(r[0])
         let r[0]=path
     endif
     return r
     return !empty(glob(fnameescape(a:path)))
 endfunction
 "▶3 os.path.isdir     :: path + FS → Bool
-let s:os.path.isdir=function('isdirectory')
+function s:os.path.isdir(path)
+    return isdirectory(s:os.path.abspath(a:path))
+endfunction
 "▶3 os.path.isfile    :: path + FS → Bool
 function s:os.path.isfile(path)
     return s:os.path.exists(a:path) && !s:os.path.isdir(a:path)

test/fwccomplete.ok

 ::: Section <Built-in completion functions/either>
 ::: Section <Built-in completion functions/first>
 ::: Section <Built-in completion functions/if>
+::: Section <Built-in completion functions/path>
 ::: Section <Different sections/{required}>
 ::: Section <Different sections/{actions}>
 <<< messages

test/fwccompletetests.dat

 
   @a
   =sort(keys(s:dict))
+#▶2 path
+:call os.mkdir('FWCdirectory')
+:call os.chdir('FWCdirectory')
+:call os.mkdir('dir')
+:call os.mkdir('.bar')
+:let abcdefghi=os.path.join('abc', 'def', 'ghi')
+:let dirdef   =os.path.join('dir', 'def')
+:let foodef   =os.path.join('foo', 'def')
+:let dirdefghi=os.path.join(dirdef, 'ghi')
+:call writefile([], 'foo', 'b')
+:call os.makedirs(abcdefghi)
+`path w
+  @+
+  abc/ dir/ foo .bar/
+
+  @.
+  .bar/
+
+  @b
+  abc/ .bar/
+
+  @i
+  dir/
+
+  @a/d
+  abc/def/
+
+  @a/d/
+  abc/def/ghi/
+
+  @/h
+  /home/
 #▶1 Different sections
 #▶2 {required}
 `in list

test/gwine/fwccompletetests.dat

+!complete
+:let s:list=['abc', 'ab-c', 'def-ghi', 'adb']
+:let s:list2=['abc', 'def-b', 'geh']
+:let s:list3=['foo', 'bar', 'baz']
+:let s:dict={'abc': 1, 'adb': 1, 'aef': 1}
+#▶1 Built-in completion functions
+#▶2 in
+`in list
+  @+
+  =s:list
+
+  @b
+  abc ab-c adb
+
+  @j
+  =[]
+#▶2 key
+`key dict
+  @+
+  =sort(keys(s:dict))
+
+  @a
+  abc adb aef
+
+  @g
+  =[]
+#▶2 take
+`take dict exact
+  @+
+  =sort(keys(s:dict))
+
+  @a
+  abc adb aef
+
+  @g
+  =[]
+#▶2 either
+`either in list, key dict
+  @+
+  =s:list+sort(keys(s:dict))
+
+  @d
+  def-ghi
+#▶2 first
+`first in list, key dict
+  @+
+  =s:list
+
+  @d
+  def-ghi
+
+  @ae
+  aef
+#▶2 if
+`if match/^a/
++   in dict
++   in list3
+  @+
+  =s:list3
+
+  @a
+  =sort(keys(s:dict))
+#▶2 path
+:call os.mkdir('FWCdirectory')
+:call os.chdir('FWCdirectory')
+:call os.mkdir('dir')
+:let abcdefghi=os.path.join('abc', 'def', 'ghi')
+:let dirdef   =os.path.join('dir', 'def')
+:let foodef   =os.path.join('foo', 'def')
+:let dirdefghi=os.path.join(dirdef, 'ghi')
+:call writefile([], 'foo', 'b')
+:call os.makedirs(abcdefghi)
+`path w
+  @+
+  abc\ dir\ foo
+
+  @f
+  foo
+
+  @i
+  dir\
+
+  @a\d
+  abc\def\
+
+  @a\d\
+  abc\def\ghi\
+
+  @T:\
+  T:\rtp\ T:\test\ T:\vimrc
+#▶1 Different sections
+#▶2 {required}
+`in list
+  @+
+  =s:list
+
+  @b+
+  =[]
+`in list in list2
+  @+
+  =s:list
+
+  @b+
+  =s:list2
+#▶2 {actions}
+`<abc in list
++ dbf in list2
++ gh (in list2  key dict)>
+  @+
+  abc dbf gh
+
+  @b
+  abc dbf
+
+  @a+
+  =s:list
+
+  @d+
+  =s:list2
+
+  @g+
+  =s:list2
+
+  @g a+
+  =sort(keys(s:dict))
+
+`<abc in list
++ dbf in list2
++ gh (in list2  key dict)
++ - (key dict  in list)>
+  @+
+  =["abc", "dbf", "gh"]+sort(keys(s:dict))
+
+  @a+
+  =s:list
+
+  @x+
+  =s:list
+# vim: cms=#%s fmr=▶,▲ sw=2 ts=2 sts=2 et