Skip to content
Open
Show file tree
Hide file tree
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
339 changes: 339 additions & 0 deletions autoload/ingo/actions/iterations.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
" ingo/actions/iterations.vim: Repeated action execution over several targets.
"
" DEPENDENCIES:
" - ingo/actions.vim autoload script
" - ingo/escape/file.vim autoload script
"
" Copyright: (C) 2016 Ingo Karkat
" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
"
" REVISION DATE REMARKS
" 1.025.001 29-Jul-2016 file creation

function! ingo#actions#iterations#WinDo( alreadyVisitedBuffers, ... )
"******************************************************************************
"* PURPOSE:
" Invoke a:Action on each window in the current tab page, unless the buffer is
" in a:alreadyVisitedBuffers.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:alreadyVisitedBuffers Dictionary with already visited buffer numbers
" as keys. Will be added to, and the same buffers
" in other windows will be skipped. Pass 0 to
" visit _all_ windows, regardless of the buffers
" they display.
" a:Action Either a Funcref or Ex commands to be executed
" in each window.
" ... Arguments passed to an a:Action Funcref.
"* RETURN VALUES:
" None.
"******************************************************************************
let l:originalWinNr = winnr()
let l:previousWinNr = winnr('#') ? winnr('#') : 1

" By entering a window, its height is potentially increased from 0 to 1 (the
" minimum for the current window). To avoid any modification, save the window
" sizes and restore them after visiting all windows.
let l:originalWindowLayout = winrestcmd()
let l:didSwitchWindows = 0

try
for l:winNr in range(1, winnr('$'))
let l:bufNr = winbufnr(l:winNr)
if a:alreadyVisitedBuffers is# 0 || ! has_key(a:alreadyVisitedBuffers, l:bufNr)
if l:winNr != winnr()
execute 'noautocmd' l:winNr . 'wincmd w'
let l:didSwitchWindows = 1
endif
if type(a:alreadyVisitedBuffers) == type({}) | let a:alreadyVisitedBuffers[bufnr('')] = 1 | endif

call call(function('ingo#actions#ExecuteOrFunc'), a:000)
endif
endfor
finally
if l:didSwitchWindows
noautocmd execute l:previousWinNr . 'wincmd w'
noautocmd execute l:originalWinNr . 'wincmd w'
silent! execute l:originalWindowLayout
endif
endtry
endfunction

function! ingo#actions#iterations#TabWinDo( alreadyVisitedTabPages, alreadyVisitedBuffers, ... )
"******************************************************************************
"* PURPOSE:
" Invoke a:Action on each window in each tab page, unless the buffer is in
" a:alreadyVisitedBuffers.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:alreadyVisitedTabPages Dictionary with already visited tabpage numbers
" as keys. Will be added to, those tab pages will
" be skipped. Pass empty Dictionary to visit _all_
" tab pages.
" a:alreadyVisitedBuffers Dictionary with already visited buffer numbers
" as keys. Will be added to, and the same buffers
" in other windows / tab pages will be skipped.
" Pass 0 to visit _all_ windows and tab pages,
" regardless of the buffers they display.
" a:Action Either a Funcref or Ex commands to be executed
" in each window.
" ... Arguments passed to an a:Action Funcref.
"* RETURN VALUES:
" None.
"******************************************************************************
let l:originalTabNr = tabpagenr()
let l:didSwitchTabs = 0
try
for l:tabNr in range(1, tabpagenr('$'))
if ! has_key(a:alreadyVisitedTabPages, l:tabNr)
let a:alreadyVisitedTabPages[l:tabNr] = 1
if ! empty(a:alreadyVisitedBuffers) && ingo#collections#differences#ContainsLoosely(keys(a:alreadyVisitedBuffers), tabpagebuflist(l:tabNr))
" All buffers of that tab page have already been visited; no
" need to go there.
continue
endif

if l:tabNr != tabpagenr()
execute 'noautocmd' l:tabNr . 'tabnext'
let l:didSwitchTabs = 1
endif

let l:originalWinNr = winnr()
let l:previousWinNr = winnr('#') ? winnr('#') : 1
" By entering a window, its height is potentially increased from 0 to 1 (the
" minimum for the current window). To avoid any modification, save the window
" sizes and restore them after visiting all windows.
let l:originalWindowLayout = winrestcmd()
let l:didSwitchWindows = 0

try
for l:winNr in range(1, winnr('$'))
let l:bufNr = winbufnr(l:winNr)
if a:alreadyVisitedBuffers is# 0 || ! has_key(a:alreadyVisitedBuffers, l:bufNr)
execute 'noautocmd' l:winNr . 'wincmd w'

let l:didSwitchWindows = 1
if type(a:alreadyVisitedBuffers) == type({}) | let a:alreadyVisitedBuffers[bufnr('')] = 1 | endif

call call(function('ingo#actions#ExecuteOrFunc'), a:000)
endif
endfor
finally
if l:didSwitchWindows
noautocmd execute l:previousWinNr . 'wincmd w'
noautocmd execute l:originalWinNr . 'wincmd w'
silent! execute l:originalWindowLayout
endif
endtry
endif
endfor
finally
if l:didSwitchTabs
noautocmd execute l:originalTabNr . 'tabnext'
endif
endtry
endfunction

function! s:GetNextArgNr( argNr, alreadyVisitedBuffers )
let l:argNr = a:argNr + 1 " Try next argument.
while l:argNr <= argc()
let l:bufNr = bufnr(ingo#escape#file#bufnameescape(argv(a:argNr - 1)))
if l:bufNr == -1 || type(a:alreadyVisitedBuffers) != type({}) || ! has_key(a:alreadyVisitedBuffers, l:bufNr)
return l:argNr
endif

" That one was already visited; continue searching.
let l:argNr += 1
endwhile
return -1
endfunction
function! ingo#actions#iterations#ArgDo( alreadyVisitedBuffers, ... )
"******************************************************************************
"* PURPOSE:
" Invoke a:Action on each argument in the argument list, unless the buffer is
" in a:alreadyVisitedBuffers.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" Prints any Vim exception as error message.
"* INPUTS:
" a:alreadyVisitedBuffers Dictionary with already visited buffer numbers
" as keys. Will be added to, and the same buffers
" in other arguments will be skipped. Pass 0 to
" visit _all_ arguments.
" a:Action Either a Funcref or Ex commands to be executed
" in each window.
" ... Arguments passed to an a:Action Funcref.
"* RETURN VALUES:
" Number of Vim exceptions raised while iterating through the argument list
" (e.g. errors when loading buffers) or from executing a:Action.
"******************************************************************************
let l:originalBufNr = bufnr('')
let l:originalWindowLayout = winrestcmd()
let l:originalWinNr = winnr()
let l:previousWinNr = winnr('#') ? winnr('#') : 1

let l:nextArgNr = s:GetNextArgNr(0, a:alreadyVisitedBuffers)
if l:nextArgNr == -1
return | " No arguments left.
endif

let l:didSplit = 0
let l:failureCnt = 0
try
try
execute 'noautocmd silent keepalt leftabove' l:nextArgNr . 'sargument'
let l:didSplit = 1
catch
call ingo#msg#VimExceptionMsg()
let l:failureCnt += 1
if bufnr('') == l:originalBufNr
" We failed to split to the target buffer; bail out, as we need
" the split.
return l:failureCnt
endif
endtry

while 1
let l:bufNr = bufnr('')
if type(a:alreadyVisitedBuffers) == type({}) | let a:alreadyVisitedBuffers[bufnr('')] = 1 | endif

try
call call(function('ingo#actions#ExecuteOrFunc'), a:000)
catch
call ingo#msg#VimExceptionMsg()
let l:failureCnt += 1
endtry

let l:nextArgNr = s:GetNextArgNr(l:nextArgNr, a:alreadyVisitedBuffers)
if l:nextArgNr == -1
break
endif

try
execute 'noautocmd silent keepalt' l:nextArgNr . 'argument'
catch
call ingo#msg#VimExceptionMsg()
let l:failureCnt += 1
endtry
endwhile
finally
if l:didSplit
noautocmd silent! close!
noautocmd execute l:previousWinNr . 'wincmd w'
noautocmd execute l:originalWinNr . 'wincmd w'
silent! execute l:originalWindowLayout
endif
endtry

return l:failureCnt
endfunction

function! s:GetNextBufNr( bufNr, alreadyVisitedBuffers )
let l:bufNr = a:bufNr + 1 " Try next buffer.
let l:lastBufNr = bufnr('$')
while l:bufNr <= l:lastBufNr
if buflisted(l:bufNr) && (type(a:alreadyVisitedBuffers) != type({}) || ! has_key(a:alreadyVisitedBuffers, l:bufNr))
return l:bufNr
endif

" That one was already visited; continue searching.
let l:bufNr += 1
endwhile
return -1
endfunction
function! ingo#actions#iterations#BufDo( alreadyVisitedBuffers, ... )
"******************************************************************************
"* PURPOSE:
" Invoke a:Action on each listed buffer, unless the buffer is in
" a:alreadyVisitedBuffers.
"* SEE ALSO:
" To execute an Action in a single visible buffer, use
" ingo#buffer#visible#Execute() / ingo#buffer#visible#Call().
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" Prints any Vim exception as error message.
"* INPUTS:
" a:alreadyVisitedBuffers Dictionary with already visited buffer numbers
" as keys. Will be added to. Pass 0 or {} to visit
" _all_ buffers.
" a:Action Either a Funcref or Ex commands to be executed
" in each buffer.
" ... Arguments passed to an a:Action Funcref.
"* RETURN VALUES:
" Number of Vim exceptions raised while iterating through the buffer list
" (e.g. errors when loading buffers) or from executing a:Action.
"******************************************************************************
let l:originalWindowLayout = winrestcmd()
let l:originalWinNr = winnr()
let l:previousWinNr = winnr('#') ? winnr('#') : 1

let l:nextBufNr = s:GetNextBufNr(0, a:alreadyVisitedBuffers)
if l:nextBufNr == -1
return | " No buffers left.
endif

let l:didSplit = 0
let l:failureCnt = 0
let l:save_switchbuf = &switchbuf | set switchbuf= | " :sbuffer should always open a new split (so we can :close it without checking).
try
try
execute 'noautocmd silent keepalt leftabove' l:nextBufNr . 'sbuffer'
catch
call ingo#msg#VimExceptionMsg()
let l:failureCnt += 1
if bufnr('') != l:nextBufNr
" We failed to split to the target buffer; bail out, as we need
" the split.
return l:failureCnt
endif
finally
let &switchbuf = l:save_switchbuf
endtry

let l:didSplit = 1
while 1
let l:bufNr = bufnr('')
if type(a:alreadyVisitedBuffers) == type({}) | let a:alreadyVisitedBuffers[bufnr('')] = 1 | endif

try
call call(function('ingo#actions#ExecuteOrFunc'), a:000)
catch
call ingo#msg#VimExceptionMsg()
let l:failureCnt += 1
endtry

let l:nextBufNr = s:GetNextBufNr(l:nextBufNr, a:alreadyVisitedBuffers)
if l:nextBufNr == -1
break
endif

try
execute 'noautocmd silent keepalt' l:nextBufNr . 'buffer'
catch
call ingo#msg#VimExceptionMsg()
let l:failureCnt += 1
endtry
endwhile
finally
if l:didSplit
noautocmd silent! close!
noautocmd execute l:previousWinNr . 'wincmd w'
noautocmd execute l:originalWinNr . 'wincmd w'
silent! execute l:originalWindowLayout
endif
endtry

return l:failureCnt
endfunction

" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
52 changes: 52 additions & 0 deletions autoload/ingo/actions/special.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
" ingo/actions/special.vim: Action execution within special environments.
"
" DEPENDENCIES:
" - ingo/actions.vim autoload script
"
" Copyright: (C) 2016 Ingo Karkat
" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
"
" REVISION DATE REMARKS
" 1.025.001 29-Jul-2016 file creation

function! ingo#actions#special#NoAutoChdir( ... )
"******************************************************************************
"* PURPOSE:
" Execute a:Action with :set noautochdir.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:Action Either a Funcref or Ex commands to be executed.
" ... Arguments passed to an a:Action Funcref.
"* RETURN VALUES:
" Result of Funcref, or empty string in case of Ex commands.
"******************************************************************************
" Unfortunately, restoring the 'autochdir' option clobbers any temporary CWD
" override. So we may have to restore the CWD, too.
let l:save_cwd = getcwd()
let l:chdirCommand = (exists('*haslocaldir') && haslocaldir() ? 'lchdir!' : 'chdir!')

" The 'autochdir' option adapts the CWD, so any (relative) filepath to the
" filename in the other window would be omitted. Temporarily turn this off;
" may be a little bit faster, too.
if exists('+autochdir')
let l:save_autochdir = &autochdir
set noautochdir
endif
try
return call(function('ingo#actions#ExecuteOrFunc'), a:000)
finally
if exists('l:save_autochdir')
let &autochdir = l:save_autochdir
endif
if getcwd() !=# l:save_cwd
execute l:chdirCommand ingo#compat#fnameescape(l:save_cwd)
endif
endtry
endfunction

" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
Loading