首页 > 其他分享 >vim跳转到函数开始([[)和转到声明(gd)的一些实现细节

vim跳转到函数开始([[)和转到声明(gd)的一些实现细节

时间:2024-09-23 21:02:22浏览次数:1  
标签:括弧 cap pos vim cursor gd 跳转 new

intro

在vim的入门介绍中,明确说明了vim是一个"文本编辑器"(text editor)而不是一个程序编辑器,和C/C++的预处理(preprocessor)一样,vim本身并不理解程序的结构。

  1. Introduction intro

Vim stands for Vi IMproved. It used to be Vi IMitation, but there are so many
improvements that a name change was appropriate. Vim is a text editor which
includes almost all the commands from the Unix program "Vi" and a lot of new
ones. It is very useful for editing programs and other plain text.

程序员在使用vim的时候,又不可避免的需要知道程序的结构:复杂的包括基于语义的自动补全,简单的例如跳转到变量定义或者函数的开始。这些功能通常通过一些插件可以完成,但是有些功能在vim中也有内置的实现。尽管不是100%准确,但是大多数情况下亦可使用。这也就是通常所说的“穷人版"(poor man's)功能。

函数开始

常规缩进

在vim的motion.txt文件中,包含了一些函数界别的跳转功能:

]] [count] sections forward or to the next '{' in the
first column. When used after an operator, then also
stops below a '}' in the first column. exclusive
Note that exclusive-linewise often applies.

][ [count] sections forward or to the next '}' in the
first column. exclusive
Note that exclusive-linewise often applies.

[[ [count] sections backward or to the previous '{' in
the first column. exclusive
Note that exclusive-linewise often applies.

[] [count] sections backward or to the previous '}' in
the first column. exclusive
Note that exclusive-linewise often applies.

尽管文档中明确说明了只会搜索位于第一列(first column)的大括弧,对于绝大部分的C语言缩进风格,这种方法都可以很好的跳转到函数的开始/结束:因为大括弧的确是在第一列。

golang

这种大括弧位于第一列在golang中并不满足,并且函数开始的大括弧必须和函数名在同一行,这样这种方法就不再适用。这一点vim文档也做了说明变通的映射方法,本质上就是执行尽可能多次数的(99)前向查找未匹配大括弧。

但是这种实现也有问题:例如C++中,可能这些函数是class的内联函数,这样就找到了类的开始。

The "]]" and "[[" commands stop at the '{' in the first column. This is
useful to find the start of a function in a C program. To search for a '}' in
the first column, the end of a C function, use "][" (forward) or "[]"
(backward). Note that the first character of the command determines the
search direction.

If your '{' or '}' are not in the first column, and you would like to use "[["
and "]]" anyway, try these mappings:
:map [[ ?{w99[{
:map ][ /}b99]}
:map ]] j0[[%/{
:map [] k$][%?}

这个方案中也有一个有意思的地方:为什么执行99[{的时候要先执行字符搜索呢?经过测试可以看到一种情况:如果当前光标在字符串内部,那么此时如果直接执行“[{”会跳过当前行所有的括弧。先搜索下会跳出字符串,当然无法解决字符串有括弧问题的,而且本身意义也不大

int tsecer() {
	{ {  "cursor here" }}
}

java

在vim的说明中,特地强调了java类型的语言,可以通过("[m")[https://vimhelp.org/motion.txt.html#]m]之类的method跳转,那么为什么这种方法对于C++语言就不能适用了呢?

vim对于这个命令的处理位于nv_bracket_block函数。

/*
 * "[{", "[(", "]}" or "])": go to Nth unclosed '{', '(', '}' or ')'
 * "[#", "]#": go to start/end of Nth innermost #if..#endif construct.
 * "[/", "[*", "]/", "]*": go to Nth comment start/end.
 * "[m" or "]m" search for prev/next start of (Java) method.
 * "[M" or "]M" search for prev/next end of (Java) method.
 */
    static void
nv_bracket_block(cmdarg_T *cap, pos_T *old_pos)
{
    pos_T	new_pos = {0, 0, 0};
    pos_T	*pos = NULL;	    // init for GCC
    pos_T	prev_pos;
    long	n;
    int		findc;
    int		c;

    if (cap->nchar == '*')
	cap->nchar = '/';
    prev_pos.lnum = 0;
    if (cap->nchar == 'm' || cap->nchar == 'M')
    {
	if (cap->cmdchar == '[')
	    findc = '{';
	else
	    findc = '}';
	n = 9999;
    }
    else
    {
	findc = cap->nchar;
	n = cap->count1;
    }
    for ( ; n > 0; --n)
    {
	if ((pos = findmatchlimit(cap->oap, findc,
			(cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, 0)) == NULL)
	{
	    if (new_pos.lnum == 0)	// nothing found
	    {
		if (cap->nchar != 'm' && cap->nchar != 'M')
		    clearopbeep(cap->oap);
	    }
	    else
		pos = &new_pos;	// use last one found
	    break;
	}
	prev_pos = new_pos;
	curwin->w_cursor = *pos;
	new_pos = *pos;
    }
    curwin->w_cursor = *old_pos;

    // Handle "[m", "]m", "[M" and "[M".  The findmatchlimit() only
    // brought us to the match for "[m" and "]M" when inside a method.
    // Try finding the '{' or '}' we want to be at.
    // Also repeat for the given count.
    if (cap->nchar == 'm' || cap->nchar == 'M')
    {
	// norm is TRUE for "]M" and "[m"
	int	    norm = ((findc == '{') == (cap->nchar == 'm'));

	n = cap->count1;
	// found a match: we were inside a method
	if (prev_pos.lnum != 0)
	{
	    pos = &prev_pos;
	    curwin->w_cursor = prev_pos;
	    if (norm)
		--n;
	}
	else
	    pos = NULL;
	while (n > 0)
	{
	    for (;;)
	    {
		if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0)
		{
		    // if not found anything, that's an error
		    if (pos == NULL)
			clearopbeep(cap->oap);
		    n = 0;
		    break;
		}
		c = gchar_cursor();
		if (c == '{' || c == '}')
		{
		    // Must have found end/start of class: use it.
		    // Or found the place to be at.
		    if ((c == findc && norm) || (n == 1 && !norm))
		    {
			new_pos = curwin->w_cursor;
			pos = &new_pos;
			n = 0;
		    }
		    // if no match found at all, we started outside of the
		    // class and we're inside now.  Just go on.
		    else if (new_pos.lnum == 0)
		    {
			new_pos = curwin->w_cursor;
			pos = &new_pos;
		    }
		    // found start/end of other method: go to match
		    else if ((pos = findmatchlimit(cap->oap, findc,
			      (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD,
								   0)) == NULL)
			n = 0;
		    else
			curwin->w_cursor = *pos;
		    break;
		}
	    }
	    --n;
	}
	curwin->w_cursor = *old_pos;
	if (pos == NULL && new_pos.lnum != 0)
	    clearopbeep(cap->oap);
    }
    if (pos != NULL)
    {
	setpcmark();
	curwin->w_cursor = *pos;
	curwin->w_set_curswant = TRUE;
#ifdef FEAT_FOLDING
	if ((fdo_flags & FDO_BLOCK) && KeyTyped
		&& cap->oap->op_type == OP_NOP)
	    foldOpenCursor();
#endif
    }
}

在函数中可以看到一个类似的9999常量。这个数值是搜索到最外层的block结构的下一层(代码中的prev_pos保存整个文件级别最外层block的次一个层级)。也就是说:这种方法依赖于method之外有一个最外层的左括弧/右括弧作为定界,从光标位置找到最外层节点的上一层即认为是method的开头。这也意味着:在C++中,如果当前method外面有一个文件最顶层的大括弧,即使不是java这种方法依然有效。

总之,这个方法的实现就是找到“自光标向外的第二层括弧结构”

以下面代码为例,当光标位于第7行时,[m向外的第二层左括弧为第3行;如果没有第一行(class tsecer {)中的左括号,在第7行执行[m则会跳转到第4行,因为它是自内向外的第2层。

 1 class tsecer {
    2 
    3 int harry(){
    4         {
    5             {   
    6                 {
>>  7                     "cursor here" 
    8                 }
    9             }
   10         }
   11     }
>> 12 }
   13    

变量声明

vim内置的跳转到声明的操作是gd/gD。这个文档准确/详细的描述了gd的执行流程:首先是通过'[["找到当前函数的开始(the start of the current function),如果找到则继续后向找到第一个空行,然后从该位置前向搜索第一个遇到的字符串。

这里有一个重要的细节:因为[[要求括弧在第一列,所以这种方法生效的前提是函数开始的左大括弧必须在第一列

  					*gd*   

gd Goto local Declaration. When the cursor is on a local
variable, this command will jump to its declaration.
First Vim searches for the start of the current
function, just like "[[". If it is not found the
search stops in line 1. If it is found, Vim goes back
until a blank line is found. From this position Vim
searches for the keyword under the cursor, like with
"*", but lines that look like a comment are ignored
(see 'comments' option).
Note that this is not guaranteed to work, Vim does not
really check the syntax, it only searches for a match
with the keyword. If included files also need to be
searched use the commands listed in |include-search|.
After this command |n| searches forward for the next
match (not backward).

文档也承认了vim并不会进行语法检查(Vim does not really check the syntax, , it only searches for a match with the keyword."。

以下面代码为例

class tsecer {
int sid;
int harry(int sid){
        {
            sid
        }
    }
}

当光标位于最后一个sid时,执行gd会找到第2行的sid。因为所有的括括弧都不在第一列,所以从文件第一行开始从后向前搜索。

引号的影响

当光标位于下面的引号内部时,执行[{会跳到函数后的左括号。这个从代码(findmatchlimit函数)看,是vim从光标位置向前搜索是,遇到第一个引号时,认为进入了字符串,所以并不会认为前面的两个左括弧匹配了"[{"的左括弧。但是到了第一行,重置了是否在引号的标志,所以认为匹配成功。当光标位于第二行引号外的时候,前向搜索引号数量是匹配的,第二个左括弧不在引号内,所以认为匹配成功。这个不知道是vim的feature还是bug,还是undefined behavior,不过问题不大。

int tsecer() {
	{ {  "cursor here" }}
}

outro

为了让vim内置命令处理起来更简单,最好把函数开始的大括号放在第一列。

标签:括弧,cap,pos,vim,cursor,gd,跳转,new
From: https://www.cnblogs.com/tsecer/p/18427876

相关文章

  • 超链接相关属性:跳转页面、跳转文件、跳转锚点、换成指定应用
    1.跳转页面我这里用绝对网络路径跳转百度、京东说一下img属性值target的含义,值_self是在当前页签跳转,相对的值_blank就是打开新标签跳转注意事项:点击前的超链接字体为蓝色,点击时为红色,点击后的超链接字体为紫色(只限第一次跳转,第一次以后点击前的超链接字体也为紫色,除非刷新......
  • vim 命令失效解决方法 输入命令无反应
     环境是centos7x86 vim显示最新版which命令vim也是有的如题命令都是有的但是无返回结果1.卸载重装yumremovevim-y2.安装yuminstallvim-y3.执行vim 报错撸提示缺库依赖搜索了一下该软件是哪个包提供的yumprovides*libgpm.so.2yum参数-h:显示......
  • linux中vim编辑器的应用实例
    前言Linux有大量的配置文件,其中编辑一些配置文件,最常用的工具就是Vim ,本文介绍一个实际应用的Vim编辑器开发文档的实例。Vim是一个类似于Vi的著名的功能强大、高度可定制的文本编辑器,在Vi的基础上改进和增加了很多特性。Vim是自由软件。Vim可以当作vi的升级版本,它可以用多......
  • [20240920]跟踪library cache lock library cache pin使用gdb.txt
    [20240920]跟踪librarycachelocklibrarycachepin使用gdb.txt--//前一阵子,写的使用gdb跟踪librarycachelocklibrarycachepin的脚本有一个小问题,无法获得lockaddress以及pinaddress--//地址,有一点点小缺陷,尝试修改完善看看。--//按照https://nenadnoveljic.com/blog/tr......
  • vim和gcc
    一.vim1.删除和跳转跳转到指定行88G(命令模式):88(末行模式)跳转文件首gg(命令模式)跳转文件尾G(命令模式)自动格式化程序gg=G(命令模式)大括号对应%(命令模式)光标移至行首0(命令模式)执行结束,工作模式不变光标移至行尾$(命令模式)执行结束,工作模式不变删除单......
  • H5跳转小程序:wx-open-launch-weapp
    项目场景:在移动端,经常要用到H5和小程序交互,如从H5跳转到小程序,又有小程序嵌入H5。今天主要分享的是从H5跳转小程序的使用方法:wx-open-launch-weapp。问题描述1、如何在H5中调用小程序?回答:在公众号里,小程序管理中,绑定对应的小程序,绑定成功后,就拥有跳转到小程序的权限了。2......
  • llm.nvim 支持在neovim中使用kimi
    llm.nvim(https://github.com/Kurama622/llm.nvim)是一个为大型语言模型(LLM)设计的通用插件,旨在使用户能够在neovim中与LLM进行交互。您可以自定义您希望使用的任何LLM(比如智谱清言、kimi、通义千问等)。最后,也是最重要的,您可以使用各种免费模型(无论是由Cloudflare还是其......
  • 在链接与运行地址不同时gdb的调试方法
    搭建一个链接和运行不同的环境SECTIONS{ .=0xffff000000080000, /*.=0x80000,*/ .text.boot:{*(.text.boot)} .text:{*(.text)} .rodata:{*(.rodata)} .....}-s还可以看到符号都链接到高地址去了但是elf文件中有详细的地址信息,如果后续qemu加载......
  • 定时器效果(实现打开一个网页一段时间后自动跳转到另一个网页)(感应灯效果)
    实现目标;打开一个网页一段时间后(自定义)自动跳转到另一个网页。相关要点:定时器window.setTimeout(调用函数,延时时间)(window在调用时可省略)eg:window.setTimeout("window.location='https://id5.163.com/index.html'",1000);注:此语句要写在js文件或<script></script>中。即打开......
  • 题解 GD230531C【眺望】
    题目描述有\(n\)座灯塔排成一排,第\(i\)座灯塔的高度是\(a_i\)。你需要求出有多少对\(l<r\)满足\(a_l=a_r\),且\(\foralll<i<r,a_i<a_l\)。灯塔的高度有\(Q\)次修改,每次给定\(x,h\),表示将\(a_x\)修改为\(h\)。求出修改之前和每次修改之后的答案。\(n......