| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- " visualrepeat.vim: Repeat command extended to visual mode.
- "
- " DEPENDENCIES:
- " - ingo/selection.vim autoload script (optional; for blockwise repeat only)
- "
- " Copyright: (C) 2011-2013 Ingo Karkat
- " The VIM LICENSE applies to this script; see ':help copyright'.
- "
- " Maintainer: Ingo Karkat <ingo@karkat.de>
- "
- " REVISION DATE REMARKS
- " 1.30.014 14-Nov-2013 ENH: When repeating over multiple lines / a
- " blockwise selection, keep track of added or
- " deleted lines, and only repeat exactly on the
- " selected lines. Thanks to Israel Chauca for
- " sending a patch!
- " When a repeat on a blockwise selection has
- " introduced additional lines, append those
- " properly indented instead of omitting them.
- " With linewise and blockwise repeat, set the
- " change marks '[,'] to the changed selection.
- " With the latter, one previously got "E19:
- " Mark has invalid line number" due to the removed
- " temporary range.
- " 1.20.013 05-Sep-2013 ENH: Implement blockwise repeat through
- " temporarily moving the block to a temporary
- " range at the end of the buffer, like the vis.vim
- " plugin.
- " 1.10.012 04-Sep-2013 ENH: Use the current cursor virtual column when
- " repeating in linewise visual mode. Add
- " visualrepeat#CaptureVirtCol() and
- " visualrepeat#repeatOnVirtCol() for that.
- " Minor: Also catch Vim echoerr exceptions and
- " anything else.
- " Move the error handling to the mapping itself
- " and do it with echoerr so that further commands
- " are properly aborted. Implement
- " visualrepeat#ErrorMsg() to avoid a dependency to
- " ingo#err#Get().
- " 1.10.011 14-Jun-2013 Minor: Make substitute() robust against
- " 'ignorecase'.
- " 1.10.010 18-Apr-2013 Check for existence of actual visual mode
- " mapping; do not accept a select mode mapping,
- " because we're applying it to a visual selection.
- " Pass through a [count] to the :normal . command.
- " 1.03.009 21-Feb-2013 REGRESSION: Fix in 1.02 does not repeat recorded
- " register when the mappings in repeat.vim and
- " visualrepeat.vim differ. We actually need to
- " always check g:repeat_sequence, since that is
- " also installed in g:repeat_reg[0]. Caught by
- " tests/ReplaceWithRegister/repeatLineAsVisual001.vim;
- " if only I had executed the tests sooner :-(
- " Fix by checking for the variable's existence
- " instead of using l:repeat_sequence.
- " 1.02.008 27-Dec-2012 BUG: "E121: Undefined variable:
- " g:repeat_sequence" when using visual repeat
- " of a mapping using registers without having used
- " repeat.vim beforehand.
- " 1.01.007 05-Apr-2012 FIX: Avoid error about undefined g:repeat_reg
- " when (a proper version of) repeat.vim isn't
- " available.
- " 1.00.006 12-Dec-2011 Catch any errors from the :normal . repetitions
- " instead of causing function errors. Also use
- " exceptions for the internal error signaling.
- " 005 06-Dec-2011 Retire visualrepeat#set_also(); it's the same as
- " visualrepeat#set() since we've dropped the
- " forced increment of b:changedtick.
- " 004 22-Oct-2011 BUG: Must initialize g:visualrepeat_tick on load
- " to avoid "Undefined variable" error in autocmds
- " on BufWrite. It can happen that this autoload
- " script is loaded without having a repetition
- " registered at the same time.
- " 003 21-Oct-2011 Also apply the same-register repeat enhancement
- " to repeat.vim here.
- " 002 17-Oct-2011 Increment b:changedtick without clobbering the
- " expression register.
- " Must also adapt g:visualrepeat_tick on buffer
- " save to allow repetition after a save and buffer
- " switch (without relying on g:repeat_sequence
- " being identical to g:visualrepeat_sequence,
- " which has formerly often saved us).
- " Omit own increment of b:changedtick, let the
- " mapping do that (or not, in case of a
- " non-modifying mapping). It seems to work without
- " it, and avoids setting the 'modified' flag on
- " unmodified buffers, which is not expected.
- " 001 17-Mar-2011 file creation
- let s:save_cpo = &cpo
- set cpo&vim
- let g:visualrepeat_tick = -1
- function! visualrepeat#set( sequence, ... )
- let g:visualrepeat_sequence = a:sequence
- let g:visualrepeat_count = a:0 ? a:1 : v:count
- let g:visualrepeat_tick = b:changedtick
- endfunction
- let s:virtcol = 1
- function! visualrepeat#CaptureVirtCol()
- let s:virtcol = virtcol('.')
- return ''
- endfunction
- function! visualrepeat#repeatOnVirtCol( virtcol, count )
- execute 'normal!' a:virtcol . '|'
- if virtcol('.') >= a:virtcol
- execute 'normal' a:count . '.'
- endif
- endfunction
- function! s:RepeatOnRange( range, command )
- " The use of :global keeps track of lines added or deleted by the repeat, so
- " that we apply exactly to the selected lines.
- execute a:range . "global/^/" . a:command
- call histdel('search', -1)
- endfunction
- function! visualrepeat#repeat()
- if g:visualrepeat_tick == b:changedtick
- " visualrepeat.vim should handle the repeat.
- let l:repeat_sequence = g:visualrepeat_sequence
- let l:repeat_count = g:visualrepeat_count
- elseif exists('g:repeat_tick') && g:repeat_tick == b:changedtick
- " repeat.vim is enabled and would handle a normal-mode repeat.
- let l:repeat_sequence = g:repeat_sequence
- let l:repeat_count = g:repeat_count
- endif
- if exists('l:repeat_sequence')
- " A mapping for visualrepeat.vim or repeat.vim to repeat has been set.
- " Ensure that a corresponding visual mode mapping exists; some plugins
- " that only use repeat.vim may not have this.
- if ! empty(maparg(substitute(l:repeat_sequence, '^.\{3}', '<Plug>', 'g'), 'x'))
- " Handle mappings that use a register and want the same register
- " used on repetition.
- let l:reg = ''
- if exists('g:repeat_reg') && exists('g:repeat_sequence') &&
- \ g:repeat_reg[0] ==# g:repeat_sequence && ! empty(g:repeat_reg[1])
- if g:repeat_reg[1] ==# '='
- " This causes a re-evaluation of the expression on repeat, which
- " is what we want.
- let l:reg = '"=' . getreg('=', 1) . "\<CR>"
- else
- let l:reg = '"' . g:repeat_reg[1]
- endif
- endif
- " The normal mode mapping to be repeated has a corresponding visual
- " mode mapping. Use this so that the repetition will affect the
- " current selection. With this we also avoid the clumsy application
- " of the normal mode command to the visual selection, and can
- " support blockwise visual mode.
- let l:cnt = l:repeat_count == -1 ? '' : (v:count ? v:count : (l:repeat_count ? l:repeat_count : ''))
- call feedkeys('gv' . l:reg . l:cnt, 'n')
- call feedkeys(l:repeat_sequence)
- return 1
- endif
- endif
- try
- " Note: :normal has no bang to allow a remapped '.' command here to
- " enable repeat.vim functionality.
- if visualmode() ==# 'v'
- " Repeat the last change starting from the current cursor position.
- execute 'normal' (v:count ? v:count : '') . '.'
- elseif visualmode() ==# 'V'
- let [l:changeStart, l:changeEnd] = [getpos("'<"), getpos("'>")]
- " For all selected lines, repeat the last change in the line.
- if s:virtcol == 1
- " The cursor is set to the first column.
- call s:RepeatOnRange("'<,'>", 'normal ' . (v:count ? v:count : '') . '.')
- else
- " The cursor is set to the cursor column; the last change is
- " only applied to lines that have at least that many characters.
- call s:RepeatOnRange("'<,'>", printf('call visualrepeat#repeatOnVirtCol(%d, %s)',
- \ s:virtcol,
- \ string(v:count ? v:count : '')
- \))
- endif
- call setpos("'[", l:changeStart)
- call setpos("']", l:changeEnd)
- else
- " Yank the selected block and repeat the last change in scratch
- " lines at the end of the buffer (using a different buffer would be
- " easier, but the repeated command may depend on the current
- " buffer's settings), so that the change is limited to the
- " selection. The vis.vim plugin does the same, but we cannot use it,
- " because it performs the movement (to the bottom of the current
- " buffer) via regular paste commands (which clobber the repeat
- " command). We need to be careful to avoid doing that, using only
- " lower level functions.
- let l:changeStart = getpos("'<")
- let l:startVirtCol = virtcol("'<")
- let [l:count, l:startColPattern, l:startLnum, l:endLnum, l:finalLnum] = [v:count, ('\%>' . (l:startVirtCol - 1) . 'v'), line("'<"), line("'>"), line('$')]
- let l:selection = split(ingo#selection#Get(), '\n', 1)
- " Save the view after the yank so that the cursor resides at the
- " beginning of the selected block, just as we would expect after the
- " repeat. (The :normal / :delete of the temporary range later
- " modifies the cursor position.)
- let l:save_view = winsaveview()
- let l:tempRange = (l:finalLnum + 1) . ',$'
- call append(l:finalLnum, l:selection)
- " The cursor is set to the first column.
- call s:RepeatOnRange(l:tempRange, 'normal ' . (l:count ? l:count : '') . '.')
- let l:result = getline(l:finalLnum + 1, '$')
- try
- " Using :undo to roll back the append and repeat is safer,
- " because any potential modification outside the temporary range
- " is also eliminated. Only explicitly delete the temporary range
- " as a fallback.
- silent undo
- catch /^Vim\%((\a\+)\)\=:E/
- silent! execute l:tempRange . 'delete _'
- endtry
- for l:lnum in range(l:startLnum, l:endLnum)
- let l:idx = l:lnum - l:startLnum
- let l:line = getline(l:lnum)
- let l:startCol = match(l:line, l:startColPattern)
- let l:endCol = l:startCol + len(l:selection[l:idx])
- let l:resultLine = get(l:result, l:idx, '') " Replace the line part with an empty string if there are less lines after the repeat.
- let l:newLine = strpart(l:line, 0, l:startCol) . l:resultLine . strpart(l:line, l:endCol)
- call setline(l:lnum, l:newLine)
- endfor
- let l:addedNum = len(l:result) - l:idx - 1
- if l:addedNum == 0
- let l:changeEnd = [0, l:lnum, l:startCol + len(l:resultLine), 0]
- else
- " The repeat has introduced additional lines; append those (as
- " new lines) properly indented to the start of the blockwise
- " selection.
- let l:indent = repeat(' ', l:startVirtCol - 1)
- " To use the buffer's indent settings, first insert spaces and
- " have :retab convert those to the proper indent. Then, append
- " the additional lines.
- call append(l:lnum, repeat([l:indent], l:addedNum))
- silent execute printf('%d,%dretab!', l:lnum + 1, l:lnum + l:addedNum + 1)
- for l:addedIdx in range(l:addedNum)
- let l:addedLnum = l:lnum + 1 + l:addedIdx
- call setline(l:addedLnum, getline(l:addedLnum) . l:result[l:idx + 1 + l:addedIdx])
- endfor
- let l:changeEnd = [0, l:addedLnum, len(getline(l:addedLnum)) + 1, 0]
- endif
- " The change marks still point to the (removed) temporary range.
- " Make them valid by setting them to the changed selection.
- call setpos("'[", l:changeStart)
- call setpos("']", l:changeEnd)
- call winrestview(l:save_view)
- endif
- return 1
- catch /^Vim\%((\a\+)\)\=:E117:.*ingo#selection#Get/ " E117: Unknown function: ingo#selection#Get
- let s:errorMsg = 'For blockwise repeat, you need to install the ingo-library dependency'
- catch /^Vim\%((\a\+)\)\=:/
- " v:exception contains what is normally in v:errmsg, but with extra
- " exception source info prepended, which we cut away.
- let s:errorMsg = substitute(v:exception, '^\CVim\%((\a\+)\)\=:', '', '')
- catch
- let s:errorMsg = v:exception
- endtry
- return 0
- endfunction
- let s:errorMsg = ''
- function! visualrepeat#ErrorMsg()
- return s:errorMsg
- endfunction
- augroup visualrepeatPlugin
- autocmd!
- autocmd BufLeave,BufWritePre,BufReadPre * let g:visualrepeat_tick = (g:visualrepeat_tick == b:changedtick || g:visualrepeat_tick == 0) ? 0 : -1
- autocmd BufEnter,BufWritePost * if g:visualrepeat_tick == 0|let g:visualrepeat_tick = b:changedtick|endif
- augroup END
- let &cpo = s:save_cpo
- unlet s:save_cpo
- " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
|