4.9 内容替换

有个名为 iFoo 的全局变量,被工程中 16 个文件引用过,由于你岳母觉得匈牙利命名法严重、异常、绝对以及十分万恶,为讨岳母欢心,不得不将该变量更名为 foo,怎么办?依次打开每个文件,逐一查找后替换?对我而言,内容替换存在两种场景:快捷替换和精确替换。

快捷替换

前面介绍的 ctrlsf 已经把匹配的字符串汇总在侧边子窗口中显示了,同时,它还允许我们直接在该子窗口中进行编辑操作,在这种环境下,如果我们能快捷选中所有匹配字符串,那么就可以先批量删除再在原位插入新的字符串,这岂不是我们需要的替换功能么?

快捷选中 ctrlsf 子窗口中的多个匹配项,关键还是这些匹配项分散在不同行的不同位置,这就需要多光标编辑功能,vim-multiple-cursors 插件(https://github.com/terryma/vim-multiple-cursors )为此而生。装好 vim-multiple-cursors 后,你随便编辑个文档,随便输入多个相同的字符串,先在可视化模式下选中其中一个,接着键入 ctrl-n,你会发现第二个该字符串也被选中了,持续键入 ctrl-n,你可以选中所有相同的字符串,把这个功能与 ctrlsf 结合,你来感受下:

4.9 内容替换  - 图1(快捷替换)
上图中,我想将 prtHelpInfo() 更名为 showHelpInfo(),先通过 ctrlsf 找到工程中所有 prtHelpInfo,然后直接在 ctrlsf 子窗口中选中第一个 ptr,再通过 vim-multiple-cursors 选中第二个 ptr,接着统一删除 ptr 并统一键入 show,最后保存并重新加载替换后的文件。
vim-multiple-cursors 默认快捷键与我系统中其他软件的快捷键冲突,按各自习惯重新设置:

  1. let g:multi_cursor_next_key='<S-n>'
  2. let g:multi_cursor_skip_key='<S-k>'

精确替换

vim 有强大的内容替换命令:

  1. :[range]s/{pattern}/{string}/[flags]

在进行内容替换操作时,我关注几个因素:如何指定替换文件范围、是否整词匹配、是否逐一确认后再替换。

如何指定替换文件范围?

  • 如果在当前文件内替换,[range] 不用指定,默认就在当前文件内;
  • 如果在当前选中区域,[range] 也不用指定,在你键入替换命令时,vim 自动将生成如下命令:

    1. :'<,'>s/{pattern}/{string}/[flags]
  • 你也可以指定行范围,如,第三行到第五行:

    1. :3,5s/{pattern}/{string}/[flags]
  • 如果对打开文件进行替换,你需要先通过 :bufdo 命令显式告知 vim 范围,再执行替换;

  • 如果对工程内所有文件进行替换,先 :args /_.cpp /_.h 告知 vim 范围,再执行替换;
    是否整词匹配?{pattern} 用于指定匹配模式。如果需要整词匹配,则该字段应由 < 和 > 修饰待替换字符串(如,<iFoo>);无须整词匹配则不用修饰,直接给定该字符串即可;

是否逐一确认后再替换?[flags] 可用于指定是否需要确认。若无须确认,该字段设定为 ge 即可;有时不见得所有匹配的字符串都需替换,若在每次替换前进行确认,该字段设定为 gec 即可。

是否整词匹配和是否确认两个条件叠加就有 4 种组合:非整词且不确认、非整词且确认、整词且不确认、整词且确认,每次手工输入这些命令真是麻烦;我把这些组合封装到一个函数中,如下 Replace() 所示:

  1. " 替换函数。参数说明:
  2. " confirm:是否替换前逐一确认
  3. " wholeword:是否整词匹配
  4. " replace:被替换字符串
  5. function! Replace(confirm, wholeword, replace)
  6. wa
  7. let flag = ''
  8. if a:confirm
  9. let flag .= 'gec'
  10. else
  11. let flag .= 'ge'
  12. endif
  13. let search = ''
  14. if a:wholeword
  15. let search .= '\<' . escape(expand('<cword>'), '/\.*$^~[') . '\>'
  16. else
  17. let search .= expand('<cword>')
  18. endif
  19. let replace = escape(a:replace, '/\&~')
  20. execute 'argdo %s/' . search . '/' . replace . '/' . flag . '| update'
  21. endfunction

为最大程度减少手工输入,Replace() 还能自动提取待替换字符串(只要把光标移至待替换字符串上),同时,替换完成后自动为你保存更改的文件。现在要做的就是赋予 confirm、wholeword 不同实参实现 4 种组合,再绑定 4 个快捷键即可。如下:

  1. " 不确认、非整词
  2. nnoremap <Leader>R :call Replace(0, 0, input('Replace '.expand('<cword>').' with: '))<CR>
  3. " 不确认、整词
  4. nnoremap <Leader>rw :call Replace(0, 1, input('Replace '.expand('<cword>').' with: '))<CR>
  5. " 确认、非整词
  6. nnoremap <Leader>rc :call Replace(1, 0, input('Replace '.expand('<cword>').' with: '))<CR>
  7. " 确认、整词
  8. nnoremap <Leader>rcw :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>
  9. nnoremap <Leader>rwc :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>

我平时用的最多的无须确认但整词匹配的替换模式,即 <leader>rw。

请将完整配置信息添加进 .vimrc 中:

  1. " 替换函数。参数说明:
  2. " confirm:是否替换前逐一确认
  3. " wholeword:是否整词匹配
  4. " replace:被替换字符串
  5. function! Replace(confirm, wholeword, replace)
  6. wa
  7. let flag = ''
  8. if a:confirm
  9. let flag .= 'gec'
  10. else
  11. let flag .= 'ge'
  12. endif
  13. let search = ''
  14. if a:wholeword
  15. let search .= '\<' . escape(expand('<cword>'), '/\.*$^~[') . '\>'
  16. else
  17. let search .= expand('<cword>')
  18. endif
  19. let replace = escape(a:replace, '/\&~')
  20. execute 'argdo %s/' . search . '/' . replace . '/' . flag . '| update'
  21. endfunction
  22. " 不确认、非整词
  23. nnoremap <Leader>R :call Replace(0, 0, input('Replace '.expand('<cword>').' with: '))<CR>
  24. " 不确认、整词
  25. nnoremap <Leader>rw :call Replace(0, 1, input('Replace '.expand('<cword>').' with: '))<CR>
  26. " 确认、非整词
  27. nnoremap <Leader>rc :call Replace(1, 0, input('Replace '.expand('<cword>').' with: '))<CR>
  28. " 确认、整词
  29. nnoremap <Leader>rcw :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>
  30. nnoremap <Leader>rwc :call Replace(1, 1, input('Replace '.expand('<cword>').' with: '))<CR>

比如,我将工程的所有 .cpp 和 .h 中的关键字 MyClassA 按不确认且整词匹配模式替换成 MyClass,所以注释中的关键字不会被替换掉。如下所示:

4.9 内容替换  - 图2(不确认且整词匹配模式的替换)
又比如,对当前文件采用需确认且无须整词匹配的模式进行替换,你会看到注释中的关键字也被替换了:
4.9 内容替换  - 图3(确认且无须整词匹配模式的替换)