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的命令都是在使用过程中迭代的经验,也只有在使用过程中才能体会到这些它们的作用和命令的强大。

“人类的本质是复读机”,在文件编辑中有大量的重复,好的工具就是如何减少这些重复。进一步说,和所有的压缩算法一样,只要高效的处理了重复,就实现了效率的提升。

posted on 2023-03-18 15:42  tsecer  阅读(35)  评论(0编辑  收藏  举报

导航