Skip to content

Surround 包围编辑 ​

为选区、文本对象或整行添加、删除、替换成对分隔符(括号、引号、HTML 标签、函数调用等),并额外支持全角符号。

默认按键映射(均可用对应的 g:im_surround_*_key 定制):

按键模式说明
[count]ys{motion}{char}normal为 motion 选中的内容添加分隔符
[count]yS{motion}{char}normal同上,但分隔符独占首尾新行
[count]yss / [count]ySSnormal为整行添加分隔符(ySS 独占新行)
[count]ds{char}normal删除光标处最近的分隔符
[count]cs{old}{new}normal将旧分隔符替换为新分隔符
[count]cS{old}{new}normal同上,新分隔符独占首尾新行
S / gSvisual为选区添加分隔符(gS 独占新行)
<c-g>s / <c-g>Sinsert插入一对分隔符并将光标置于中间
ib / abvisual/operator-pending自动识别最近包围,选中其内部/整体

常见用法示例(* 为光标位置):

旧文本按键新文本说明
surr*ound_wordsysiw)(surr*ound_words)紧凑括号包裹单词
surr*ound_wordsysiw(( surr*ound_words )带空格括号包裹
*make stringsys$""*make strings"引号包裹到行尾
[delete ar*ound me!]ds]delete ar*ound me!删除方括号
remove \<b>HTML t*ags\</b>dstremove HTML t*ags删除标签对
'change quot*es'cs'""change quot*es"单引号换双引号
delete(functi*on calls)dsffuncti*on calls删除函数调用
surr*ound_words2ysiw)((surr*ound_words))叠两层紧凑括号
((delete ar*ound me!))2ds)(delete ar*ound me!)删外层(第 2 层)
(f*oo)dsafoo自动识别并删除
(f*oo)dib()删内部,保留括号
(f*oo)dab(空)删整体,包括括号

前置计数:在 ys / yss / ds / cs 前加数字。ys 系一次套多层,如 2ysiw) 得到 ((word));ds / cs 系操作由内向外第 N 层,如嵌套括号内 2ds) 删外层、2cs)] 换外层。

点重复:ys / yss / ds / cs 做完后按 . 可在别处重复上一次操作,不用重输符号,如 ysiw) 后移动光标按 . 直接套同种括号。S / gS 可视包裹和 insert 的 <C-g>s / <C-g>S 不支持 .。

配置 ​

vim
" 包围功能开关(默认 0)
let g:im_surround_enable = 1

" 自定义快捷键
let g:im_surround_add_key              = 'ys'     " 加包围 (normal)
let g:im_surround_add_cur_key          = 'yss'    " 整行加包围 (normal)
let g:im_surround_add_linewise_key     = 'yS'     " 加包围-新行式 (normal)
let g:im_surround_add_cur_linewise_key = 'ySS'    " 整行加包围-新行式 (normal)
let g:im_surround_delete_key           = 'ds'     " 删包围 (normal)
let g:im_surround_change_key           = 'cs'     " 换包围 (normal)
let g:im_surround_change_linewise_key  = 'cS'     " 换包围-新行式 (normal)
let g:im_surround_visual_key           = 'S'      " 可视模式包裹 (visual)
let g:im_surround_visual_linewise_key  = 'gS'     " 可视模式新行包裹 (visual)
let g:im_surround_insert_key           = '<C-g>s' " 插入模式插入 (insert)
let g:im_surround_insert_linewise_key  = '<C-g>S' " 插入模式换行插入 (insert)
let g:im_surround_ib_key               = 'ib'     " 文本对象-内部 (visual/operator-pending)
let g:im_surround_ab_key               = 'ab'     " 文本对象-整体 (visual/operator-pending)

" ds / cs 定位到包围对时的闪光高亮时长(毫秒,0 关闭)
let g:im_surround_flash_ms          = 120

" 独占新行 / 跨行操作后自动重缩进(默认 1)
let g:im_surround_indent            = 1

" 自定义包围规则
let g:im_surround_surrounds = im#surround#config#default_surrounds()
" 等价于
let g:im_surround_surrounds = [
      \ {'key': '(',  'add': ['( ', ' )'], 'find': function('im#surround#find#matchpair')},
      \ {'key': ')',  'add': ['(', ')'],   'find': function('im#surround#find#matchpair')},
      \ {'key': '[',  'add': ['[ ', ' ]'], 'find': function('im#surround#find#matchpair')},
      \ {'key': ']',  'add': ['[', ']'],   'find': function('im#surround#find#matchpair')},
      \ {'key': '{',  'add': ['{ ', ' }'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '}',  'add': ['{', '}'],   'find': function('im#surround#find#matchpair')},
      \ {'key': '<',  'add': ['< ', ' >'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '>',  'add': ['<', '>'],   'find': function('im#surround#find#matchpair')},
      \ {"key": "'",  'add': ["'", "'"],   'find': function('im#surround#find#quote')},
      \ {'key': '"',  'add': ['"', '"'],   'find': function('im#surround#find#quote')},
      \ {'key': '`',  'add': ['`', '`'],   'find': function('im#surround#find#quote')},
      \ {'key': 't',  'add': function('im#surround#add#tag'),     'find': function('im#surround#find#tag'),       'replace': function('im#surround#change#tag')},
      \ {'key': 'T',  'add': function('im#surround#add#tag'),     'find': function('im#surround#find#tag'),       'replace': function('im#surround#change#tag_full')},
      \ {'key': 'f',  'add': function('im#surround#add#func'),    'find': function('im#surround#find#func'),      'replace': function('im#surround#change#func')},
      \ {'key': 'a',  'find': function('im#surround#find#auto')},
      \ {'key': 'i',  'add': function('im#surround#add#input')},
      \ {'key': '‘', 'add': ['‘', '’'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '’', 'add': ['‘', '’'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '“', 'add': ['“', '”'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '”', 'add': ['“', '”'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '(', 'add': ['(', ')'], 'find': function('im#surround#find#matchpair')},
      \ {'key': ')', 'add': ['(', ')'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '【', 'add': ['【', '】'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '】', 'add': ['【', '】'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '「', 'add': ['「', '」'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '」', 'add': ['「', '」'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '『', 'add': ['『', '』'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '』', 'add': ['『', '』'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '《', 'add': ['《', '》'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '》', 'add': ['《', '》'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '<', 'add': ['<', '>'], 'find': function('im#surround#find#matchpair')},
      \ {'key': '>', 'add': ['<', '>'], 'find': function('im#surround#find#matchpair')},
      \ ]


" 自定义别名(整体替换默认表)
let g:im_surround_aliases = im#surround#config#default_aliases()
let g:im_surround_aliases += [
      \ {'key': ')', 'targets' : [')', ')']},
      \ {'key': '(', 'targets' : ['(', '(']},
      \ {'key': ')', 'targets' : [')', ')']},
      \ {'key': '(', 'targets' : ['(', '(']},
      \ {'key': '[', 'targets' : ['{', '[', '「', '『', '【']},
      \ {'key': ']', 'targets' : ['}', ']', '」', '』', '】']},
      \ {'key': '{', 'targets' : ['{', '[', '「', '『', '【']},
      \ {'key': '}', 'targets' : ['}', ']', '」', '』', '】']},
      \ {'key': '「', 'targets' : ['{', '[', '「', '『', '【']},
      \ {'key': '」', 'targets' : ['}', ']', '」', '』', '】']},
      \ {'key': '『', 'targets' : ['{', '[', '「', '『', '【']},
      \ {'key': '』', 'targets' : ['}', ']', '」', '』', '】']},
      \ {'key': '【', 'targets' : ['{', '[', '「', '『', '【']},
      \ {'key': '】', 'targets' : ['}', ']', '」', '』', '】']},
      \ {'key': '<', 'targets' : ['<',  '《']},
      \ {'key': '>', 'targets' : ['>', '》' ]},
      \ {'key': '《', 'targets' : ['《',  '<']},
      \ {'key': '》', 'targets' : ['》', '>' ]}
      \ ]

g:im_surround_surrounds ​

该项为 List,推荐基于 im#surround#config#default_surrounds() 扩展。

字段类型含义
keyString触发字符;为空或类型不符整项失效
addList 或 Funcref(char) -> List添加时分隔符来源
findFuncref(char) -> Dictds / cs 定位旧包围
replaceFuncref() -> Listcs 生成新分隔符;缺省则高亮旧包围等新键

add 只管添加,find 只管 ds / cs 定位,replace 只管 cs 生成。

add

形式入参返回值含义
List—[String, String]直接赋值左右分隔符
Funcrefchar[String, String] / []动态生成,空表放弃

find

形式入参返回值含义
FuncrefcharDict / {}定位光标处旧包围,供 ds / cs 使用

命中返回 Dict 键值如下:

键类型必填含义
first_pos[Number, Number]是开分隔符首字节 [行, 字节列]
last_pos[Number, Number]是闭分隔符末字节 [行, 字节列]
open_lenNumber否开分隔符字节数
close_lenNumber否闭分隔符字节数

replace

形式入参返回值含义
Funcref—[left, right] / []新左右分隔符,空表放弃

g:im_surround_aliases ​

该项为 List,建议先用 im#surround#config#default_aliases() 取默认再改。

字段类型含义
keyString别名触发字符
targetsList目标 key 列表

ds / cs 输入别名键时,在 targets 的所有目标中挑光标处最内层的一对。

默认别名如下:

vim
let s:default_aliases = [
      \ {'key': 'q', 'targets': ['"', "'"]},
      \ {'key': 'r', 'targets': [']']},
      \ {'key': 'b', 'targets': [')']},
      \ {'key': 'B', 'targets': ['}']},
      \ ]

所有 g:im_surround_* 支持 b: 局部覆盖,优先级为 b: > g: > 默认值。surrounds 与 aliases 为整体替换语义,自定义后默认全被取代,需先取默认再拼接。

内置函数一览 ​

内置 add / replace 函数(add 入参均为 ch,replace 均无入参,出参均为 [left, right] / []):

函数入参返回值含义
im#surround#add#tagch[left, right] / []输标签名与属性
im#surround#add#funcch[left, right] / []输函数名
im#surround#add#inputch[left, right] / []各输左右分隔符
im#surround#add#invalidch[left, right] / []所键字符作左右分隔符
im#surround#change#tag—[left, right] / []换标签名,留属性
im#surround#change#tag_full—[left, right] / []换标签名,去属性
im#surround#change#func—[left, right] / []换函数名

内置 find 函数(入参均为 ch,pattern 多一个 opt):

函数入参返回值含义
im#surround#find#matchpairchDict / {}左右不同定界符
im#surround#find#quotechDict / {}左右相同引号
im#surround#find#tagchDict / {}HTML / XML 标签(复用 at)
im#surround#find#funcchDict / {}函数调用
im#surround#find#invalidchDict / {}兜底同字符成对
im#surround#find#autochDict / {}自动识别最近包围
im#surround#find#patternch, optDict / {}自定义正则,见下

用 im#surround#find#pattern 自定义正则包围(add / ds / cs 均可):

vim
let b:im_surround_surrounds = [
      \ {'key': 'b', 'add': ['**', '**'],
      \  'find': {ch -> im#surround#find#pattern(ch,
      \    {'open_pat': '\V**', 'close_pat': '\V**', 'scope': 'line'})}},
      \ ]

其中 opt 字段键值如下

字段含义
open_pat左侧 Vim 正则,字面加 \V
close_pat右侧 Vim 正则,字面加 \V
scopeline 仅当前行(默认),buffer 可跨行

进阶使用案例 ​

你可以通过 FileType 自动命令为特定文件类型(如 Markdown)动态扩展包围规则:

vim
augroup RimeGroup
  autocmd!
  autocmd FileType markdown call IMSurroundMarkdown()
augroup END

function! IMSurroundMarkdown() abort
  let b:im_surround_surrounds = g:im_surround_surrounds + [
        \ {'key': 'i', 'add': ['*', '*'],     'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V*', 'close_pat': '\V*', 'scope': 'line'})}},
        \ {'key': 'b', 'add': ['**', '**'],   'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V**', 'close_pat': '\V**', 'scope': 'line'})}},
        \ {'key': 'h', 'add': ['==', '=='],   'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V==', 'close_pat': '\V==', 'scope': 'line'})}},
        \ {'key': 'c', 'add': ['`', '`'],     'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V`', 'close_pat': '\V`', 'scope': 'line'})}},
        \ {'key': 'C', 'add': ['```', '```'], 'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V```\.\*', 'close_pat': '\V```', 'scope': 'buffer'})}},
        \ {'key': 'm', 'add': ['$', '$'],     'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V$', 'close_pat': '\V$', 'scope': 'line'})}},
        \ {'key': 'M', 'add': ['$$', '$$'],   'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V$$', 'close_pat': '\V$$', 'scope': 'buffer'})}},
        \ {'key': 'l', 'add': ['[', ']()'],   'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V[', 'close_pat': '\V](\.\{-})', 'scope': 'line'})}},
        \ {'key': 'L', 'add': ['![', ']()'],  'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V![', 'close_pat': '\V](\.\{-})', 'scope': 'line'})}},
        \ {'key': 'w', 'add': ['[[', ']]'],   'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V[[', 'close_pat': '\V]]', 'scope': 'line'})}},
        \ {'key': 'W', 'add': ['![[', ']]'],  'find': {ch -> im#surround#find#pattern(ch, {'open_pat': '\V![[', 'close_pat': '\V]]', 'scope': 'line'})}},
        \ ]
endfunction

基于 MIT 许可发布