From: Dusk Banks Date: Thu, 13 Jan 2022 11:50:22 +0000 (-0800) Subject: misc/vim: Further improve `:FactorVocab` completion X-Git-Tag: 0.99~1808 X-Git-Url: https://gitweb.factorcode.org/gitweb.cgi?p=factor.git;a=commitdiff_plain;h=2cbced67e74e2cca4740bb227897948f657cce22 misc/vim: Further improve `:FactorVocab` completion The approach to "vocab:" pathnames is more strict than Factor's, but it also is less likely to infinitely recurse. --- diff --git a/misc/vim/autoload/factor.vim b/misc/vim/autoload/factor.vim new file mode 100644 index 0000000000..bdcd51b52c --- /dev/null +++ b/misc/vim/autoload/factor.vim @@ -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: diff --git a/misc/vim/plugin/factor.vim b/misc/vim/plugin/factor.vim index 1a27b5a1ca..832ffc311c 100644 --- a/misc/vim/plugin/factor.vim +++ b/misc/vim/plugin/factor.vim @@ -1,107 +1,50 @@ +" Location: plugin/factor.vim + nmap fi :FactorVocabImpl nmap fd :FactorVocabDocs nmap ft :FactorVocabTests nmap fv :FactorVocab nmap fn :NewFactorVocab -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("") -command! -nargs=1 -complete=customlist,FactorCompleteVocab NewFactorVocab :call MakeFactorVocab("") -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(,"edit",) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,factor#complete_vocab_glob NewFactorVocab + \ execute factor#make_vocab_command(,"edit",) +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: