0%

IDEA VIM文本编辑器

VIM 文本编辑器使用方法及技巧
--来源 《Practical Vim》

模式

普通模式

目的 操作 重复 回退
做出一个修改 {edit} . u
在行内查找下一指定字符 f{char}/t{char} ; ,
在行内查找上一指定字符 F{char}/T{char} ; ,
在文档中查找下一处匹配项 /pattern/<CR> n N
在文档中查找上一处匹配项 ?pattern/<CR> n N
执行替换 :s/target/replacement & u
执行一系列修改 qx{changes}q @x u

进入插入模式的方式

命令 等效按键 说明
i(insert) 从光标所在处前一个字符输入
I(insert) ^i 从光标所在行的第一个非空格符处输入
a(append) 从光标所在的下一个字符处输入
A(append) &a 从光标所在行的最后一个字符处输入
o A<Enter> 从光标所在的下一行处输入新的一行
O ko 从光标所在的上一行处输入新的一行
s cl 删除光标所在的下一个字符并进入编辑状态
S ^C 从光标所在行的第一个非空字符开始删除后面的内容后进入编辑状态
c(change) 删除选定范围内的内容进入编辑状态
C(change) c$ 删除当前行光标后面的部分进入编辑状态
J (Join) 将光标所在行与下一行的数据结合成同一行

操作符命令

Vim 的语法只有一条额外规则,即当一个操作符命令被连续调用两次时,它会作 用于当前行。所以 dd 删除当前行,而 >> 缩进当前行。gU 命令是一种特殊情况,我 们既可以用 gUgU ,也可以用简化版的 gUU 来使它作用于当前行。

命令 用途
c 修改(删除后进入插入模式)
d 删除
y 复制到寄存器
x,X x 向后删除一个字符,X 向前删除一个字符
p,P 粘贴在光标所在行之后(P 为光标所在行之前)
~ 反转光标下字符的大小写
g- 反转大小写
gu 转换为小写
gU 转换为大写
> 增加缩进
< 减少缩进

插入模式

返回普通模式的方式

按键操作 用途
ESC 切换到普通模式
<C-[> 切换到普通模式
<C-o> 切换到插入-普通模式

插入-普通模式的意思就是暂时退出插入模式,并在执行一个普通模式的命令后返回。

TIP:当我们编辑到一半光标已经移出屏幕中央时,可以使用 <C-o>zz 暂时退出插入模式、执行一个普通模式的命令并紧接着进入插入模式。

反向删除的方式

按键操作 用途
<C-h> 删除前一个字符(同退格键)
<C-w> 删除前一个单词
<C-u> 删除至行首

替换模式

按键操作 用途
r 进入替换模式(替换一个字符后退出)
R 进入替换模式(ESC 后退出)
gr 进入虚拟替换模式(替换一个字符后退出)
gR 进入虚拟替换模式(ESC 后退出)

某些字符会使替换模式变得复杂化。以制表符为例,在文件中它以单个字符表示,但在屏幕上它却会占据若干列的宽度,此宽度由 ‘tabstop’ 设置决定(参见:h ‘tabstop’ )。如果把光标移到制表符上,然后进入替换模式,那么我们所输入的下一个字符将会替换制表符。假设 ‘tabstop’ 选项设置为 8(这是缺省值),那么该操作的结果就是把 8 个字符替换成了一个字符,这将大幅缩短当前行的长度。

不过 Vim 还有另外一种替换模式,称为虚拟替换模式(Virtual Replace mode)。该模式可由 gR 命令触发,它会把制表符当成一组空格进行处理。假设我们把光标移到一个占屏幕 8 列宽的制表符上,然后切换到虚拟替换模式,在输入前 7 个字符时,每个字符都会被插入到制表符之前;最后,当输入了第 8 个字符时,该字符将会替换制表符。

在虚拟替换模式中,我们是按屏幕上实际显示的宽度来替换字符的,而不是按文件中所保存的字符进行替换。这会减少意外情况的发生,因此我建议在可能的情况下尽量使用虚拟替换模式。

可视模式

Vim 具有 3 种不同的可视模式,分别用于操作字符文本、行文本或块文本。

进入可视模式

按键操作 用途 备注
v 激活面向字符的可视模式 再次触发返回普通模式
V 激活面向行的可视模式 再次触发返回普通模式
<C-v> 面向列块的可视模式 再次触发返回普通模式
gv 重选上次的高亮选区
o 切换活动端点

高亮选区的范围由其两个端点界定。其中一端固定,而另一端可以随光标自由移动,我们可以用 o 键来切换其活动的端点。在定义选区时,如果定义到一半时,才发现选区开始的位置不对,此时用这个键会很方便,我们用不着退出可视模式再从头开始,只需按一下 o,然后重新调整选区的边界即可。

面向行/列的可视命令

在列间增加分隔竖线

上述列表使用 . 连续删除了多个空列,也可以使用向右移动扩展选区的形式删除多列空行。

在行间增加横线

向多行插入文本

在长短不一的高亮块后添加文本

可视-选择模式

在可视-选择模式中,选中一段文本后,再输入任意可见字符时,这些选中的文本将会被删除,同时 Vim 会进入插入模式,并插入这个可见字符。

<C-g> 可以在可视模式及选择模式间切换。切换后看到的唯一不同是屏幕下方的提示信息会在 “— 可视 —”(— VISUAL —)及“—选择—”(—SELECT—)间转换。但是,如果在选择模式中输入任意可见字符的话,此字符会替换所选内容并切换到插入模式。当然,如果是在可视模式中,你仍可以像往常一样用 c 键来修改所选内容。

可视模式选择技巧

按键操作 含义 用途
vit visually select inside tag 选择标签中内容

命令行模式

常用命令

命令 用途
:[range]delete [x] 删除指定范围内的行到寄存器x中
:[range]yank [x] 复制指定范围内的行到寄存器x中
:[line]put [x] 在指定行后粘贴寄存器x中的内容
:[range]copy [address] 把指定范围内的行拷贝到 {address} 所指定的行下
:[range]move [address] 把指定范围内的行移动到 {address} 所指定的行下
:[range]join 连接指定范围内的行
:[range]normal {commands} 对指定范围内的每一行执行普通模式命令 {commands}

copy 命令可以使用 t 来替代,move 命令可以使用 m 来代替。

复制

命令 用途
:6t. 把第六行复制到当前行下方
:t6 把当前行复制到第六行下方
:t. 为当前行创建一个副本
:t$ 把当前行复制到文件结尾
:'<,'>t0 把高亮选中的行复制到文件开头

t. 命令会创建一个当前行副本,而另外一种做法则是用普通模式的复制和粘贴命令 yyp 来达到同样的效果。这两种复制当前行的技术有个需要关注的差别。
yyp 会使用寄存器,而 t. 则不会。

范围指代

命令 用途
0 虚拟行,位于第一行上方
1 文件的第一行
. 当前行
$ 末尾行
% 文件中所有行,(1,$) 的简写形式
'< 代表高亮选区首行的位置标记
'> 代表高亮选区的最后一行

在使用可视模式选择一个高亮范围后,按 : 进入命令行模式,会自动补充 '<,'> 来指代对高亮范围对应的行进行操作。

模式指定范围

VIM 接受以 /{start-pattern}/,/{end-pattern}/ EX 命令来圈定范围。

:/<html>/,/</html>/p

<html>
	<head>
		<title>Practical Vim</title>
	</head>
	<body>
		<h1>Practical Vim</h1>
	</body>
</html>

自动补全 Ex 命令

如果我们多次按 <Tab> 键的话,命令行上会依次显示 colder、colorscheme,然后再回 到最初的 col,如此循环往复。要想反向遍历补全列表,可以按 <S-Tab>。而我们使用 <C-d> 命令会让 Vim 显示可用的补全列表。

回溯历史命令

命令 用途
q/ 打开查找命令历史的命令行窗口
q: 打开 Ex 命令历史的命令行窗口
<C-f> 从命令行模式切换到命令行窗口

与外部交互

执行 Shell 命令

在 Vim 的命令行模式中,给命令加一个叹号前缀 :!{cmd} 就可以调用外部程序。而使用 :shell 则会开启一个 Shell 会话,并在不需要时执行 :exit 命令后返回 VIM。

管理缓冲区的内容

命令 用途
:read !{cmd} 在shell中执行 {cmd},并将其标准输出插入到光标下方
:[range]write !{cmd} 在shell中执行 {cmd},以 [range] 作为标准输入
:[range]!{filter} 使用外部程序 {filter} 过滤指定的 [range]

寄存器

Vim 的删除、复制与粘贴命令都会用到众多寄存器中的某一个。我们可以通过给命令加 “{register} 前缀的方式指定要用的寄存器。若不指明,Vim 将缺省使用无名寄存器。

使用 :reg {register} 可以查看寄存器内内容

另外还可以活用 <C-r> 来提升效率,在插入模式中使用 <C-r>{register} 向文本中插入寄存器中的内容;在命令行模式中使用 <C-r>{register} 向命令行中插入寄存器中的内容。

无名寄存器

倘若没有指定要使用的寄存器,VIM 将缺省使用无名寄存器,它可以用双引号表示。 如 “”dd 效果相同。

xsd{motion}c{motion}y{motion} 命令(以及它们对应的大写命令) 都会覆盖无名寄存器中的内容。无论哪一种情况,都可以通过加 "{register} 前缀来指定另外一个寄存器,但无名寄存器总是缺省的。事实上,无名寄存器的内容很容易被覆盖,如果我们不小心的话,会导致问题发生。

复制专用寄存器

当我们使用 y{motion} 命令时,要复制的文本不仅会被拷贝到无名寄存器中,而且也被拷贝到了复制专用寄存器中,后者可用数字 0 加以引用。

复制专用寄存器,顾名思义,仅当使用 y{motion} 命令时才会被赋值。如果我们复制了一些文本,可以确信该文本会一直保存于寄存器 0 中,直到我们复制其他文本时才会被覆盖。复制专用寄存器是稳定的,而无名寄存器是易变的。

有名寄存器

VIM 提供了一组以 26 个英文字母 a-z 命名的有名寄存器。需要注意的是,用小写字母引用有名寄存器,会覆盖该寄存器的原有内容,而换用大写字母的话, 则会将新内容添加到该寄存器的原有内容之后。

系统寄存器和选择专用寄存器

如果想从 Vim 复制文本到外部程序(反之亦然),则必须使用系统剪贴板。Vim 的加号寄存器与系统剪贴板等效,可用 + 号引用。

如果我们在外部程序中用剪切或复制命令获取了文本,就可以通过 "+p 命令(或在插入模式下用 <C-r>+)将其粘贴到 Vim 内部。相反地,如果在 Vim 的复制或删除命令之前加入 "+ ,相应的文本将被捕获至系统剪贴板。这意味着我们能够轻松地把文本粘贴到其他应用程序中了。

表达式寄存器

用 = 符号指明使用表达式寄存器。在插入模式中,输入 = 就可 以访问这一寄存器。这条命令会在屏幕的下方显示一个提示符,我们可以在其后输入要执行的表达式。输入表达式后敲一下 ,Vim 就会把执行的结果插入到文档的当 前位置了。

其他寄存器

寄存器 内容
"% 当前文件名
"# 轮换文件名
". 上次插入的文本
": 上次执行的Ex命令
"/ 上次查找的模式

录制宏

q 键既是“录制”按钮,也是“停止”按钮。为了录制我们的按键操作,一开始需要按 q{register},从而指定一个用于保存宏的寄存器。当状态栏中出现“记录中” 时,表示录制已经开始。此后,我们执行的每一条命令都将被宏捕获,直到我们再次按下 q 键停下为止。

通过 :reg {register} 可以查看到寄存器中存放的录制宏内容

执行宏

有多种单次执行宏的方式

  1. @{register} 命令执行指定寄存器的内容
  2. @@ 来重复最近调用过的宏

如果是需要反复执行的宏可以可以通过 {times}@@ 来指定宏的执行次数。当然,我们也可以通过可视模式选中需要执行的行后,通过命令 :<,> normal @{register} 对那些行执行宏;也可以通过 :from,to normal @{register} 来对指定的行执行宏。

追加宏

有时候,我们在录制宏的过程中会漏掉某个至关重要的步骤。在这种情况下,我们没必要从头开始重录所有的步骤,而是可以在现有宏的结尾附加额外的命令。

在我们输入 qa 时,Vim 将开始录制接下来的按键操作,并将它们保存到寄存器 a 中,这会覆盖该寄存器原有的内容。如果我们输入的是 qA 的话,Vim 也会录制按键操作,但会把它们附加到寄存器 a 原有的内容之后。我们可以用这种方式更正该错误。

更快的移动和跳转

页面移动

命令 等价按键 功能
Ctrl + u (up) 上移半页
Ctrl + d (down) 下移半页
Ctrl + f (forward) Page Up 下移一页
Ctrl + b (back) Page Down 上移一页
z + Enter 将当前行滚动到屏幕顶端
z + . 将当前行滚动到屏幕中心
z + - 将当前行移到屏幕底部

如果前面加上数值参数,代表将指定行滚动到屏幕指定的位置。

跳转

方向跳转

命令 等价按键 功能
h
j 下(同 Enter)
k
l 右(同 Space)

词跳转

命令 功能
w 正向移动到下一单词的开头
W 正向移动到下一单词的开头(忽略字符及标点)
b 反向移动到上一单词的开头
B 反向移动到上一单词的开头(忽略字符及标点)
e 正向移动到下一个单词词尾
E 正向移动到下一个单词词尾(忽略字符及标点)
ge 反向移动到上一单词的结尾

使用 % 可以在一对匹配开、闭括号间进行跳转,包括:()[]{}

行跳转

命令 功能
j 向下移动一个实际行
k 向上移动一个实际行
0 移动至实际行的行首
^ 移动到实际行的第一个非空白字符
$ 移动到实际行的行尾

上述命令前加 g 则代表按屏幕行移动

页跳转

命令 功能
H (High) 光标移动到页首
M (Mid) 光标移动到页中
L (Low) 光标移动到页尾
nH 移动到页首往下的第 n 行
nL 移动到页尾往上的第 n 行

结构跳转

命令 功能
(, ) ( 移动到当前句子开头 ) 移动到结尾
{, } { 移动到当前段落开头 } 移动到结尾
[[, ]] [[ 移动到当前小节开头 ]] 移动到结尾

前面加数值参数可以移动多次

文档跳转

命令 功能
G(Goto) 移动到文档最后一行
nG 移动到文档第n行
gg 移动到文档第一行
<C-o> 返回到上次进行操作的地方

查找

字符查找

命令 用途
f{char} 正向移动到下一个 {char} 所在之处
F{char} 反向移动到上一个 {char} 所在之处
t{char} 正向移动到下一个 {char} 所在之处的前一个字符上
T{char} 反向移动到上一个 {char} 所在之处的后一个字符上
; 重复上次的字符查找命令
, 反转方向查找上次的字符查找命令

词查找

命令 功能
/pattern 从光标位置向下搜索
?pattern 从光标位置向上搜索
n 往同一个方向重复搜索
N 往相反方向重复搜索
/ + Enter 向前重复搜索
? + Enter 向后重复搜索
* 查找光标所在的词

由于搜索会将光标移动至匹配单词的第一个字符,因此可配合操作实现增删改等功能。
使用 {action}{motion} 删除对应的内容,如 :d/{pattern} 为删除匹配行

TextObject

Vim 的文本对象分为两类:一类是操作分隔符的文本对象,如 i)i"it、;另一类用于操作文本块,如单词、句子和段落。

分隔文本对象

分割文本对象有两个关键词,即以 a 开头的 around 命令,和以 i 开头的 inside。

文本对象 等价按键 选择区域
a) ab 一对圆括号(范围包含括号)
i) ib 一对圆括号(范围不包含括号)
a]/i] 一对方括号(区别同上)
a}/i} aB/iB 一对花括号(区别同上)
a>/i> 一对尖括号(区别同上)
a’/i’ 一对单引号(区别同上)
a”/i” 一对双引号(区别同上)
a`/i` 一对反引号(区别同上)

范围文本对象

文本对象 选择范围
iw 当前单词
aw 当前单词及一个空格
iW 当前字串
aW 当前字串及一个空格
is 当前句子
as 当前句子及一个空格
ip 当前段落
ap 当前段落及一个空行

位置标记

命令 功能
mx 将当前位置标记成 x
'x 将光标移动至标记 x 所在行
`x 将光标移动至标记 x 所在字符

x 可以是任意字母

下面是一些其他的位置标记

位置标记 跳转到
`` 当前文件中上次跳转动作之前的位置
`. 上次修改的地方
`^ 上次插入的地方
`[ 上次修改或复制的起始位置
`] 上次修改或复制的结束位置
`\< 上次高亮选区的起始位置
`> 上次高亮选区的结束位置

查找和替换

增强型功能

高亮匹配显示

在匹配时,配置 :set hlsearch:set hls 来高亮显示匹配的文本,:set nohlsearch 或是 :set nohls 关闭高亮显示。

增量搜索

缺省情况下,我们在输入查找模式时,Vim 不会进行查找,只有当我们按下 <CR> 后, 它才会立即展开行动。:set incsearch 是我最喜爱的增强型功能。 该设置会让 Vim 根据已在查找域中输入的文本,预览第一处匹配。每当我们新输入一个 字符时,Vim 会即时更新预览内容。

使用元字符匹配

元字符 说明 示例
\c 忽略大小写匹配 /\cfoo 代表向下查询 foo、Foo、FOO
\C 强制大小写匹配
\v 开启 very magic 搜索模式 /\v[0-9a-fA-f]{6} 无需对保留字符转义即可按模式匹配
\V 原义搜索模式 会屏蔽 .* 以及 + 的特殊含义
<\zs 标志匹配的起始标记
>\ze 标志匹配的结尾标记

匹配范围标记

单词界定符,其本身不匹配任何字符,仅表示单词与围绕此单词的空白字符(或标点符号)之间的边界。

单词匹配

/\v<word> 只匹配 word 单词,忽略 password 这种内嵌匹配

调整匹配范围

这个基本模式用到了一个常见的正则表达式惯例 "[^"]+"。该模式使用两个引号作为起始与结尾的标记,然后匹配除引号之外的一个或多个字符。在最后一行作为压轴出场的模式中,我们在开引号之后加入了元字符 \zs,在闭引号之前加入了元字符\ze。这样一来,引号本身被排除于匹配之外,只剩下引用的内容被高亮起来。

替换

substitute 命令允许我们先查找一段文本,再用另一段文本将其替换掉。命令的语法如下所示:

:[range]s[ubstitute]/{pattern}/{string}/[flags]

其中 range 有多个指代符号:

符号 解释 示例
$ 代表最后一行 :1,$s/pattern/content/g
% 同 1,$ 代表全文 :%s/pattern/content/g
. 代表当前行

利用标志位调整替换行为

标志位 描述
g 使得 subsititute 命令可在整行范围内执行
c 可以确认或拒绝每一处修改
n 抑制正常的替换行为,而只是报告本次 substitute 命令匹配的个数
& 仅仅用于指示 Vim 重用上一次 substitute 命令所用过的标志位

全行替换
:s/going/rolling/g

全局替换
:%s/going/rolling/g

确认每一处替换行为

:%s/content/copy/gc

Vim 会为我们提示 所有的选项“y/n/a/q/l/^E/^Y”。下表展示了每种答案的含义

答案 用途
y 替换此处匹配
n 忽略此处匹配
q 退出替换过程
l “last” 替换此处匹配后退出
a “all” 替换此处与之后所有的匹配
<C-e> 向上滚动屏幕
<C-y> 向下滚动屏幕

引用

假设我们已经复制了多行文本,并存放于寄存器 0 中。我们现在的目标是在 substitute 命令的替换域中使用这段文本。通过运行以下命令,可以做到这一点:

:%s//\=@0/g

替换域中出现的 \= 将指示 Vim 执行一段表达式脚本。在 Vim 脚本中,我们可以用 @{register} 来引用某个寄存器的内容。具体来说,@0 会返回复制专用寄存器 的内容,而 @" 则返回无名寄存器的内容。

将光标偏移到查找匹配的结尾

每当我们执行查找命令时,光标总会被定位于匹配的首字母上。虽然这种缺省操 作看起来比较合理,但我们可能有时更倾向于将光标定位于查找匹配的结尾。Vim 的 查找偏移功能,可以将此想法变为现实(参见 :h search-offset )。

交换两个单词位置

使用子匹配重排 CSV 文件的字段

last name, first name, email
neil, drew, drew@email.com
doe, john, john@email.com

如果我们想交换这些字段的次序,即把电子邮箱放到首列,其次是名字,最后一列为姓氏。通过使用以下 substitute 命令,我们可以做到这一点:

/\v^([^,]*),([^,]*),([^,]*)$
:%s//\3,\2,\1

在这个模式中,[^,] 会匹配除逗号以外的任何字符,因此,([^,]*) 不仅会 匹配 0 次或多次连续的非逗号字符,而且会把捕获到的结果当作子匹配。将此表达式重复 3 次,即可分别捕获 CSV 文件中 3 组字段中的每一列内容。

我们可以通过记号 \n 来引用这些子匹配。因此,在替换域中,\1 表示姓氏, \2 表示名字,\3 表示电子邮箱。在把一行内容切分成单独的字段后,我们可以把它们按照设想的顺序进行重新排列,即 \3\2\1 —— 电子邮箱,名字,姓氏。

交换两个或更多的单词

我们甚至不需要创建函数就可以实现这个功能。我们只需为此简单地定义一个字典数据结构,其中包含两组键-值对。当我们将”dog”作为“键”传入字典 swapper 时,它会返回”man”,反之亦然。

let swapper={"dog":"man","man":"dog"}

:echo swapper["dog"]
> man
:echo swapper["man"]
> dog

此模式可以轻松地匹配整个单词“man”或整个单词“dog”。其中,圆括号用于捕获已匹配的文本,方便我们在替换域中引用。

/\v(<man>|<dog>)

我们通常使用 Vim 的符号 \1\2 (以此类推)来引用被捕获的文本。但在 Vim 脚本中,我们必须调用 submatch() 函数才能得到被捕获的文本。

/\v(<man>|<dog>)
:%s//\={"dog":"man","man":"dog"}[submatch(1)]/g

Global 命令

:global 命令允许我们在某个指定模式的所有匹配行上运行 Ex 命令。通常采用以下形式:

:[range] global[!] /{pattern}/ [cmd]

首先,在缺省情况下,:global 命令的作用范围是整个文件(%),这一点与其他大多数 Ex 命令(包括 :delete:substitute 以及 :normal)有所不同,这些命令的缺省范围仅为当前行(.)。其次,{pattern} 域与查找历史相互关联。这意味着如果将该域留空的话,Vim 会自动使用当前的查找模式。另外,[cmd] 可以是除 :global 命令之外的任何 Ex 命令。顺便提一 下,如果我们不指定任何 [cmd],Vim 将缺省使用 :print

我们可以用 :global! 或者 :vglobal(v 表示 invert)反转 :global 命令 的行为。这两条命令将指示 Vim 在没有匹配到指定模式的行上执行 [cmd]

显示指定模式的文本

命令 功能
:g/pattern 显示全局包含 pattern 的文本
:g!/pattern 显示全局不包含 pattern 的文本
:g/pattern/nu 显示全局包含 pattern 的文本并显示行号
:60,100g/pattern 显示 60 到 100 行包含 pattern 的文本

指定模式的所有匹配行上运行Ex命令

> :g/re/d

或

> /re
> :g//d

可以将 :global 命令的查找域留空,Vim 将会重用最后一次的查找模式。

指定模式的反选匹配行上运行Ex命令

> :v/re/d

将 TODO 项收集至寄存器

:g/TODO //将带有TODO的文本行打印出来

:g/TODO/y A //将带有TODO的文本行附加到a寄存器(A 大写表示附加 a 小写表示覆盖) 

常用技巧

选择性修改文档内匹配的单词

增减数字

使用 <C-a> <C-x> 可以对数字进行增减,而不用通过编辑模式更改文本。

用迭代求值的方式给列表编号

:normal @a 命令将指示 Vim 在高亮选中的每一行上执行这个宏。i 的初始值是 2,但它在每次宏执行完后都会递增。最终,每行都以连续的数字开头了。

对文本进行排序

:[range] sort

:.+1,/}/-1 sort
从当前行向下偏移一行开始,直到匹配到模式 /}/ 的那一行为止,按照字母顺序重新排序。

/}/ 会将光标置于 } 字符所在行的起始位置

IDEA 常用技巧

显示当前所处的模式

set showmode

Leader Action模式

通过 ,re 简化触发 :action RenameElement<CR>

let mapleader = ","

nnoremap <leader>re :action RenameElement<CR>
nnoremap <leader>gi :action GotoImplementation<CR>
nnoremap <leader>im :action ImplementMethods<CR>
nnoremap <leader>rv :action IntroduceVariable<CR>
nnoremap <leader>cr :action CopyReference<CR>
nnoremap <leader>em :action ExtractMethod<CR>
nnoremap <leader>sw :action SurroundWith<CR>