Skip to content

Commit 0b6e2b6

Browse files
committed
Merge branch 'release-3.0.0'
2 parents 52eed14 + 7a4fbdf commit 0b6e2b6

3 files changed

Lines changed: 227 additions & 158 deletions

File tree

autoload/fetch.vim

Lines changed: 148 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ set cpoptions&vim
1010
let s:specs = {}
1111

1212
" - trailing colon, i.e. ':lnum[:colnum[:]]'
13-
" trigger with '?*:[0123456789]*' pattern
1413
let s:specs.colon = {'pattern': '\m\%(:\d\+\)\{1,2}:\?'}
1514
function! s:specs.colon.parse(file) abort
1615
let l:file = substitute(a:file, self.pattern, '', '')
@@ -19,7 +18,6 @@ function! s:specs.colon.parse(file) abort
1918
endfunction
2019

2120
" - trailing parentheses, i.e. '(lnum[:colnum])'
22-
" trigger with '?*([0123456789]*)' pattern
2321
let s:specs.paren = {'pattern': '\m(\(\d\+\%(:\d\+\)\?\))'}
2422
function! s:specs.paren.parse(file) abort
2523
let l:file = substitute(a:file, self.pattern, '', '')
@@ -28,16 +26,14 @@ function! s:specs.paren.parse(file) abort
2826
endfunction
2927

3028
" - Plan 9 type line spec, i.e. '[:]#lnum'
31-
" trigger with '?*#[0123456789]*' pattern
3229
let s:specs.plan9 = {'pattern': '\m:#\(\d\+\)'}
3330
function! s:specs.plan9.parse(file) abort
3431
let l:file = substitute(a:file, self.pattern, '', '')
3532
let l:pos = matchlist(a:file, self.pattern)[1]
3633
return [l:file, ['cursor', [l:pos, 0]]]
3734
endfunction
3835

39-
" - Pytest type method spec, i.e. ::method
40-
" trigger with '?*::?*' pattern
36+
" - Pytest type method spec, i.e. '::method'
4137
let s:specs.pytest = {'pattern': '\m::\(\w\+\)'}
4238
function! s:specs.pytest.parse(file) abort
4339
let l:file = substitute(a:file, self.pattern, '', '')
@@ -84,140 +80,174 @@ endfunction " }}}
8480
" and returning a List of
8581
" 0 unspec'ed path String
8682
" 1 position setting |call()| arguments List
87-
" @notes: the autocommand match patterns are not included
8883
function! fetch#specs() abort " {{{
8984
return deepcopy(s:specs)
9085
endfunction " }}}
9186

92-
" Resolve {spec} for the current buffer, substituting the resolved
93-
" file (if any) for it, with the cursor placed at the resolved position:
87+
" Resolve the buffer {bufname}, substituting the resolved file (if any) for
88+
" it, with the cursor placed at the resolved position:
9489
" @signature: fetch#buffer({spec:String})
9590
" @returns: Boolean
96-
function! fetch#buffer(spec) abort " {{{
97-
let l:bufname = expand('%')
98-
let l:spec = s:specs[a:spec]
91+
" @note: only buffers visible in the current tab page are resolved
92+
" @vimlint(EVL103, 1, a:bufname)
93+
function! fetch#buffer(bufname) abort " ({{{
94+
" no need for switching if the buffer is not on display
95+
let l:bufwinnr = bufwinnr(a:bufname)
96+
if l:bufwinnr is -1 | return 0 | endif
9997

100-
" exclude obvious non-matches
101-
if matchend(l:bufname, l:spec.pattern) isnot len(l:bufname)
102-
return 0
103-
endif
98+
" check for a matching spec, return if none matches
99+
for l:spec in values(s:specs)
100+
if matchend(a:bufname, l:spec.pattern) is len(a:bufname)
101+
break
102+
endif
103+
unlet! l:spec
104+
endfor
105+
" @vimlint(EVL104, 1, l:spec)
106+
if exists('l:spec') isnot 1 | return 0 | endif
104107

105108
" only substitute if we have a valid resolved file
106-
" and a spurious unresolved buffer both
107-
let [l:file, l:jump] = l:spec.parse(l:bufname)
108-
if !filereadable(l:file) || s:bufignore.detect(bufnr('%')) is 1
109+
" and a bona fide spurious unresolved buffer both
110+
let [l:file, l:jump] = l:spec.parse(a:bufname)
111+
if !filereadable(l:file) || s:bufignore.detect(bufnr(a:bufname)) is 1
109112
return 0
110113
endif
111114

112-
" we have a spurious unresolved buffer: set up for wiping
113-
set buftype=nowrite " avoid issues voiding the buffer
114-
set bufhidden=wipe " avoid issues with |bwipeout|
115+
" activate the correct window (if needed)
116+
let l:oldwinnr = s:gotowin(l:bufwinnr)
115117

116-
" substitute resolved file for unresolved buffer on arglist
117-
if has('listcmds')
118-
let l:argidx = index(argv(), l:bufname)
119-
if l:argidx isnot -1
120-
execute 'argdelete' fnameescape(l:bufname)
121-
execute l:argidx.'argadd' fnameescape(l:file)
118+
try
119+
" we have a spurious unresolved buffer: set up for wiping
120+
set buftype=nowrite " avoid issues voiding the buffer
121+
set bufhidden=wipe " avoid issues with |bwipeout|
122+
123+
" substitute resolved file for unresolved buffer on arglist
124+
let l:argidx = index(argv(), a:bufname)
125+
if l:argidx isnot -1
126+
if has('listcmds')
127+
" execute l:argidx.'argadd' fnameescape(l:file)
128+
execute 'argdelete' fnameescape(a:bufname)
129+
endif
130+
" set arglist index to resolved file if required
131+
let l:cmd = l:argidx.'argedit'
122132
endif
123-
endif
124133

125-
" set arglist index to resolved file if required
126-
" (needs to happen independently of arglist switching to work
127-
" with the double processing of the first -o/-O/-p window)
128-
if index(argv(), l:file) isnot -1
129-
let l:cmd = 'argedit'
130-
endif
134+
" edit resolved file and place cursor at position spec; we need to
135+
" suppress regular error handling or files after the offending one will
136+
" not be processed in VimEnter
137+
let l:shortmess = &shortmess
138+
set shortmess+=oO " avoid "Press ENTER" prompt on switch
139+
try
140+
execute 'keepalt' get(l:, 'cmd', 'edit').v:cmdarg fnameescape(l:file)
141+
catch
142+
echohl ErrorMsg | echomsg v:errmsg | echohl None
143+
if bufname('%') isnot l:file " do not return on unrelated errors
144+
return 0
145+
endif
146+
finally
147+
let &shortmess = l:shortmess
148+
endtry
149+
150+
if !empty(v:swapcommand)
151+
execute 'normal' v:swapcommand
152+
endif
153+
return s:setpos(l:jump)
131154

132-
" edit resolved file and place cursor at position spec
133-
let l:shortmess = &shortmess
134-
set shortmess+=oO " avoid "Press ENTER" prompt on switch
135-
try
136-
execute 'keepalt' get(l:, 'cmd', 'edit').v:cmdarg fnameescape(l:file)
137155
finally
138-
let &shortmess = l:shortmess
156+
call s:gotowin(l:oldwinnr)
139157
endtry
140-
if !empty(v:swapcommand)
141-
execute 'normal' v:swapcommand
142-
endif
143-
return s:setpos(l:jump)
144158
endfunction " }}}
159+
" @vimlint(EVL103, 0, a:bufname)
160+
" @vimlint(EVL104, 0, l:spec)
145161

146-
" Edit |<cfile>|, resolving a possible trailing spec:
147-
" @signature: fetch#cfile({count:Number})
148-
" @returns: Boolean
149-
" @notes: - will test all available specs for a match
150-
" - will fall back on Vim's |gF| when no spec matches
151-
function! fetch#cfile(count) abort " {{{
152-
let l:cfile = expand('<cfile>')
153-
154-
if !empty(l:cfile)
155-
" locate '<cfile>' in current line
156-
let l:pattern = '\M'.escape(l:cfile, '\')
157-
let l:position = searchpos(l:pattern, 'bcn', line('.'))
158-
if l:position == [0, 0]
159-
let l:position = searchpos(l:pattern, 'cn', line('.'))
160-
endif
162+
if has('file_in_path') " {{{
163+
" Edit |<cfile>|, resolving a possible trailing spec:
164+
" @signature: fetch#cfile({count:Number})
165+
" @returns: Boolean
166+
" @notes: - will test all available specs for a match
167+
" - will fall back on Vim's |gF| when no spec matches
168+
function! fetch#cfile(count) abort " {{{
169+
let l:cfile = expand('<cfile>')
161170

162-
" test for a trailing spec, accounting for multi-line '<cfile>' matches
163-
let l:lines = split(l:cfile, "\n")
164-
let l:line = getline(l:position[0] + len(l:lines) - 1)
165-
let l:offset = (len(l:lines) > 1 ? 0 : l:position[1]) + len(l:lines[-1]) - 1
166-
for l:spec in values(s:specs)
167-
if match(l:line, l:spec.pattern, l:offset) is l:offset
168-
let l:match = matchstr(l:line, l:spec.pattern, l:offset)
169-
" leverage Vim's own |gf| for opening the file
170-
execute 'normal!' a:count.'gf'
171-
return s:setpos(l:spec.parse(l:cfile.l:match)[1])
171+
if !empty(l:cfile)
172+
" locate '<cfile>' in current line
173+
let l:pattern = '\M'.escape(l:cfile, '\')
174+
let l:position = searchpos(l:pattern, 'bcn', line('.'))
175+
if l:position == [0, 0]
176+
let l:position = searchpos(l:pattern, 'cn', line('.'))
172177
endif
173-
endfor
174-
endif
175178

176-
" fall back to Vim's |gF|
177-
execute 'normal!' a:count.'gF'
178-
return 1
179-
endfunction " }}}
179+
" test for a trailing spec, accounting for multi-line '<cfile>' matches
180+
let l:lines = split(l:cfile, "\n")
181+
let l:line = getline(l:position[0] + len(l:lines) - 1)
182+
let l:offset = (len(l:lines) > 1 ? 0 : l:position[1]) + len(l:lines[-1]) - 1
183+
for l:spec in values(s:specs)
184+
if match(l:line, l:spec.pattern, l:offset) is l:offset
185+
let l:match = matchstr(l:line, l:spec.pattern, l:offset)
186+
" leverage Vim's own |gf| for opening the file
187+
execute 'normal!' a:count.'gf'
188+
return s:setpos(l:spec.parse(l:cfile.l:match)[1])
189+
endif
190+
endfor
191+
endif
180192

181-
" Edit the visually selected file, resolving a possible trailing spec:
182-
" @signature: fetch#visual({count:Number})
183-
" @returns: Boolean
184-
" @notes: - will test all available specs for a match
185-
" - will fall back on Vim's |gF| when no spec matches
186-
function! fetch#visual(count) abort " {{{
187-
" get text between last visual selection marks
188-
" adapted from http://stackoverflow.com/a/6271254/990363
189-
let [l:startline, l:startcol] = getpos("'<")[1:2]
190-
let [l:endline, l:endcol] = getpos("'>")[1:2]
191-
let l:endcol = min([l:endcol, col([l:endline, '$'])]) " 'V' col nr. bug
192-
let l:endcol -= &selection is 'inclusive' ? 0 : 1
193-
let l:lines = getline(l:startline, l:endline)
194-
if visualmode() isnot? 'v' " block-wise selection
195-
let l:endexpr = 'matchstr(v:val, "\\m^.*\\%'.string(l:endcol).'c.\\?")'
196-
call map(l:lines, 'strpart('.l:endexpr.', '.string(l:startcol-1).')')
197-
else
198-
let l:lines[-1] = matchstr(lines[-1], '\m^.*\%'.string(l:endcol).'c.\?')
199-
let l:lines[0] = strpart(l:lines[0], l:startcol-1)
200-
endif
201-
let l:selection = join(l:lines, "\n")
202-
203-
" test for a trailing spec
204-
if !empty(l:selection)
205-
let l:line = getline(l:endline)
206-
for l:spec in values(s:specs)
207-
if match(l:line, l:spec.pattern, l:endcol) is l:endcol
208-
let l:match = matchstr(l:line, l:spec.pattern, l:endcol)
209-
call s:dovisual(a:count.'gf') " leverage Vim's |gf| to get the file
210-
return s:setpos(l:spec.parse(l:selection.l:match)[1])
193+
" fall back to Vim's |gF|
194+
execute 'normal!' a:count.'gF'
195+
return 1
196+
endfunction " }}}
197+
198+
if has('visual') " {{{
199+
" Edit the visually selected file, resolving a possible trailing spec:
200+
" @signature: fetch#visual({count:Number})
201+
" @returns: Boolean
202+
" @notes: - will test all available specs for a match
203+
" - will fall back on Vim's |gF| when no spec matches
204+
function! fetch#visual(count) abort " {{{
205+
" get text between last visual selection marks
206+
" adapted from http://stackoverflow.com/a/6271254/990363
207+
let [l:startline, l:startcol] = getpos("'<")[1:2]
208+
let [l:endline, l:endcol] = getpos("'>")[1:2]
209+
let l:endcol = min([l:endcol, col([l:endline, '$'])]) " 'V' col nr. bug
210+
let l:endcol -= &selection is 'inclusive' ? 0 : 1
211+
let l:lines = getline(l:startline, l:endline)
212+
if visualmode() isnot? 'v' " block-wise selection
213+
let l:endexpr = 'matchstr(v:val, "\\m^.*\\%'.string(l:endcol).'c.\\?")'
214+
call map(l:lines, 'strpart('.l:endexpr.', '.string(l:startcol-1).')')
215+
else
216+
let l:lines[-1] = matchstr(lines[-1], '\m^.*\%'.string(l:endcol).'c.\?')
217+
let l:lines[0] = strpart(l:lines[0], l:startcol-1)
211218
endif
212-
endfor
213-
endif
219+
let l:selection = join(l:lines, "\n")
214220

215-
" fall back to Vim's |gF|
216-
call s:dovisual(a:count.'gF')
217-
return 1
218-
endfunction " }}}
221+
" test for a trailing spec
222+
if !empty(l:selection)
223+
let l:line = getline(l:endline)
224+
for l:spec in values(s:specs)
225+
if match(l:line, l:spec.pattern, l:endcol) is l:endcol
226+
let l:match = matchstr(l:line, l:spec.pattern, l:endcol)
227+
call s:dovisual(a:count.'gf') " leverage Vim's |gf| to get the file
228+
return s:setpos(l:spec.parse(l:selection.l:match)[1])
229+
endif
230+
endfor
231+
endif
232+
233+
" fall back to Vim's |gF|
234+
call s:dovisual(a:count.'gF')
235+
return 1
236+
endfunction " }}}
237+
endif " }}}
238+
endif " }}}
219239

220240
" Private helper functions: {{{
241+
" - go to window {winnr} without affecting editor state
242+
" returns the original window number
243+
function! s:gotowin(winnr) abort
244+
let l:curwinnr = bufwinnr('%')
245+
if a:winnr isnot -1 && l:curwinnr isnot a:winnr
246+
execute 'silent keepjumps noautocmd '.a:winnr.'wincmd w'
247+
endif
248+
return l:curwinnr
249+
endfunction
250+
221251
" - place the current buffer's cursor, triggering the "BufFetchPosX" events
222252
" see :h call() for the format of the {calldata} List
223253
function! s:setpos(calldata) abort
@@ -239,11 +269,13 @@ function! s:doautocmd(pattern) abort
239269
endif
240270
endfunction
241271

242-
" - send command to the last visual selection
243-
function! s:dovisual(command) abort
244-
let l:cmd = index(['v', 'V', ''], mode()) is -1 ? 'gv'.a:command : a:command
245-
execute 'normal!' l:cmd
246-
endfunction
272+
if has('visual')
273+
" - send command to the last visual selection
274+
function! s:dovisual(command) abort
275+
let l:cmd = index(['v', 'V', ''], mode()) is -1 ? 'gv'.a:command : a:command
276+
execute 'normal!' l:cmd
277+
endfunction
278+
endif
247279
" }}}
248280

249281
let &cpoptions = s:cpoptions

0 commit comments

Comments
 (0)