vim中一些有用但是不常用功能备忘
背景
在vim的使用过程中,会发现一些比较冷门但是也非常有用的命令,整理备忘一下。
行中(gm)
修改一行开始和结尾都有比较方便的命令,但是当行比较长的时候,如果想移动到中间某个为止就比较麻烦,此时可以通过gm来首先移动到行的中间,然后再使用w/b甚至f/u来进行小范围移动。
复选(gv)
在手动缩进一块代码的时候,通常会发现一次缩进不够,需要再次/多次缩进(当然也可以提前算好次数,使用次数前置)。对于大块文本的选择其实也不方便,所以希望可以重复上次选中的内容,这个时候就有这个宝藏命令了。
大范围移动([[ ( { ]])
这些可以识别句子,段落,配对的大括弧等,对于经常使用C/C++语言开发极为方便。
屏幕范围移动(H M L)
在使用四个移动按键移动屏幕时速度非常慢,此时使用这几个命令可以快速的把鼠标移动到屏幕的最高、中间、最低位置。和ctrl + U/D比较,这个只是移动鼠标,不滚动屏幕,所以看起来更连续。而且这三个按键难得和动作匹配,也很容易记忆和使用。
还有通过ctrl + e 和 ctrl - y来直接将屏幕滚动。
再次替换(&)
文档说明了这个操作就是:s
的同义词,也就是重复上次替换,只是这次替换时不带替换flags的;如果需要保留上次替换的标志位,可以使用g&命令。
& Synonym for `:s` (repeat last substitute). Note that the flags are not remembered, thus it might
actually work differently. You can use `:&&` to keep
the flags.
*g&* g& Synonym for `:%s//~/&` (repeat last substitute with last search pattern on all lines with the same flags).
For example, when you first do a substitution with
`:s/pattern/repl/flags` and then `/search` for
something else, `g&` will do `:%s/search/repl/flags`.
Mnemonic: global substitute. {not in Vi}
在代码实现中,就是把这些单字符的操作替换换双字符操作。这种替换从程序实现的角度来看微不足道,但是作为使用者来说,少敲击一次键盘就是一次胜利。
static void
nv_optrans(cmdarg_T *cap)
{
static char_u *(ar[8]) = {(char_u *)"dl", (char_u *)"dh",
(char_u *)"d$", (char_u *)"c$",
(char_u *)"cl", (char_u *)"cc",
(char_u *)"yy", (char_u *)":s\r"};
static char_u *str = (char_u *)"xXDCsSY&";
///...
}
寄存器(registers)
black hole "_ 比方说已经把一个内容拷贝到了寄存器中,光标移动到目标之后想把光标位置内容先删除掉,但是此时删除就会覆盖常用的默认寄存器,此时可以明确指定把操作结果放在黑洞寄存器中,从而避免覆盖已经存在的寄存器。当然也可以使用数字寄存器0,该值始终表存最近一次yank的内容;对应的数字1表示最近一次delete/change的内容(虽然两个数字相邻,但是它们在键盘上的位置是真远啊~)。
当前文件名 "% 在编辑源文件的时候,希望打开/创建相同文件名(不同后缀)的头文件,此时可以通过该寄存器获得文件名。
last search pattern"/ 一般会首先通过search确认可能修改的内容,然后再执行substitute命令,此时通过该寄存器可以获得上次搜索pattern字符串。
插入命令输出(:read !cmd)
在代码中可能会用到一些自动生成的代码,此时就需要执行一个外部工具,将工具的输出直接插入到特定为止,此时就可以使用这个read命令,直接将脚本生成的命令插入到正在编辑的文本为止。
%
这个更方便的是可以内置识别C语言中的注释和宏定义的内容,这样在语法级别特定范围的选择就比较方便。
这个操作看起来是专门为C语言准备的:可以识别C语言的注释(早期C语言标准只支持这种注释)和宏指令,并且对于宏指示更加友好,即使光标不在指示引导符前面,只要光标后面没有括弧,还是会尝试搜索宏指示(毕竟,宏指示只可能在一行的开始)。
% Find the next item in this line after or under the
cursor and jump to its match. inclusive motion.
Items can be:
([{}]) parenthesis or (curly/square) brackets
(this can be changed with the
'matchpairs' option)
/* */ start or end of C-style comment
#if, #ifdef, #else, #elif, #endif
C preprocessor conditionals (when the
cursor is on the # or no ([{
following)
从代码的流程来看,在没有指定起始字符的时候,
- 判断光标位置(只判断光标位置)是否是注释符号(*或者/),如果是的话分别尝试判断前后字符是否组成注释的开始或者结束,如果是的话就认为是搜索注释段。
- 从当前位置开始搜索的是匹配pair对中的人一个字符(忽略不在字符集中的所有字符),如果找到会根据字符本身决定是前向还是后向搜索对应的匹配字符;如果没有找到
- 如果没有找到括弧集合中任意元素,从行头开始判断是否是一个C语言预处理指示的开始
这个流程对于宏指令的支持最为友好,因为宏指令通常不会包含任何括弧,这时vim会从行开始搜索宏起始标志,所以光标在指示行任何位置就行;会从光标位置开始搜索,并跳过不是括弧的字符,这也就意味着光标只要在括弧的前面即可;而对于注释的支持最弱,光标位置必须是注释符号两个中的某一个上,前后都不行。
/*
* findmatchlimit -- find the matching paren or brace, if it exists within
* maxtravel lines of the cursor. A maxtravel of 0 means search until falling
* off the edge of the file.
*
* "initc" is the character to find a match for. NUL means to find the
* character at or after the cursor. Special values:
* '*' look for C-style comment / *
* '/' look for C-style comment / *, ignoring comment-end
* '#' look for preprocessor directives
* 'R' look for raw string start: R"delim(text)delim" (only backwards)
*
* flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
* FM_FORWARD search forwards (when initc is '/', '*' or '#')
* FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
* FM_SKIPCOMM skip comments (not implemented yet!)
*
* "oap" is only used to set oap->motion_type for a linewise motion, it can be
* NULL
*/
pos_T *
findmatchlimit(
oparg_T *oap,
int initc,
int flags,
int maxtravel)
{
///...
/*
* initc was not given, must look for something to match under
* or near the cursor.
* Only check for special things when 'cpo' doesn't have '%'.
*/
if (!cpo_match)
{
/* Are we before or at #if, #else etc.? */
ptr = skipwhite(linep);
if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep))
{
ptr = skipwhite(ptr + 1);
if ( STRNCMP(ptr, "if", 2) == 0
|| STRNCMP(ptr, "endif", 5) == 0
|| STRNCMP(ptr, "el", 2) == 0)
hash_dir = 1;
}
/* Are we on a comment? */
else if (linep[pos.col] == '/')
{
if (linep[pos.col + 1] == '*')
{
comment_dir = FORWARD;
backwards = FALSE;
pos.col++;
}
else if (pos.col > 0 && linep[pos.col - 1] == '*')
{
comment_dir = BACKWARD;
backwards = TRUE;
pos.col--;
}
}
else if (linep[pos.col] == '*')
{
if (linep[pos.col + 1] == '/')
{
comment_dir = BACKWARD;
backwards = TRUE;
}
else if (pos.col > 0 && linep[pos.col - 1] == '/')
{
comment_dir = FORWARD;
backwards = FALSE;
}
}
}
/*
* If we are not on a comment or the # at the start of a line, then
* look for brace anywhere on this line after the cursor.
*/
if (!hash_dir && !comment_dir)
{
/*
* Find the brace under or after the cursor.
* If beyond the end of the line, use the last character in
* the line.
*/
if (linep[pos.col] == NUL && pos.col)
--pos.col;
for (;;)
{
initc = PTR2CHAR(linep + pos.col);
if (initc == NUL)
break;
find_mps_values(&initc, &findc, &backwards, FALSE);
if (findc)
break;
pos.col += MB_PTR2LEN(linep + pos.col);
}
if (!findc)
{
/* no brace in the line, maybe use " #if" then */
if (!cpo_match && *skipwhite(linep) == '#')
hash_dir = 1;
else
return NULL;
}
///...
}
关闭除当前窗口外所有其他窗口(ctrl R o)
自觉不自觉、主动或被动的打开了很多窗口,最后想让整个世界清净起来,可以使用该命令让当前窗口成为only窗口。
结论
vim的在线文档简洁、高效、详细、全面,却是是学习vim的最佳文档。vim的命令都是在使用过程中迭代的经验,也只有在使用过程中才能体会到这些它们的作用和命令的强大。
“人类的本质是复读机”,在文件编辑中有大量的重复,好的工具就是如何减少这些重复。进一步说,和所有的压缩算法一样,只要高效的处理了重复,就实现了效率的提升。