]> gitweb.factorcode.org Git - factor.git/commitdiff
misc/vim: Further improve `:FactorVocab` completion
authorDusk Banks <me@bb010g.com>
Thu, 13 Jan 2022 11:50:22 +0000 (03:50 -0800)
committerJohn Benediktsson <mrjbq7@gmail.com>
Fri, 14 Jan 2022 17:03:41 +0000 (09:03 -0800)
The approach to "vocab:" pathnames is more strict than Factor's, but it
also is less likely to infinitely recurse.

misc/vim/autoload/factor.vim [new file with mode: 0644]
misc/vim/plugin/factor.vim

diff --git a/misc/vim/autoload/factor.vim b/misc/vim/autoload/factor.vim
new file mode 100644 (file)
index 0000000..bdcd51b
--- /dev/null
@@ -0,0 +1,191 @@
+" Location:     autoload/factor.vim
+
+" Section: Utilities
+
+let s:path_sep_pattern = (exists('+shellslash') ? '[\\/]' : '/')..'\+'
+
+" Remove the separator at the end of a:path.
+" (Modified from vim-jp/vital.vim.)
+function! s:remove_last_path_sep(path) abort
+  return substitute(a:path, s:path_sep_pattern..'$', '', '')
+endfunction
+
+function! s:ensure_last_path_sep(path) abort
+  return a:path =~# s:path_sep_pattern..'$' ? a:path
+        \ : a:path..(!exists('+shellslash') || &shellslash ? '/' : '\')
+endfunction
+
+if !exists('g:FactorGlobEscape')
+  if exists('+shellslash') && !&shellslash
+    let g:FactorGlobEscape = '*[]?`{$'
+  else
+    let g:FactorGlobEscape = '*[]?`{$\'
+  endif
+endif
+
+" (Based on tpope/vim-scriptease.)
+function! s:glob(pattern, nosuf = 0, alllinks = 0) abort
+  if v:version >= 704
+    return glob(a:pattern, a:nosuf, 1, a:alllinks)
+  else
+    return split(glob(a:pattern, a:nosuf), "\n")
+  endif
+endfunction
+
+" Section: File discovery & globbing
+
+function! factor#get_vocab_roots() abort
+  if exists('g:FactorVocabRoots')
+    return g:FactorVocabRoots
+  endif
+  if !exists('g:FactorAdditionalVocabRoots')
+    try
+      let g:FactorAdditionalVocabRoots = map(
+            \ filter(readfile(fnamemodify('~/.factor-roots', ':p')), 'v:val !=# '''''),
+            \ 's:remove_last_path_sep(v:val)')
+    catch /^Vim\%((\a\+)\)\=:E484/
+      let g:FactorAdditionalVocabRoots = []
+    endtry
+  endif
+  let g:FactorVocabRoots =
+        \ map(g:FactorDefaultVocabRoots + g:FactorAdditionalVocabRoots, 's:remove_last_path_sep(v:val)')
+  return g:FactorVocabRoots
+endfunction
+
+function! factor#expand_vocab_roots(vocab_roots)
+  let sep = !exists('+shellslash') || &shellslash ? '/' : '\'
+  let expanded_vocab_roots = []
+  for vocab_root in a:vocab_roots
+    if vocab_root =~# '^vocab:'
+      let expanded_vocab_roots_len = len(expanded_vocab_roots)
+      let i = 0
+      while i < expanded_vocab_roots_len
+        call add(expanded_vocab_roots,
+              \ s:ensure_last_path_sep(expanded_vocab_roots[i])..vocab_root[6:])
+        let i += 1
+      endwhile
+    else
+      call add(expanded_vocab_roots,
+            \ vocab_root =~# '^resource:' ? g:FactorResourcePath..vocab_root[9:] : vocab_root)
+    endif
+  endfor
+  return expanded_vocab_roots
+endfunction
+
+function! factor#detect_parent_vocab_roots(vocab_roots, fname, expr, nosuf = 0, alllinks = 0) abort
+  let sep = !exists('+shellslash') || &shellslash ? '/' : '\'
+  let parent_vocab_roots = []
+  let expanded_vocab_roots = {}
+  for expanded_vocab_root in factor#expand_vocab_roots(a:vocab_roots)
+    let expanded_vocab_roots[fnamemodify(expanded_vocab_root, ':p')] = 1
+  endfor
+  let current_path = fnamemodify(a:fname, ':p')
+  while current_path !=# ''
+    let current_path_glob = s:ensure_last_path_sep(escape(current_path, g:FactorGlobEscape))
+    let paths = s:glob(current_path_glob..a:expr, a:nosuf, a:alllinks)
+    for path in paths
+      let path = fnamemodify(path, ':p')
+      if get(expanded_vocab_roots, path, 0)
+        call add(parent_vocab_roots, path)
+      end
+    endfor
+    let current_path = current_path ==# '/' ? '' : fnamemodify(current_path, ':h')
+  endwhile
+  return parent_vocab_roots
+endfunction
+
+" (Based on tpope/vim-scriptease.)
+function! factor#glob(expr, vocab = 0, trailing_dir_sep = 0, output = 0, nosuf = 0, alllinks = 0) abort
+  let sep = !exists('+shellslash') || &shellslash ? '/' : '\'
+  let expr = a:vocab ? 'vocab:'..substitute(a:expr, '\.', sep, 'g') : a:expr
+  let found = {}
+  if expr =~# '^resource:'
+    for path_root in s:glob(escape(g:FactorResourcePath, g:FactorGlobEscape), 1, 1)
+      let path_root = fnamemodify(path_root, ':p')
+      for path in s:glob(escape(path_root, g:FactorGlobEscape)..expr[9:], a:nosuf, a:alllinks)
+        if a:trailing_dir_sep == 1 | let path = fnamemodify(path, ':p') | elseif a:trailing_dir_sep == 2
+          let path = s:remove_last_path_sep(fnamemodify(path, ':p'))
+        endif
+        let path = a:output == 0 ? 'resource:'..path[strlen(path_root):] : path
+        let found[path] = 1
+      endfor
+    endfor
+  elseif expr =~# '^vocab:'
+    let expanded_vocab_roots = factor#expand_vocab_roots(factor#get_vocab_roots())
+    for vocab_root in expanded_vocab_roots
+      for path_root in s:glob(escape(vocab_root, g:FactorGlobEscape), 1, 1)
+        let path_root = fnamemodify(path_root, ':p')
+        for path in s:glob(escape(path_root, g:FactorGlobEscape)..expr[6:], a:nosuf, a:alllinks)
+          if a:trailing_dir_sep == 1 | let path = fnamemodify(path, ':p') | elseif a:trailing_dir_sep == 2
+            let path = s:remove_last_path_sep(fnamemodify(path, ':p'))
+          endif
+          let path = a:output == 0 ? 'vocab:'..path[strlen(path_root):] : a:output == 1 ? path
+                \ : substitute(path[strlen(path_root):], s:path_sep_pattern, '.', 'g')
+          let found[path] = 1
+        endfor
+      endfor
+    endfor
+  else
+    if expr =~# '^\%[resource]\*' | let found['resource:'] = 1 | endif
+    if expr =~# '^\%[vocab]\*' | let found['vocab:'] = 1 | endif
+    for expr_path in s:glob(expr, 1, 1)
+      let expr_path = fnamemodify(expr_path, ':p')
+      for vocab_root in factor#get_vocab_roots()
+        if vocab_root =~# '^resource:|^vocab:' | continue | endif
+        for path_root in s:glob(escape(vocab_root, g:FactorGlobEscape), 1, 1)
+          let path_root = fnamemodify(path_root, ':p')
+          if expr_path[0:strlen(path_root)] !=# path_root | break | endif
+          for path in s:glob(escape(path_root, g:FactorGlobEscape)..expr[strlen(path_root):], a:nosuf, a:alllinks)
+            if a:trailing_dir_sep == 1 | let path = fnamemodify(path, ':p') | elseif a:trailing_dir_sep == 2
+              let path = s:remove_last_path_sep(fnamemodify(path, ':p'))
+            endif
+            let path = a:output == 0 ? path[strlen(path_root):] : a:output == 1 ? path
+                  \ : substitute(path[strlen(path_root):], s:path_sep_pattern, '.', 'g')
+            let found[path] = 1
+          endfor
+        endfor
+      endfor
+    endfor
+  endif
+  return sort(keys(found))
+endfunction
+
+" Section: Completion
+
+function! factor#complete_glob(arg_lead, cmd_line, cursor_pos) abort
+  return factor#glob(a:arg_lead..'*', 0, 1)
+endfunction
+
+function! factor#complete_vocab_glob(arg_lead, cmd_line, cursor_pos) abort
+  return factor#glob(a:arg_lead..'*.', 1, 2, 2)
+endfunction
+
+" Section: Commands
+
+function! factor#go_to_vocab_command(count, cmd, vocab) abort
+  let sep = !exists('+shellslash') || &shellslash ? '/' : '\'
+  let vocab_glob = 'vocab:'..substitute(a:vocab, '\.', sep, 'g')..sep..matchstr(a:vocab, '[^.]*$')..'.factor'
+  let vocab_file = get(factor#glob(vocab_glob, 0, 2, 1), a:count - 1, 0)
+  if !!vocab_file
+    return 'echoerr '..string('Factor: Can''t find vocabulary '..a:vocab..' in vocabulary roots')
+  endif
+  return a:cmd..' '..fnameescape(vocab_file)
+endfunction
+
+function! factor#make_vocab_command(count, cmd, vocab) abort
+  let sep = !exists('+shellslash') || &shellslash ? '/' : '\'
+  let new_vocab_root = FactorNewVocabRoot()
+  let vocab_dir = get(factor#glob(new_vocab_root, 0, 1, 1), a:count - 1, 0)
+  if !!vocab_dir
+      return 'echoerr '..string('Factor: Can''t find new vocabulary root '..
+            \ string(new_vocab_root)..' in vocabulary roots')
+  endif
+  let vocab_dir = fnamemodify(vocab_dir..substitute(a:vocab, '\.', sep, 'g'), ':~')
+  echo vocab_dir
+  let vocab_file = vocab_dir..sep..fnamemodify(vocab_dir, ':t')..'.factor'
+  echo vocab_file
+  call mkdir(vocab_dir, 'p')
+  return a:cmd..' '..fnameescape(vocab_file)
+endfunction
+
+" vim:sw=2:et:
index 1a27b5a1ca26822b3f10c4c72b1f1ad4f82aafe2..832ffc311c8abbe314cc64eb3aee2cde25c982d4 100644 (file)
+" Location:     plugin/factor.vim
+
 nmap <silent> <Leader>fi :FactorVocabImpl<CR>
 nmap <silent> <Leader>fd :FactorVocabDocs<CR>
 nmap <silent> <Leader>ft :FactorVocabTests<CR>
 nmap <Leader>fv :FactorVocab<SPACE>
 nmap <Leader>fn :NewFactorVocab<SPACE>
 
-if !exists("g:FactorRoot")
-    let g:FactorRoot = "~/factor"
-endif
-
-if !exists("g:FactorVocabRoots")
-    let g:FactorVocabRoots = ["core", "basis", "extra", "work"]
+if !exists('g:FactorResourcePath')
+    let g:FactorResourcePath = '~/factor/'
 endif
 
-if !exists("g:FactorNewVocabRoot")
-    let g:FactorNewVocabRoot = "work"
+if !exists('g:FactorDefaultVocabRoots')
+    let g:FactorDefaultVocabRoots = ['resource:core', 'resource:basis', 'resource:extra', 'resource:work']
 endif
+" let g:FactorAdditionalVocabRoots = ... " see autoload/factor.vim
+unlet! g:FactorVocabRoots
 
-command! -nargs=1 -complete=customlist,FactorCompleteVocab FactorVocab :call GoToFactorVocab("<args>")
-command! -nargs=1 -complete=customlist,FactorCompleteVocab NewFactorVocab :call MakeFactorVocab("<args>")
-command! FactorVocabImpl  :call GoToFactorVocabImpl()
-command! FactorVocabDocs  :call GoToFactorVocabDocs()
-command! FactorVocabTests :call GoToFactorVocabTests()
-
-function! FactorVocabRoot(root)
-    let cwd = getcwd()
-    exe "lcd " fnameescape(g:FactorRoot)
-    let vocabroot = fnamemodify(a:root, ":p")
-    exe "lcd " fnameescape(cwd)
-    return vocabroot
-endfunction
-
-function! s:unique(list)
-    let dict = {}
-    for value in a:list
-        let dict[value] = 1
-    endfor
-    return sort(keys(dict))
-endfunction
-
-function! FactorCompleteVocab(arglead, cmdline, cursorpos)
-    let vocabs = []
-    let vocablead = substitute(a:arglead, "\\.", "/", "g")
-    for root in g:FactorVocabRoots
-        let vocabroot = FactorVocabRoot(root)
-        let newvocabs = globpath(vocabroot, vocablead . "*")
-        if newvocabs != ""
-            let newvocabsl = split(newvocabs, "\n")
-            let newvocabsl = filter(newvocabsl, 'getftype(v:val) == "dir"')
-            let newvocabsl = map(newvocabsl, 'substitute(v:val, "^\\V" . escape(vocabroot, "\\"), "\\1", "g")')
-            let vocabs += newvocabsl
-        endif
-    endfor
-    let vocabs = s:unique(vocabs)
-    let vocabs = map(vocabs, 'substitute(v:val, "/\\|\\\\", ".", "g")')
-    return vocabs
-endfunction
-
-function! FactorVocabFile(root, vocab, mustexist)
-    let vocabpath = substitute(a:vocab, "\\.", "/", "g")
-    let vocabfile = FactorVocabRoot(a:root) . vocabpath . "/" . fnamemodify(vocabpath, ":t") . ".factor"
-
-    if !a:mustexist || getftype(vocabfile) != ""
-        return vocabfile
-    else
-        return ""
-    endif
-endfunction
+if !exists('*FactorNewVocabRoot') | function! FactorNewVocabRoot() abort
+    return 'resource:work'
+endfunction | endif
 
-function! GoToFactorVocab(vocab)
-    for root in g:FactorVocabRoots
-        let vocabfile = FactorVocabFile(root, a:vocab, 1)
-        if vocabfile != ""
-            exe "edit " fnameescape(vocabfile)
-            return
-        endif
-    endfor
-    echo "Vocabulary " vocab " not found"
-endfunction
-
-function! MakeFactorVocab(vocab)
-    let vocabfile = FactorVocabFile(g:FactorNewVocabRoot, a:vocab, 0)
-    echo vocabfile
-    let vocabdir = fnamemodify(vocabfile, ":h")
-    echo vocabdir
-    exe "!mkdir -p " shellescape(vocabdir)
-    exe "edit " fnameescape(vocabfile)
-endfunction
+command! -bar -bang -range=1 -nargs=1 -complete=customlist,factor#complete_vocab_glob FactorVocab
+            \ execute factor#go_to_vocab_command(<count>,"edit<bang>",<q-args>)
+command! -bar -bang -range=1 -nargs=1 -complete=customlist,factor#complete_vocab_glob NewFactorVocab
+            \ execute factor#make_vocab_command(<count>,"edit<bang>",<q-args>)
+command! FactorVocabImpl -bar :call GoToFactorVocabImpl()
+command! FactorVocabDocs -bar :call GoToFactorVocabDocs()
+command! FactorVocabTests -bar :call GoToFactorVocabTests()
 
 function! FactorFileBase()
-    let filename = expand("%:r")
-    let filename = substitute(filename, "-docs", "", "")
-    let filename = substitute(filename, "-tests", "", "")
+    let filename = expand('%:r')
+    let filename = substitute(filename, '-docs', '', '')
+    let filename = substitute(filename, '-tests', '', '')
     return filename
 endfunction
 
 function! GoToFactorVocabImpl()
-    exe "edit " fnameescape(FactorFileBase() . ".factor")
+    exe 'edit ' fnameescape(FactorFileBase() . '.factor')
 endfunction
 
 function! GoToFactorVocabDocs()
-    exe "edit " fnameescape(FactorFileBase() . "-docs.factor")
+    exe 'edit ' fnameescape(FactorFileBase() . '-docs.factor')
 endfunction
 
 function! GoToFactorVocabTests()
-    exe "edit " fnameescape(FactorFileBase() . "-tests.factor")
+    exe 'edit ' fnameescape(FactorFileBase() . '-tests.factor')
 endfunction
+
+" vim:sw=4:et: