• 4.9 内容替换
    • 快捷替换
    • 精确替换

    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(确认且无须整词匹配模式的替换)