首页 > 其他分享 >vim内置erm的使用及实现

vim内置erm的使用及实现

时间:2024-07-12 20:31:17浏览次数:16  
标签:term 内置 int vim terminal job erm loop

intro

在使用vim的时候,一个非常常用的功能就是搜索功能。

搜索

在缺少工程级别搜索的情况下,搜索通常不是一次完成的:通常的场景是提供一个最明显的关键字(并且忽略大小写),然后从结果中再缩小搜索范围,直到找到搜索结果。在这个迭代的过程中,可能需要从上次的输出中拷贝一部分、简单编辑之后再次搜索。这拷贝/编辑功能是vim的强项。如果能将命令的输出不断拷贝/粘贴/编辑是一个很好的功能。

耗时命令

在功能验证时,需要不断的构建和测试,这两个可能都是耗时比较长的操作,如果在vim中直接执行就不能执行vim操作,例如浏览代码。

另外,构建错误可能会输出错误文件的名字和行号,这就免不了需要打开这些文件。如果这些文件在一个vim的buffer中,直接通过gf就可以打开文件。

指令序列

通过git提交代码的时候,经常需要先执行git status查看修改了哪些文件,通过git diff查看修改的内容是会否符合预期,然后git add,git commit。

这样每次执行完之后再切换回vim跟平时的操作流并不一致,而程序员都是不喜欢这种不连续的切换状态。

vim提供的term功能,正好可以解决这些问题:启动一个终端,可有执行命令,并且可以进入多Normal模式下使用vim的编辑器功能。当然,前面的问题都不是只能使用term解决(通常也可以在cmd中启动bash来实现),只是通过这种方法解决看起来更加流畅和自然。

这个term功能尽管看起来并不太常用,但是这个功能在vim的源代码中出镜率还很高:在src文件夹下有专门的libvterm文件夹,这算是vim源代码中依赖的一个不太常规的外部库(readline算是比较常用的)。

在vim的主循环函数main_loop中

/*
 * Main loop: Execute Normal mode commands until exiting Vim.
 * Also used to handle commands in the command-line window, until the window
 * is closed.
 * Also used to handle ":visual" command after ":global": execute Normal mode
 * commands, return when entering Ex mode.  "noexmode" is TRUE then.
 */
    void
main_loop(
    int		cmdwin,	    // TRUE when working in the command-line window
    int		noexmode)   // TRUE when return on entering Ex mode
{
///...
	/*
	 * If we're invoked as ex, do a round of ex commands.
	 * Otherwise, get and execute a normal mode command.
	 */
	if (exmode_active)
	{
	    if (noexmode)   // End of ":global/path/visual" commands
		goto theend;
	    do_exmode(exmode_active == EXMODE_VIM);
	}
	else
	{
#ifdef FEAT_TERMINAL
	    if (term_use_loop()
		    && oa.op_type == OP_NOP && oa.regname == NUL
		    && !VIsual_active
		    && !skip_term_loop)
	    {
		// If terminal_loop() returns OK we got a key that is handled
		// in Normal mode.  With FAIL we first need to position the
		// cursor and the screen needs to be redrawn.
		if (terminal_loop(TRUE) == OK)
		    normal_cmd(&oa, TRUE);
	    }
	    else
#endif
	    {
#ifdef FEAT_TERMINAL
		skip_term_loop = FALSE;
#endif
		normal_cmd(&oa, TRUE);
	    }
	}
///...
"

libvterm

direction

一个直觉的疑问是:vim本身不就是在处理终端吗,为什么还需要一个vterm?这个其实就是term的两个方面:当vim作为一个编辑器时,它是一个term的使用者,只需要按照需求向终端输出控制命令(例如移动光标),而在vim中执行term命令时则是作为终端的输出方。这意味着需要解析类似移动光标/切换背景颜色之类的控制命令序列,

这一点并不是vim的主要功能,所以需要引入一个专门解析并执行终端控制命令的功能库。这个功能库接收并执行终端命令,并将处理之后的文字存储在特定位置,这样vim就可以直接使用处理后的内容,只需要将结果显示到buffer中即可。这里可以大哥不太恰当的比喻:libvterm就是一个电脑,可以将输入转换为屏幕像素信息,而vim作为一个显示器只需要按照像素信息显示即可。这也是真实/物理终端的功能:接收控制序列并展示在终端屏幕上。只是libvterm的作用是接收控制数列,将结果以数据结构的形式保存在内存中(可以供业务进程来取度)。

功能

  • 回调

当使用libvterm创建一个终端时,需要提供vterm相关回调,其中最关键的是handle_damage,用来处理输出内容的变化。


static VTermScreenCallbacks screen_callbacks = {
  handle_damage,	// damage
  handle_moverect,	// moverect
  handle_movecursor,	// movecursor
  handle_settermprop,	// settermprop
  handle_bell,		// bell
  handle_resize,	// resize
  handle_pushline,	// sb_pushline
  NULL,			// sb_popline
  NULL			// sb_clear
};
  • 变化内容

libvterm提供了获取屏幕上每个cell信息的接口。

/*
 * Fill one screen line from a line of the terminal.
 * Advances "pos" to past the last column.
 */
    static void
term_line2screenline(
	term_T		*term,
	win_T		*wp,
	VTermScreen	*screen,
	VTermPos	*pos,
	int		max_col)
{
    int off = screen_get_current_line_off();

    for (pos->col = 0; pos->col < max_col; )
    {
	VTermScreenCell cell;
	int		c;

	if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
	    CLEAR_FIELD(cell);
///...

获取到的cell包括了字符内容(例如汉字的utf-8),文字及背景颜色,是否斜体/斜体/下划线等。有了这些信息,vim只负责展示即可。

typedef struct {
  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
  char     width;
  VTermScreenCellAttrs attrs;
  VTermColor fg, bg;
} VTermScreenCell;

typedef struct {
    unsigned int bold      : 1;
    unsigned int underline : 2;
    unsigned int italic    : 1;
    unsigned int blink     : 1;
    unsigned int reverse   : 1;
    unsigned int conceal   : 1;
    unsigned int strike    : 1;
    unsigned int font      : 4; /* 0 to 9 */
    unsigned int dwl       : 1; /* On a DECDWL or DECDHL line */
    unsigned int dhl       : 2; /* On a DECDHL line (1=top 2=bottom) */
    unsigned int small     : 1;
    unsigned int baseline  : 2;
} VTermScreenCellAttrs;
// VIM: this was a union, but that doesn't always work.
typedef struct {
  /**
   * Tag indicating which member is actually valid.
   * Please use the `VTERM_COLOR_IS_*` test macros to check whether a
   * particular type flag is set.
   */
  uint8_t type;

  uint8_t red, green, blue;

  uint8_t index;
} VTermColor;

伪终端

在terminal的帮助文档中,有这么一段话

On Unix a pty is used to make it possible to run all kinds of commands. You
can even run Vim in the terminal! That's used for debugging, see below.

这里的伪终端(pty)有事什么用的呢?这个其实理解起来相对简单一些:它是libvterm的输入。在典型的运行shell的终端中,这些控制序列由bash生成,当然也可以由vim生成,或者任何进程向自己标准输出打印生成。

这就是文档里说的“You can even run Vim in the terminal!”(甚至还加了个惊叹号)的原因。

按键处理

谁处理输入

在vim的主循环中,会判断当前活跃的buffer是不是一个终端对应的buffer。全局变量curbuf保存当前(光标所在)buffer,而每个buffer结构中的b_term保存终端指针,如果buffer是由终端生成,则会指向对应终端对象。

 * Returns TRUE if the current window contains a terminal and we are sending
 * keys to the job.
 * If "check_job_status" is TRUE update the job status.
 */
    static int
term_use_loop_check(int check_job_status)
{
    term_T *term = curbuf->b_term;

    return term != NULL
	&& !term->tl_normal_mode
	&& term->tl_vterm != NULL
	&& term_job_running_check(term, check_job_status);
}

/*
 * Returns TRUE if the current window contains a terminal and we are sending
 * keys to the job.
 */
    int
term_use_loop(void)
{
    return term_use_loop_check(FALSE);
}

此时,主流程执行转移到terminal_loop函数,并继续的等待/读取/处理用户的按键输入。

    int
terminal_loop(int blocking)
{
    while (blocking || vpeekc_nomap() != NUL)
    {
    ///...
	raw_c = term_vgetc();
    ///...
}
}

特殊按键

在term使用中,真正需要感兴趣的是哪些按键是特殊的。

从代码上看,在终端中真正有特殊意义的就是 ctrl-w(或者其他配置的termkey)或者ctrl-backslash引导的序列。由于ctrl-w刚好和bash的前向删除单个word冲突,所以这个需要注意下。从代码(和文档)可以看到,在terminal中通过 Ctrl -w .组合按键来向job发送Ctrl-W,并且还可以通过Ctrl-w “访问寄存器内容。

    int
terminal_loop(int blocking)
{
///...
	// Was either CTRL-W (termwinkey) or CTRL-\ pressed?
	// Not in a system terminal.
	if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
#ifdef FEAT_GUI
		&& !curbuf->b_term->tl_system
#endif
		)
	{
	    int	    prev_c = c;
	    int	    prev_raw_c = raw_c;
	    int	    prev_mod_mask = mod_mask;

	    if (add_to_showcmd(c))
		out_flush();

	    raw_c = term_vgetc();
	    c = raw_c_to_ctrl(raw_c);

	    clear_showcmd();

	    if (!term_use_loop_check(TRUE)
					 || in_terminal_loop != curbuf->b_term)
		// job finished while waiting for a character
		break;

	    if (prev_c == Ctrl_BSL)
	    {
		if (c == Ctrl_N)
		{
		    // CTRL-\ CTRL-N : go to Terminal-Normal mode.
		    term_enter_normal_mode();
		    ret = FAIL;
		    goto theend;
		}
		// Send both keys to the terminal, first one here, second one
		// below.
		send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
									 TRUE);
	    }
	    else if (c == Ctrl_C)
	    {
		// "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job
		mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
	    }
	    else if (c == '.')
	    {
		// "CTRL-W .": send CTRL-W to the job
		// "'termwinkey' .": send 'termwinkey' to the job
		raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
	    }
	    else if (c == Ctrl_BSL)
	    {
		// "CTRL-W CTRL-\": send CTRL-\ to the job
		raw_c = ctrl_to_raw_c(Ctrl_BSL);
	    }
	    else if (c == 'N')
	    {
		// CTRL-W N : go to Terminal-Normal mode.
		term_enter_normal_mode();
		ret = FAIL;
		goto theend;
	    }
	    else if (c == '"')
	    {
		term_paste_register(prev_c);
		continue;
	    }
	    else if (termwinkey == 0 || c != termwinkey)
	    {
		// space for CTRL-W, modifier, multi-byte char and NUL
		char_u buf[1 + 3 + MB_MAXBYTES + 1];

		// Put the command into the typeahead buffer, when using the
		// stuff buffer KeyStuffed is set and 'langmap' won't be used.
		buf[0] = Ctrl_W;
		buf[special_to_buf(c, mod_mask, FALSE, buf + 1) + 1] = NUL;
		ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
		ret = OK;
		goto theend;
	    }
	}
///...
}

outro

尽管很多人都不会用到vim的term功能,但是这些功能的代码在vim中却是实实在在存在的,并且影响了vim的代码结构和流程。了解这些功能更便于理解vim这个整体及其它相关模块的功能。

vim作虽然是一个无处不在的、徒手可得的编辑器,但实现并不简单。由于vim的大部分功能都是基于常见功能、基础设备实现,所以涉及到了不少设备的本质和用法,而且有不少优秀的设计/实现思路可以借鉴。

标签:term,内置,int,vim,terminal,job,erm,loop
From: https://www.cnblogs.com/tsecer/p/18299331

相关文章

  • vim命令总结
    vim命令1、touch创建文件2、vim或vi编辑文件3、vim文件名4、vim编辑器共分为三种模式:(1)命令模式esc或ctrl+c(2)编辑模式按i键(3)底层命令模式先进入命令模式=shift+:=输入命令5、快捷键(1)enter键换行(2)backspce退格键,删除光标前一......
  • python 内置高級函數盤點
    1. map(function,iterable,...)map()函数接受一个函数和一个可迭代对象作为参数,将函数应用于可迭代对象的每个元素,并返回一个包含结果的迭代器#将列表中的每个元素加1numbers=[1,2,3,4,5]result=map(lambdax:x+1,numbers)print(list(result))#输出[2,3......
  • IOC 内置容器的使用
    //nuget安装:Microsoft.Extensions.DependencyInjection//ServiceCollection的生命周期//AddTransient瞬时生命周期,每次创建都是一个全新的实例//AddSingleton单列生命周期,同一个类型创建出来的是同一个实例//AddScoped作用域生命周期,同一个services获取到的是用一个......
  • Day33.内置方法
    1.内置方法_自定义类和内置方法的输出2.内置方法_方法__str__需要返回一个字符串3.内置方法_方法__str__返回对象数据4.内置方法_方法__del__未清理对象先执行类外的程序,然后再执行类下的__del__方法5.内置方法_方法__del__清理对象之后,先执行类下的__del__方法6.内置方......
  • [Tools] VIm cheat sheet
    vimcheatsheetKeepthishandyasyouexperimentwithvim:http://www.fprintf.net/vimCheatSheet.htmlHereisanotherguidethatcoversthecommandsincrementally:http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ movingaround-hjkl......
  • Lingo学习(三)——工厂合并、运算符、内置函数
    一、工厂合并(一)工厂合并——生产二维矩阵【引入】sets:                                factory/1..6/:a;                  plant/1..8/:d;                    Cooperation(fact......
  • ros环境搭建及vim-plug插件安装
    1.Ubutun安装20.04(原因:ros环境搭建教程及报错解决方法参考资料最多);2.Ubutun20.04ros环境搭建及安装包参考教程:【安装】Ubuntu20.04下安装ROS的完整过程(内含已装好ROS的虚拟机、虚拟机创建过程、ROS安装过程及全过程录屏)_ros虚拟机现成的-CSDN博客(感谢无私作出奉献的前辈们);3.ros......
  • 理解 Linux 文件权限(2)& vim编辑器
    1、如何理解文件权限1)查看文件• 想要理解文件权限,需要先从查看文件入手•使用ls–l命令查看Linux系统上的文件、目录和设备的权限①对象的类型②文件属性③目录/链接个数④所有者(owner)⑤组(group)⑥文件大小⑦最后修改的日期⑧文件名其中:• ①代表了对象的类型:......
  • E. Permutation Sorting
    原题链接题解对于数\(i\)来说,如果其当前位置到最终位置上,有\(k\)个数不在最终位置,那么数\(i\)至少要走\(k\)次如果这\(k\)个数里,有\(m\)个在数\(i\)回到最终位置时,提前回到了最终位置,那么数\(i\)要走\(k-m\)次具象化就是一个一个的区间(起点为当前位置,终点......
  • Nuxt框架中内置组件详解及使用指南(五)
    title:Nuxt框架中内置组件详解及使用指南(五)date:2024/7/10updated:2024/7/10author:cmdragonexcerpt:摘要:本文详细介绍了Nuxt框架中和组件的使用方法与配置,包括安装、基本用法、属性详解、示例代码以及高级功能如事件处理、自定义图片属性和图片格式回退策略。同时,还......