@@ -10,7 +10,6 @@ set cpoptions&vim
1010let s: specs = {}
1111
1212" - trailing colon, i.e. ':lnum[:colnum[:]]'
13- " trigger with '?*:[0123456789]*' pattern
1413let s: specs .colon = {' pattern' : ' \m\%(:\d\+\)\{1,2}:\?' }
1514function ! 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
1918endfunction
2019
2120" - trailing parentheses, i.e. '(lnum[:colnum])'
22- " trigger with '?*([0123456789]*)' pattern
2321let s: specs .paren = {' pattern' : ' \m(\(\d\+\%(:\d\+\)\?\))' }
2422function ! 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
2826endfunction
2927
3028" - Plan 9 type line spec, i.e. '[:]#lnum'
31- " trigger with '?*#[0123456789]*' pattern
3229let s: specs .plan9 = {' pattern' : ' \m:#\(\d\+\)' }
3330function ! 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 ]]]
3734endfunction
3835
39- " - Pytest type method spec, i.e. ::method
40- " trigger with '?*::?*' pattern
36+ " - Pytest type method spec, i.e. '::method'
4137let s: specs .pytest = {' pattern' : ' \m::\(\w\+\)' }
4238function ! 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
8883function ! fetch#specs () abort " {{{
8984 return deepcopy (s: specs )
9085endfunction " }}}
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 )
144158endfunction " }}}
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
223253function ! s: setpos (calldata) abort
@@ -239,11 +269,13 @@ function! s:doautocmd(pattern) abort
239269 endif
240270endfunction
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
249281let &cpoptions = s: cpoptions
0 commit comments