Skip to content

Commit 12f3fac

Browse files
ejonestpope
authored andcommitted
Search related files as default for :Efoo
Adds a fallback for the navigation commands to search related files for a file matching the corresponding type. Related files are searched recursively, breadth-first, and recursion is limited to a maximum depth. In addition to moving "outward" from the current file, the search moves "inward" from files matching the target type. If the "inward" search for a file intersects with the "outward" search, that file is considered a match for navigation. Fixes tpope#80, tpope#26, tpope#22
1 parent ea1347b commit 12f3fac

File tree

3 files changed

+86
-4
lines changed

3 files changed

+86
-4
lines changed

README.markdown

+6-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ Here are some examples for this very project:
7373

7474
With these in place, you could use `:Eplugin projectionist` to edit
7575
`plugin/projectionist.vim` and `:Edoc projectionist` to edit
76-
`doc/projectionist.txt`. For `README.markdown`, since there's no glob, it
77-
becomes the default destination for `:Edoc` if no argument is given.
76+
`doc/projectionist.txt`. If no argument is given, it will edit an alternate
77+
file of that type (see below) or a projection without a glob. So in this
78+
example `:Edoc` would default to editing `README.markdown`.
7879

7980
The `E` stands for `edit`. You also get `S`, `V`, and `T` variants that
8081
`split`, `vsplit`, and `tabedit`.
@@ -95,6 +96,9 @@ implementation and test:
9596
"src/test/java/*.java": {"alternate": "src/main/java/{}.java"}
9697
}
9798

99+
In addition, the navigation commands (like `:Eplugin` above) will search
100+
alternates when no argument is given to edit a related file of that type.
101+
98102
Bonus feature: `:A {filename}` edits a file relative to the root of the
99103
project.
100104

autoload/projectionist.vim

+73-1
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,41 @@ function! projectionist#query_file(key, ...) abort
450450
return s:uniq(files)
451451
endfunction
452452

453+
let s:projectionist_max_file_recursion = 3
454+
455+
function! s:query_file_recursive(key, ...) abort
456+
let keys = type(a:key) == type([]) ? a:key : [a:key]
457+
let start_file = get(a:0 ? a:1 : {}, 'file', get(b:, 'projectionist_file', expand('%:p')))
458+
let files = []
459+
let visited_files = {start_file: 1}
460+
let current_files = [start_file]
461+
let depth = 0
462+
while !empty(current_files) && depth < s:projectionist_max_file_recursion
463+
let next_files = []
464+
for file in current_files
465+
let query_opts = extend(a:0 ? copy(a:1) : {}, {'file': file})
466+
for key in keys
467+
let [root, match] = get(projectionist#query(key, query_opts), 0, ['', []])
468+
let subfiles = type(match) == type([]) ? copy(match) : [match]
469+
call map(filter(subfiles, 'len(v:val)'), 's:absolute(v:val, root)')
470+
if !empty(subfiles)
471+
break
472+
endif
473+
endfor
474+
for subfile in subfiles
475+
if !has_key(visited_files, subfile)
476+
let visited_files[subfile] = 1
477+
call add(files, subfile)
478+
call add(next_files, subfile)
479+
endif
480+
endfor
481+
endfor
482+
let current_files = next_files
483+
let depth += 1
484+
endwhile
485+
return files
486+
endfunction
487+
453488
function! s:shelljoin(val) abort
454489
return substitute(s:join(a:val), '["'']\([{}]\)["'']', '\1', 'g')
455490
endfunction
@@ -674,6 +709,38 @@ function! projectionist#navigation_commands() abort
674709
return commands
675710
endfunction
676711

712+
function! s:find_related_file(patterns) abort
713+
let alternates = s:query_file_recursive(['related', 'alternate'], {'lnum': 0})
714+
for alternate in alternates
715+
for pattern in a:patterns
716+
if !empty(s:match(alternate, pattern))
717+
return alternate
718+
endif
719+
endfor
720+
endfor
721+
let current_file = get(b:, 'projectionist_file', expand('%:p'))
722+
for pattern in a:patterns
723+
if pattern !~# '\*'
724+
continue
725+
endif
726+
for candidate in projectionist#glob(pattern)
727+
let candidate_alternates = s:query_file_recursive(
728+
\ ['related', 'alternate'],
729+
\ {'lnum': 0, 'file': candidate})
730+
for candidate_alternate in candidate_alternates
731+
if candidate_alternate ==# current_file
732+
return candidate
733+
endif
734+
for alternate in alternates
735+
if alternate ==# candidate_alternate
736+
return candidate
737+
endif
738+
endfor
739+
endfor
740+
endfor
741+
endfor
742+
endfunction
743+
677744
function! s:open_projection(mods, edit, variants, ...) abort
678745
let formats = []
679746
for variant in a:variants
@@ -692,7 +759,12 @@ function! s:open_projection(mods, edit, variants, ...) abort
692759
let base = matchstr(name, '[^\/]*$')
693760
call map(formats, 'substitute(substitute(v:val, "\\*\\*\\([\\/]\\=\\)", empty(dir) ? "" : dir . "\\1", ""), "\\*", base, "")')
694761
else
695-
call filter(formats, 'v:val !~# "\\*"')
762+
let related_file = s:find_related_file(formats)
763+
if !empty(related_file)
764+
let formats = [related_file]
765+
else
766+
call filter(formats, 'v:val !~# "\\*"')
767+
endif
696768
endif
697769
if empty(formats)
698770
return 'echoerr "Invalid number of arguments"'

doc/projectionist.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ The full list of available properties in a projection is as follows:
6262
*projectionist-alternate*
6363
"alternate" ~
6464
Determines the destination of the |projectionist-:A| command. If this
65-
is a list, the first readable file will be used.
65+
is a list, the first readable file will be used. Will also be used as
66+
a default for |projectionist-related|.
6667
*projectionist-console*
6768
"console" ~
6869
Command to run to start a REPL or other interactive shell. Will be
@@ -99,6 +100,11 @@ The full list of available properties in a projection is as follows:
99100
this option is provided for a literal filename rather than a glob, it
100101
is used as the default destination of the navigation command when no
101102
argument is given.
103+
*projectionist-related*
104+
"related" ~
105+
Indicates one or more files to search when a navigation command is
106+
called without an argument, to find a default destination. Related
107+
files are searched recursively.
102108

103109
*g:projectionist_heuristics*
104110
In addition to ".projections.json", projections can be defined globally

0 commit comments

Comments
 (0)