-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #3.
- Loading branch information
Showing
6 changed files
with
302 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,143 +1,240 @@ | ||
" AUTOLOAD FUNCTION LIBRARY FOR VIM-FETCH | ||
let s:cpo = &cpo | ||
set cpo&vim | ||
if &compatible || v:version < 700 | ||
finish | ||
endif | ||
|
||
" Position specs Dictionary: | ||
let s:cpoptions = &cpoptions | ||
set cpoptions&vim | ||
|
||
" Position specs Dictionary: {{{ | ||
let s:specs = {} | ||
|
||
" - trailing colon, i.e. ':lnum[:colnum[:]]' | ||
" trigger with '?*:[0123456789]*' pattern | ||
let s:specs.colon = {'pattern': '\m\%(:\d\+\)\{1,2}:\?$'} | ||
let s:specs.colon = {'pattern': '\m\%(:\d\+\)\{1,2}:\?'} | ||
function! s:specs.colon.parse(file) abort | ||
return [substitute(a:file, self.pattern, '', ''), | ||
\ split(matchstr(a:file, self.pattern), ':')] | ||
let l:file = substitute(a:file, self.pattern, '', '') | ||
let l:pos = split(matchstr(a:file, self.pattern), ':') | ||
return [l:file, ['cursor', [l:pos[0], get(l:pos, 1, 0)]]] | ||
endfunction | ||
|
||
" - trailing parentheses, i.e. '(lnum[:colnum])' | ||
" trigger with '?*([0123456789]*)' pattern | ||
let s:specs.paren = {'pattern': '\m(\(\d\+\%(:\d\+\)\?\))$'} | ||
let s:specs.paren = {'pattern': '\m(\(\d\+\%(:\d\+\)\?\))'} | ||
function! s:specs.paren.parse(file) abort | ||
return [substitute(a:file, self.pattern, '', ''), | ||
\ split(matchlist(a:file, self.pattern)[1], ':')] | ||
let l:file = substitute(a:file, self.pattern, '', '') | ||
let l:pos = split(matchlist(a:file, self.pattern)[1], ':') | ||
return [l:file, ['cursor', [l:pos[0], get(l:pos, 1, 0)]]] | ||
endfunction | ||
|
||
" - Plan 9 type line spec, i.e. '[:]#lnum' | ||
" trigger with '?*#[0123456789]*' pattern | ||
let s:specs.plan9 = {'pattern': '\m:#\(\d\+\)$'} | ||
let s:specs.plan9 = {'pattern': '\m:#\(\d\+\)'} | ||
function! s:specs.plan9.parse(file) abort | ||
return [substitute(a:file, self.pattern, '', ''), | ||
\ [matchlist(a:file, self.pattern)[1]]] | ||
let l:file = substitute(a:file, self.pattern, '', '') | ||
let l:pos = matchlist(a:file, self.pattern)[1] | ||
return [l:file, ['cursor', [l:pos, 0]]] | ||
endfunction | ||
|
||
" Detection methods for buffers that bypass `filereadable()`: | ||
let s:ignore = [] | ||
|
||
" - non-file buffer types | ||
call add(s:ignore, {'types': ['quickfix', 'acwrite', 'nofile']}) | ||
function! s:ignore[-1].detect(buffer) abort | ||
return index(self.types, getbufvar(a:buffer, '&buftype')) isnot -1 | ||
" - Pytest type method spec, i.e. ::method | ||
" trigger with '?*::?*' pattern | ||
let s:specs.pytest = {'pattern': '\m::\(\w\+\)'} | ||
function! s:specs.pytest.parse(file) abort | ||
let l:file = substitute(a:file, self.pattern, '', '') | ||
let l:name = matchlist(a:file, self.pattern)[1] | ||
let l:method = '\m\C^\s*def\s\+\%(\\\n\s*\)*\zs'.l:name.'\s*(' | ||
return [l:file, ['search', [l:method, 'cw']]] | ||
endfunction " }}} | ||
|
||
" Detection heuristics for buffers that should not be resolved: {{{ | ||
let s:bufignore = {'freaks': []} | ||
function! s:bufignore.detect(bufnr) abort | ||
for l:freak in self.freaks | ||
if l:freak.detect(a:bufnr) is 1 | ||
return 1 | ||
endif | ||
endfor | ||
return filereadable(bufname(a:bufnr)) | ||
endfunction | ||
|
||
" - non-document file types that do not trigger the above | ||
" not needed for: Unite / VimFiler / VimShell / CtrlP / Conque-Shell | ||
call add(s:ignore, {'types': ['netrw']}) | ||
function! s:ignore[-1].detect(buffer) abort | ||
return index(self.types, getbufvar(a:buffer, '&filetype')) isnot -1 | ||
" - unlisted status as a catch-all for UI type buffers | ||
call add(s:bufignore.freaks, {}) | ||
function! s:bufignore.freaks[-1].detect(buffer) abort | ||
return buflisted(a:buffer) is 0 | ||
endfunction | ||
|
||
" - redirected buffers | ||
call add(s:ignore, {'bufvars': ['netrw_lastfile']}) | ||
function! s:ignore[-1].detect(buffer) abort | ||
for l:var in self.bufvars | ||
if !empty(getbufvar(a:buffer, l:var)) | ||
return 1 | ||
endif | ||
endfor | ||
return 0 | ||
" - any 'buftype' but empty and "nowrite" as explicitly marked "not a file" | ||
call add(s:bufignore.freaks, {'buftypes': ['', 'nowrite']}) | ||
function! s:bufignore.freaks[-1].detect(buffer) abort | ||
return index(self.buftypes, getbufvar(a:buffer, '&buftype')) is -1 | ||
endfunction | ||
|
||
" - out-of-filesystem Netrw file buffers | ||
call add(s:bufignore.freaks, {}) | ||
function! s:bufignore.freaks[-1].detect(buffer) abort | ||
return !empty(getbufvar(a:buffer, 'netrw_lastfile')) | ||
endfunction " }}} | ||
|
||
" Get a copy of vim-fetch's spec matchers: | ||
" @signature: fetch#specs() | ||
" @returns: Dictionary<Dictionary> of specs, keyed by name, | ||
" each spec Dictionary with the following keys: | ||
" - 'pattern' String to match the spec in a file name | ||
" - 'parse' Funcref taking a spec'ed file name and | ||
" returning a two item List of | ||
" {unspec'ed path:String}, {pos:List<Number[,Number]>} | ||
" @notes: the autocommand match patterns are not included | ||
function! fetch#specs() abort | ||
" -'pattern' String to match the spec in a file name | ||
" -'parse' Funcref taking a spec'ed file name | ||
" and returning a List of | ||
" 0 unspec'ed path String | ||
" 1 position setting |call()| arguments List | ||
" @notes: the autocommand match patterns are not included | ||
function! fetch#specs() abort " {{{ | ||
return deepcopy(s:specs) | ||
endfunction | ||
endfunction " }}} | ||
|
||
" Resolve {spec} for the current buffer, substituting the resolved | ||
" file (if any) for it, with the cursor placed at the resolved position: | ||
" @signature: fetch#buffer({spec:String}) | ||
" @returns: Boolean | ||
function! fetch#buffer(spec) abort " {{{ | ||
let l:bufname = expand('%') | ||
let l:spec = s:specs[a:spec] | ||
|
||
" Edit {file}, placing the cursor at the line and column indicated by {spec}: | ||
" @signature: fetch#edit({file:String}, {spec:String}) | ||
" @returns: Boolean indicating if a spec has been succesfully resolved | ||
" @notes: - won't work from a |BufReadCmd| event as it doesn't load non-spec'ed files | ||
" - won't work from events fired before the spec'ed file is loaded into | ||
" the buffer (i.e. before '%' is set to the spec'ed file) like |BufNew| | ||
" as it won't be able to wipe the spurious new spec'ed buffer | ||
function! fetch#edit(file, spec) abort | ||
" naive early exit on obvious non-matches | ||
if filereadable(a:file) || match(a:file, s:specs[a:spec].pattern) is -1 | ||
" exclude obvious non-matches | ||
if matchend(l:bufname, l:spec.pattern) isnot len(l:bufname) | ||
return 0 | ||
endif | ||
|
||
" check for unspec'ed editable file | ||
let [l:file, l:pos] = s:specs[a:spec].parse(a:file) | ||
if !filereadable(l:file) | ||
return 0 " in doubt, end with invalid user input | ||
" only substitute if we have a valid resolved file | ||
" and a spurious unresolved buffer both | ||
let [l:file, l:jump] = l:spec.parse(l:bufname) | ||
if !filereadable(l:file) || s:bufignore.detect(bufnr('%')) is 1 | ||
return 0 | ||
endif | ||
|
||
" processing setup | ||
let l:pre = '' " will be prefixed to edit command | ||
" we have a spurious unresolved buffer: set up for wiping | ||
set buftype=nowrite " avoid issues voiding the buffer | ||
set bufhidden=wipe " avoid issues with |bwipeout| | ||
|
||
" if current buffer is spec'ed and invalid set it up for wiping | ||
if expand('%:p') is fnamemodify(a:file, ':p') | ||
for l:ignore in s:ignore | ||
if l:ignore.detect(bufnr('%')) is 1 | ||
return 0 | ||
endif | ||
endfor | ||
set buftype=nowrite " avoid issues voiding the buffer | ||
set bufhidden=wipe " avoid issues with |bwipeout| | ||
let l:pre .= 'keepalt ' " don't mess up alternate file on switch | ||
endif | ||
|
||
" clean up argument list | ||
" substitute resolved file for unresolved buffer on arglist | ||
if has('listcmds') | ||
let l:argidx = index(argv(), a:file) | ||
if l:argidx isnot -1 " substitute un-spec'ed file for spec'ed | ||
execute 'argdelete' fnameescape(a:file) | ||
let l:argidx = index(argv(), l:bufname) | ||
if l:argidx isnot -1 | ||
execute 'argdelete' fnameescape(l:bufname) | ||
execute l:argidx.'argadd' fnameescape(l:file) | ||
endif | ||
endif | ||
|
||
" edit on argument list if required | ||
" set arglist index to resolved file if required | ||
" (needs to happen independently of arglist switching to work | ||
" with the double processing of the first -o/-O/-p window) | ||
if index(argv(), l:file) isnot -1 | ||
let l:pre .= 'arg' " set arglist index to edited file | ||
let l:cmd = 'argedit' | ||
endif | ||
|
||
" open correct file and place cursor at position spec | ||
execute l:pre.'edit' fnameescape(l:file) | ||
return fetch#setpos(l:pos) | ||
endfunction | ||
" edit resolved file and place cursor at position spec | ||
execute 'keepalt' get(l:, 'cmd', 'edit').v:cmdarg fnameescape(l:file) | ||
if !empty(v:swapcommand) | ||
execute 'normal' v:swapcommand | ||
endif | ||
return s:setpos(l:jump) | ||
endfunction " }}} | ||
|
||
" Edit |<cfile>|, resolving a possible trailing spec: | ||
" @signature: fetch#cfile({count:Number}) | ||
" @returns: Boolean | ||
" @notes: - will test all available specs for a match | ||
" - will fall back on Vim's |gF| when no spec matches | ||
function! fetch#cfile(count) abort " {{{ | ||
let l:cfile = expand('<cfile>') | ||
|
||
if !empty(l:cfile) | ||
" locate '<cfile>' in current line | ||
let l:pattern = '\M'.escape(l:cfile, '\') | ||
let l:position = searchpos(l:pattern, 'bcn', line('.')) | ||
if l:position == [0, 0] | ||
let l:position = searchpos(l:pattern, 'cn', line('.')) | ||
endif | ||
|
||
" test for a trailing spec, accounting for multi-line '<cfile>' matches | ||
let l:lines = split(l:cfile, "\n") | ||
let l:line = getline(l:position[0] + len(l:lines) - 1) | ||
let l:offset = (len(l:lines) > 1 ? 0 : l:position[1]) + len(l:lines[-1]) - 1 | ||
for l:spec in values(s:specs) | ||
if match(l:line, l:spec.pattern, l:offset) is l:offset | ||
let l:match = matchstr(l:line, l:spec.pattern, l:offset) | ||
" leverage Vim's own |gf| for opening the file | ||
execute 'normal!' a:count.'gf' | ||
return s:setpos(l:spec.parse(l:cfile.l:match)[1]) | ||
endif | ||
endfor | ||
endif | ||
|
||
" fall back to Vim's |gF| | ||
execute 'normal!' a:count.'gF' | ||
return 1 | ||
endfunction " }}} | ||
|
||
" Place the current buffer's cursor at {pos}: | ||
" @signature: fetch#setpos({pos:List<Number[,Number]>}) | ||
" Edit the visually selected file, resolving a possible trailing spec: | ||
" @signature: fetch#visual({count:Number}) | ||
" @returns: Boolean | ||
" @notes: triggers the |User| events | ||
" - BufFetchPosPre before setting the position | ||
" - BufFetchPosPost after setting the position | ||
function! fetch#setpos(pos) abort | ||
silent doautocmd <nomodeline> User BufFetchPosPre | ||
let b:fetch_lastpos = [max([a:pos[0], 1]), max([get(a:pos, 1, 0), 1])] | ||
call cursor(b:fetch_lastpos[0], b:fetch_lastpos[1]) | ||
" @notes: - will test all available specs for a match | ||
" - will fall back on Vim's |gF| when no spec matches | ||
function! fetch#visual(count) abort " {{{ | ||
" get text between last visual selection marks | ||
" adapted from http://stackoverflow.com/a/6271254/990363 | ||
let [l:startline, l:startcol] = getpos("'<")[1:2] | ||
let [l:endline, l:endcol] = getpos("'>")[1:2] | ||
let l:endcol -= &selection is 'inclusive' ? 0 : 1 | ||
let lines = getline(l:startline, l:endline) | ||
let lines[-1] = matchstr(lines[-1], '\m^.\{'.string(l:endcol).'}') | ||
let lines[0] = matchstr(lines[0], '\m^.\{'.string(l:startcol - 1).'}\zs.*') | ||
let l:selection = join(lines, "\n") | ||
|
||
" test for a trailing spec | ||
if !empty(l:selection) | ||
let l:line = getline(l:endline) | ||
for l:spec in values(s:specs) | ||
if match(l:line, l:spec.pattern, l:endcol) is l:endcol | ||
let l:match = matchstr(l:line, l:spec.pattern, l:endcol) | ||
call s:dovisual(a:count.'gf') " leverage Vim's |gf| to get the file | ||
return s:setpos(l:spec.parse(l:selection.l:match)[1]) | ||
endif | ||
endfor | ||
endif | ||
|
||
" fall back to Vim's |gF| | ||
call s:dovisual(a:count.'gF') | ||
return 1 | ||
endfunction " }}} | ||
|
||
" Private helper functions: {{{ | ||
" - place the current buffer's cursor, triggering the "BufFetchPosX" events | ||
" see :h call() for the format of the {calldata} List | ||
function! s:setpos(calldata) abort | ||
call s:doautocmd('BufFetchPosPre') | ||
keepjumps call call('call', a:calldata) | ||
let b:fetch_lastpos = getpos('.')[1:2] | ||
silent! normal! zOzz | ||
silent doautocmd <nomodeline> User BufFetchPosPost | ||
return getpos('.')[1:2] == b:fetch_lastpos | ||
call s:doautocmd('BufFetchPosPost') | ||
return 1 | ||
endfunction | ||
|
||
" - apply User autocommands matching {pattern}, but only if there are any | ||
" 1. avoids flooding message history with "No matching autocommands" | ||
" 2. avoids re-applying modelines in Vim < 7.3.442, which doesn't honor |<nomodeline>| | ||
" see https://groups.google.com/forum/#!topic/vim_dev/DidKMDAsppw | ||
function! s:doautocmd(pattern) abort | ||
if exists('#User#'.a:pattern) | ||
execute 'doautocmd <nomodeline> User' a:pattern | ||
endif | ||
endfunction | ||
|
||
" - send command to the last visual selection | ||
function! s:dovisual(command) abort | ||
let l:cmd = index(['v', 'V', ''], mode()) is -1 ? 'gv'.a:command : a:command | ||
execute 'normal!' l:cmd | ||
endfunction | ||
" }}} | ||
|
||
let &cpo = s:cpo | ||
unlet! s:cpo | ||
let &cpoptions = s:cpoptions | ||
unlet! s:cpoptions | ||
|
||
" vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
" VIM-STAY INTEGRATION MODULE | ||
" https://github.com/kopischke/vim-stay | ||
let s:cpoptions = &cpoptions | ||
set cpoptions&vim | ||
|
||
" - register integration autocommands | ||
function! stay#integrate#fetch#setup() abort | ||
autocmd User BufFetchPosPost let b:stay_atpos = b:fetch_lastpos | ||
endfunction | ||
|
||
let &cpoptions = s:cpoptions | ||
unlet! s:cpoptions | ||
|
||
" vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: |
Oops, something went wrong.