Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 75 additions & 7 deletions autoload/projectionist.vim
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ endfunction
function! projectionist#json_parse(string) abort
let string = type(a:string) == type([]) ? join(a:string, ' ') : a:string
if exists('*json_decode')
try
return json_decode(string)
catch
endtry
Expand Down Expand Up @@ -370,9 +369,27 @@ function! s:expand_placeholders(value, expansions, ...) abort
return !a:0 && value =~# "[\001-\006\016-\037]" ? '' : value
endfunction

let s:valid_key = '^\%([^*{}]*\*\*[^*{}]\{2\}\)\=[^*{}]*\*\=[^*{}]*$'
" test if pattern has backref like {1} or * followed by * (or **)
let s:valid_backref_key = '{\d}\|\v(^|[^*])\*[^*]+\*'
let s:valid_nobackref_key = '^\%([^*{}]*\*\*[^*{}]\{2\}\)\=[^*{}]*\*\=[^*{}]*$'
let s:valid_key = s:valid_nobackref_key . '\|' . s:valid_backref_key

function! s:match(file, pattern) abort
if a:pattern =~# s:valid_backref_key
let pattern = s:slash(a:pattern)
let file = s:slash(a:file)
let regex = substitute(pattern, '\*\*\/\=', "\x01", 'g')
let regex = substitute(regex, '\*', '\\v([^/]*)\\V', 'g')
let regex = substitute(regex, "\x01", '\\v(.{-})/=\\V', 'g')
let regex = substitute(regex, '{\(\d\)}', '\\\1', 'g')
let matches = matchlist(file, '\V\^' . regex . '\$')
if !empty(matches)
return join(filter(matches[1:], 'v:val !=# ""'), '/')
else
return ''
endif
endif

if a:pattern =~# '^[^*{}]*\*[^*{}]*$'
let pattern = s:slash(substitute(a:pattern, '\*', '**/*', ''))
elseif a:pattern =~# '^[^*{}]*\*\*[^*{}]\+\*[^*{}]*$'
Expand Down Expand Up @@ -760,8 +777,9 @@ endfunction
function! s:open_projection(mods, edit, variants, ...) abort
let formats = []
for variant in a:variants
call add(formats, variant[0] . projectionist#slash() . (variant[1] =~# '\*\*'
\ ? variant[1] : substitute(variant[1], '\*', '**/*', '')))
call add(formats, s:slash(variant[0] . projectionist#slash() . (variant[1] =~# '\*\*'
\ || variant[1] =~# s:valid_backref_key
\ ? variant[1] : substitute(variant[1], '\*', '**/*', ''))))
endfor
let cmd = s:parse(a:mods, a:000)
if get(cmd.args, -1, '') ==# '`=`'
Expand All @@ -771,9 +789,58 @@ function! s:open_projection(mods, edit, variants, ...) abort
if len(cmd.args)
call filter(formats, 'v:val =~# "\\*"')
let name = s:slash(join(cmd.args, ' '))
let dir = matchstr(name, '.*\ze/')
let base = substitute(name, '.*/', '', '')
call map(formats, 'substitute(substitute(v:val, "\\*\\*\\(/\\=\\)", empty(dir) ? "" : dir . "\\1", ""), "\\*", base, "")')
let mod_formats = []
for format in formats
if format =~# s:valid_backref_key
" match name=a/b/c with format=*/**/*
" by matching */** groups and fixed text around them
" i.e. fixed[0] . group[0] . fixed[1] ...

" Find fixed text parts around the * ** groups
let fixed = split(format, '\*\*/\=', 1)
let num_double = len(fixed) - 1
if num_double > 1
return 'echoerr "Only up to one ** group is allowed"'
endif
let fixed = split(fixed[0], '\*', 1) + fixed[1:]
let i_double = len(fixed) - num_double - 1
if num_double > 0
let fixed = fixed[:-2] + split(fixed[-1], '\*', 1)
endif

let groups = split(name, '/')
let n_extra = len(groups) - (len(fixed)-1) + num_double
if num_double == 0
if n_extra != 0
continue " not enough segments to match
endif
else
" for **, regroup the groups assuming ** matches 0-n segments
if n_extra < 0
continue " not enough segments to match
elseif n_extra == 0
let groups = (i_double > 0 ? groups[:i_double-1] : []) + [''] + groups[i_double:]
else
let groups = (i_double > 0 ? groups[:i_double-1] : []) +
\ [join(groups[i_double : i_double+n_extra-1], '/') . '/'] +
\ groups[i_double+n_extra : ]
endif
endif
" reconstruct the name with the new groups
let f = fixed[0]
for i in range(len(fixed) - 1)
let f .= groups[i] . fixed[i+1]
endfor
" handle backrefs like {1}
let f = substitute(f, '{\(\d\)}', '\=groups[submatch(1)-1]', 'g')
else " handle original format with ** and *
let dir = matchstr(name, '.*\ze/')
let base = substitute(name, '.*/', '', '')
let f = substitute(substitute(format, "\\*\\*\\(/\\=\\)", empty(dir) ? "" : dir . "\\1", ""), "\\*", base, "")
endif
call add(mod_formats, f)
endfor
let formats = mod_formats
else
let related_file = s:find_related_file(formats)
if !empty(related_file)
Expand Down Expand Up @@ -805,6 +872,7 @@ function! s:projection_complete(lead, cmdline, _) abort
continue
endif
let glob = substitute(format, '[^/]*\ze\*\*/\*', '', 'g')
let glob = substitute(glob, '{\d}', '*', 'g')
let results += map(projectionist#glob(glob), 's:match(v:val, format)')
endfor
call s:uniq(results)
Expand Down