visualrepeat.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. " visualrepeat.vim: Repeat command extended to visual mode.
  2. "
  3. " DEPENDENCIES:
  4. " - ingo/selection.vim autoload script (optional; for blockwise repeat only)
  5. "
  6. " Copyright: (C) 2011-2013 Ingo Karkat
  7. " The VIM LICENSE applies to this script; see ':help copyright'.
  8. "
  9. " Maintainer: Ingo Karkat <ingo@karkat.de>
  10. "
  11. " REVISION DATE REMARKS
  12. " 1.30.014 14-Nov-2013 ENH: When repeating over multiple lines / a
  13. " blockwise selection, keep track of added or
  14. " deleted lines, and only repeat exactly on the
  15. " selected lines. Thanks to Israel Chauca for
  16. " sending a patch!
  17. " When a repeat on a blockwise selection has
  18. " introduced additional lines, append those
  19. " properly indented instead of omitting them.
  20. " With linewise and blockwise repeat, set the
  21. " change marks '[,'] to the changed selection.
  22. " With the latter, one previously got "E19:
  23. " Mark has invalid line number" due to the removed
  24. " temporary range.
  25. " 1.20.013 05-Sep-2013 ENH: Implement blockwise repeat through
  26. " temporarily moving the block to a temporary
  27. " range at the end of the buffer, like the vis.vim
  28. " plugin.
  29. " 1.10.012 04-Sep-2013 ENH: Use the current cursor virtual column when
  30. " repeating in linewise visual mode. Add
  31. " visualrepeat#CaptureVirtCol() and
  32. " visualrepeat#repeatOnVirtCol() for that.
  33. " Minor: Also catch Vim echoerr exceptions and
  34. " anything else.
  35. " Move the error handling to the mapping itself
  36. " and do it with echoerr so that further commands
  37. " are properly aborted. Implement
  38. " visualrepeat#ErrorMsg() to avoid a dependency to
  39. " ingo#err#Get().
  40. " 1.10.011 14-Jun-2013 Minor: Make substitute() robust against
  41. " 'ignorecase'.
  42. " 1.10.010 18-Apr-2013 Check for existence of actual visual mode
  43. " mapping; do not accept a select mode mapping,
  44. " because we're applying it to a visual selection.
  45. " Pass through a [count] to the :normal . command.
  46. " 1.03.009 21-Feb-2013 REGRESSION: Fix in 1.02 does not repeat recorded
  47. " register when the mappings in repeat.vim and
  48. " visualrepeat.vim differ. We actually need to
  49. " always check g:repeat_sequence, since that is
  50. " also installed in g:repeat_reg[0]. Caught by
  51. " tests/ReplaceWithRegister/repeatLineAsVisual001.vim;
  52. " if only I had executed the tests sooner :-(
  53. " Fix by checking for the variable's existence
  54. " instead of using l:repeat_sequence.
  55. " 1.02.008 27-Dec-2012 BUG: "E121: Undefined variable:
  56. " g:repeat_sequence" when using visual repeat
  57. " of a mapping using registers without having used
  58. " repeat.vim beforehand.
  59. " 1.01.007 05-Apr-2012 FIX: Avoid error about undefined g:repeat_reg
  60. " when (a proper version of) repeat.vim isn't
  61. " available.
  62. " 1.00.006 12-Dec-2011 Catch any errors from the :normal . repetitions
  63. " instead of causing function errors. Also use
  64. " exceptions for the internal error signaling.
  65. " 005 06-Dec-2011 Retire visualrepeat#set_also(); it's the same as
  66. " visualrepeat#set() since we've dropped the
  67. " forced increment of b:changedtick.
  68. " 004 22-Oct-2011 BUG: Must initialize g:visualrepeat_tick on load
  69. " to avoid "Undefined variable" error in autocmds
  70. " on BufWrite. It can happen that this autoload
  71. " script is loaded without having a repetition
  72. " registered at the same time.
  73. " 003 21-Oct-2011 Also apply the same-register repeat enhancement
  74. " to repeat.vim here.
  75. " 002 17-Oct-2011 Increment b:changedtick without clobbering the
  76. " expression register.
  77. " Must also adapt g:visualrepeat_tick on buffer
  78. " save to allow repetition after a save and buffer
  79. " switch (without relying on g:repeat_sequence
  80. " being identical to g:visualrepeat_sequence,
  81. " which has formerly often saved us).
  82. " Omit own increment of b:changedtick, let the
  83. " mapping do that (or not, in case of a
  84. " non-modifying mapping). It seems to work without
  85. " it, and avoids setting the 'modified' flag on
  86. " unmodified buffers, which is not expected.
  87. " 001 17-Mar-2011 file creation
  88. let s:save_cpo = &cpo
  89. set cpo&vim
  90. let g:visualrepeat_tick = -1
  91. function! visualrepeat#set( sequence, ... )
  92. let g:visualrepeat_sequence = a:sequence
  93. let g:visualrepeat_count = a:0 ? a:1 : v:count
  94. let g:visualrepeat_tick = b:changedtick
  95. endfunction
  96. let s:virtcol = 1
  97. function! visualrepeat#CaptureVirtCol()
  98. let s:virtcol = virtcol('.')
  99. return ''
  100. endfunction
  101. function! visualrepeat#repeatOnVirtCol( virtcol, count )
  102. execute 'normal!' a:virtcol . '|'
  103. if virtcol('.') >= a:virtcol
  104. execute 'normal' a:count . '.'
  105. endif
  106. endfunction
  107. function! s:RepeatOnRange( range, command )
  108. " The use of :global keeps track of lines added or deleted by the repeat, so
  109. " that we apply exactly to the selected lines.
  110. execute a:range . "global/^/" . a:command
  111. call histdel('search', -1)
  112. endfunction
  113. function! visualrepeat#repeat()
  114. if g:visualrepeat_tick == b:changedtick
  115. " visualrepeat.vim should handle the repeat.
  116. let l:repeat_sequence = g:visualrepeat_sequence
  117. let l:repeat_count = g:visualrepeat_count
  118. elseif exists('g:repeat_tick') && g:repeat_tick == b:changedtick
  119. " repeat.vim is enabled and would handle a normal-mode repeat.
  120. let l:repeat_sequence = g:repeat_sequence
  121. let l:repeat_count = g:repeat_count
  122. endif
  123. if exists('l:repeat_sequence')
  124. " A mapping for visualrepeat.vim or repeat.vim to repeat has been set.
  125. " Ensure that a corresponding visual mode mapping exists; some plugins
  126. " that only use repeat.vim may not have this.
  127. if ! empty(maparg(substitute(l:repeat_sequence, '^.\{3}', '<Plug>', 'g'), 'x'))
  128. " Handle mappings that use a register and want the same register
  129. " used on repetition.
  130. let l:reg = ''
  131. if exists('g:repeat_reg') && exists('g:repeat_sequence') &&
  132. \ g:repeat_reg[0] ==# g:repeat_sequence && ! empty(g:repeat_reg[1])
  133. if g:repeat_reg[1] ==# '='
  134. " This causes a re-evaluation of the expression on repeat, which
  135. " is what we want.
  136. let l:reg = '"=' . getreg('=', 1) . "\<CR>"
  137. else
  138. let l:reg = '"' . g:repeat_reg[1]
  139. endif
  140. endif
  141. " The normal mode mapping to be repeated has a corresponding visual
  142. " mode mapping. Use this so that the repetition will affect the
  143. " current selection. With this we also avoid the clumsy application
  144. " of the normal mode command to the visual selection, and can
  145. " support blockwise visual mode.
  146. let l:cnt = l:repeat_count == -1 ? '' : (v:count ? v:count : (l:repeat_count ? l:repeat_count : ''))
  147. call feedkeys('gv' . l:reg . l:cnt, 'n')
  148. call feedkeys(l:repeat_sequence)
  149. return 1
  150. endif
  151. endif
  152. try
  153. " Note: :normal has no bang to allow a remapped '.' command here to
  154. " enable repeat.vim functionality.
  155. if visualmode() ==# 'v'
  156. " Repeat the last change starting from the current cursor position.
  157. execute 'normal' (v:count ? v:count : '') . '.'
  158. elseif visualmode() ==# 'V'
  159. let [l:changeStart, l:changeEnd] = [getpos("'<"), getpos("'>")]
  160. " For all selected lines, repeat the last change in the line.
  161. if s:virtcol == 1
  162. " The cursor is set to the first column.
  163. call s:RepeatOnRange("'<,'>", 'normal ' . (v:count ? v:count : '') . '.')
  164. else
  165. " The cursor is set to the cursor column; the last change is
  166. " only applied to lines that have at least that many characters.
  167. call s:RepeatOnRange("'<,'>", printf('call visualrepeat#repeatOnVirtCol(%d, %s)',
  168. \ s:virtcol,
  169. \ string(v:count ? v:count : '')
  170. \))
  171. endif
  172. call setpos("'[", l:changeStart)
  173. call setpos("']", l:changeEnd)
  174. else
  175. " Yank the selected block and repeat the last change in scratch
  176. " lines at the end of the buffer (using a different buffer would be
  177. " easier, but the repeated command may depend on the current
  178. " buffer's settings), so that the change is limited to the
  179. " selection. The vis.vim plugin does the same, but we cannot use it,
  180. " because it performs the movement (to the bottom of the current
  181. " buffer) via regular paste commands (which clobber the repeat
  182. " command). We need to be careful to avoid doing that, using only
  183. " lower level functions.
  184. let l:changeStart = getpos("'<")
  185. let l:startVirtCol = virtcol("'<")
  186. let [l:count, l:startColPattern, l:startLnum, l:endLnum, l:finalLnum] = [v:count, ('\%>' . (l:startVirtCol - 1) . 'v'), line("'<"), line("'>"), line('$')]
  187. let l:selection = split(ingo#selection#Get(), '\n', 1)
  188. " Save the view after the yank so that the cursor resides at the
  189. " beginning of the selected block, just as we would expect after the
  190. " repeat. (The :normal / :delete of the temporary range later
  191. " modifies the cursor position.)
  192. let l:save_view = winsaveview()
  193. let l:tempRange = (l:finalLnum + 1) . ',$'
  194. call append(l:finalLnum, l:selection)
  195. " The cursor is set to the first column.
  196. call s:RepeatOnRange(l:tempRange, 'normal ' . (l:count ? l:count : '') . '.')
  197. let l:result = getline(l:finalLnum + 1, '$')
  198. try
  199. " Using :undo to roll back the append and repeat is safer,
  200. " because any potential modification outside the temporary range
  201. " is also eliminated. Only explicitly delete the temporary range
  202. " as a fallback.
  203. silent undo
  204. catch /^Vim\%((\a\+)\)\=:E/
  205. silent! execute l:tempRange . 'delete _'
  206. endtry
  207. for l:lnum in range(l:startLnum, l:endLnum)
  208. let l:idx = l:lnum - l:startLnum
  209. let l:line = getline(l:lnum)
  210. let l:startCol = match(l:line, l:startColPattern)
  211. let l:endCol = l:startCol + len(l:selection[l:idx])
  212. let l:resultLine = get(l:result, l:idx, '') " Replace the line part with an empty string if there are less lines after the repeat.
  213. let l:newLine = strpart(l:line, 0, l:startCol) . l:resultLine . strpart(l:line, l:endCol)
  214. call setline(l:lnum, l:newLine)
  215. endfor
  216. let l:addedNum = len(l:result) - l:idx - 1
  217. if l:addedNum == 0
  218. let l:changeEnd = [0, l:lnum, l:startCol + len(l:resultLine), 0]
  219. else
  220. " The repeat has introduced additional lines; append those (as
  221. " new lines) properly indented to the start of the blockwise
  222. " selection.
  223. let l:indent = repeat(' ', l:startVirtCol - 1)
  224. " To use the buffer's indent settings, first insert spaces and
  225. " have :retab convert those to the proper indent. Then, append
  226. " the additional lines.
  227. call append(l:lnum, repeat([l:indent], l:addedNum))
  228. silent execute printf('%d,%dretab!', l:lnum + 1, l:lnum + l:addedNum + 1)
  229. for l:addedIdx in range(l:addedNum)
  230. let l:addedLnum = l:lnum + 1 + l:addedIdx
  231. call setline(l:addedLnum, getline(l:addedLnum) . l:result[l:idx + 1 + l:addedIdx])
  232. endfor
  233. let l:changeEnd = [0, l:addedLnum, len(getline(l:addedLnum)) + 1, 0]
  234. endif
  235. " The change marks still point to the (removed) temporary range.
  236. " Make them valid by setting them to the changed selection.
  237. call setpos("'[", l:changeStart)
  238. call setpos("']", l:changeEnd)
  239. call winrestview(l:save_view)
  240. endif
  241. return 1
  242. catch /^Vim\%((\a\+)\)\=:E117:.*ingo#selection#Get/ " E117: Unknown function: ingo#selection#Get
  243. let s:errorMsg = 'For blockwise repeat, you need to install the ingo-library dependency'
  244. catch /^Vim\%((\a\+)\)\=:/
  245. " v:exception contains what is normally in v:errmsg, but with extra
  246. " exception source info prepended, which we cut away.
  247. let s:errorMsg = substitute(v:exception, '^\CVim\%((\a\+)\)\=:', '', '')
  248. catch
  249. let s:errorMsg = v:exception
  250. endtry
  251. return 0
  252. endfunction
  253. let s:errorMsg = ''
  254. function! visualrepeat#ErrorMsg()
  255. return s:errorMsg
  256. endfunction
  257. augroup visualrepeatPlugin
  258. autocmd!
  259. autocmd BufLeave,BufWritePre,BufReadPre * let g:visualrepeat_tick = (g:visualrepeat_tick == b:changedtick || g:visualrepeat_tick == 0) ? 0 : -1
  260. autocmd BufEnter,BufWritePost * if g:visualrepeat_tick == 0|let g:visualrepeat_tick = b:changedtick|endif
  261. augroup END
  262. let &cpo = s:save_cpo
  263. unlet s:save_cpo
  264. " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :