Vim中如何移动光标

Posted tsecer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vim中如何移动光标相关的知识,希望对你有一定的参考价值。

一、问题

明显的,在normal模式下,通过hjkl四个按键进行移动,但是之类的问题是vim如何移动光标而不是用户怎么移动光标。在bash界面中,我们通过通过方向键来移动光标位置。在vim中,vim是完全控制了当前终端,假设你获得了终端的控制权,你将如何控制光标在整个终端的任意位置进行移动呢?

二、通过strace看光标移动时系统调用

其中的32200进程是一个vim进程,当在vim中执行一次移动(按下j向下移动)时,通过strace可以看到vim向终端写入了大量眼花缭乱的、令人晕眩的字符串输出。
tsecer@harry: strace -s 1000 -e write -p 32200
Process 32200 attached
write(1, "\\33[?25l\\33[m\\33[48;5;234m\\33[60;183Hj\\33[24;12H", 38) = 38
write(1, "\\33[60;183H \\33[25;9H\\33[1;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mfile\\33[m\\33[48;5;234m \\33[2;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mtext\\33[m\\33[48;5;234m \\33[3;9H\\33[1m\\33[38;5;161m\\33[48;5;236mx\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mt0\\33[m\\33[48;5;234m:\\33[4;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mglobl\\33[m\\33[48;5;234m \\33[5;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mtype\\33[m\\33[48;5;234m \\33[6;9H\\33[38;5;208m\\33[48;5;236mo\\33[m\\33[48;5;234m\\33[2C:\\33[7;9H\\33[1m\\33[38;5;161m\\33[48;5;236m0\\33[m\\33[48;5;234m: \\33[8;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mfile\\33[m\\33[48;5;234m \\33[9;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mloc\\33[m\\33[48;5;234m \\33[10;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mcfi_startproc\\33[m\\33[48;5;234m \\33[11;9H\\33[38;5;208m\\33[48;5;236mp\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208mh\\33[m\\33[48;5;234m\\33[12;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mcfi_def_cfa_offset\\33[m\\33[48;5;234m \\33[13;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;"..., 2031) = 2031
write(1, "\\33[1m\\33[38;5;161m\\33[48;5;235m.LC0\\33[m\\33[48;5;234m\\33[48;5;235m: \\33[1C \\33[m\\33[48;5;234m\\33[26;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mstring\\33[m\\33[48;5;234m \\33[27;9H\\33[48;5;236m:\\33[m\\33[48;5;234m \\33[28;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mstring\\33[m\\33[48;5;234m \\33[29;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mtext\\33[m\\33[48;5;234m \\33[30;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mglobl\\33[m\\33[48;5;234m \\33[31;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mtype\\33[m\\33[48;5;234m \\33[32;9H\\33[48;5;236m:\\33[m\\33[48;5;234m \\33[33;9H\\33[1m\\33[38;5;161m\\33[48;5;236m1\\33[m\\33[48;5;234m: \\33[34;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mloc\\33[m\\33[48;5;234m \\33[35;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mcfi_startproc\\33[m\\33[48;5;234m \\33[36;9H\\33[38;5;208m\\33[48;5;236mp\\33[m"..., 2033) = 2033
write(1, "\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[50;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mloc\\33[m\\33[48;5;234m \\33[51;9H\\33[38;5;208m\\33[48;5;236mm\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[52;9H\\33[38;5;208m\\33[48;5;236mm\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[53;9H\\33[38;5;208m\\33[48;5;236mm\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[54;9H\\33[38;5;208m\\33[48;5;236mc\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[55;9H\\33[1m\\33[38;5;161m\\33[48;5;236m.\\33[m\\33[48;5;234m\\33[1m\\33[38;5;161mloc\\33[m\\33[48;5;234m \\33[56;9H\\33[38;5;208m\\33[48;5;236mm\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[57;9H\\33[38;5;208m\\33[48;5;236mm\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[58;9H\\33[38;5;208m\\33[48;5;236mm\\33[m\\33[48;5;234m\\33[2C\\33[38;5;208ml\\33[m\\33[48;5;234m\\33[59;186H\\33[1m\\33[38;5;232m\\33[48;5;144m5\\33[m\\33[48;5;234m\\33[38;5;232m\\33[48;5;144m:\\33[25;9H\\33[?25h", 810) = 810

三、这些字符串的意义

这些其实是早期终端定义的一些转义字符串序列,也就是通过特殊的序列表示对终端的控制(而不是字符串本身)。在vim输出中,比较明显的就是"\\33[",这个就是文档中说明的CSI (Control Sequence Introducer) sequences。对于数字类型的参数,通过分号(";")分割。
其中最常见的就是H未设置光标位置,对应的描述为
CSI n ; m H
CUP
Cursor Position Moves the cursor to row n, column m. The values are 1-based, and default to 1 (top left corner) if omitted. A sequence such as CSI ;5H is a synonym for CSI 1;5H as well as CSI 17;H is the same as CSI 17H and CSI 17;1H
另一个m对应的意义为设置颜色
CSI n m
SGR
Select Graphic Rendition Sets the appearance of the following characters.

四、putty中对这些代码的处理

提醒一下,这些所谓的转义内容并不是由操作系统(驱动)处理的,而是由终端处理的。由于现在已经很难找到(也没必要)实体的终端,所以这些控制序列是由终端模拟器(SecureCRT、putty)来完成。在putty的代码中,我们可以看到对这些序列的处理流程。

putty-0.75\\terminal.h
enum {
TOPLEVEL,
SEEN_ESC,
SEEN_CSI,
SEEN_OSC,
SEEN_OSC_W,

DO_CTRLS,

SEEN_OSC_P,
OSC_STRING, OSC_MAYBE_ST,
VT52_ESC,
VT52_Y1,
VT52_Y2,
VT52_FG,
VT52_BG
} termstate;

代码可以看到,当遇到分号(;)时会增加参数的数量,在遇到ESC之后遇到([)进入CSI状态
putty-0.75\\terminal.c
/*
* Remove everything currently in `inbuf\' and stick it up on the
* in-memory display. There\'s a big state machine in here to
* process escape sequences...
*/
static void term_out(Terminal *term)
{
……
term->termstate = TOPLEVEL;
switch (ANSI(c, term->esc_query)) {
case \'[\': /* enter CSI mode */
term->termstate = SEEN_CSI;
term->esc_nargs = 1;
term->esc_args[0] = ARG_DEFAULT;
term->esc_query = 0;
break;
……
case SEEN_CSI:
term->termstate = TOPLEVEL; /* default */
if (isdigit(c)) {
if (term->esc_nargs <= ARGS_MAX) {
if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT)
term->esc_args[term->esc_nargs - 1] = 0;
if (term->esc_args[term->esc_nargs - 1] <=
UINT_MAX / 10 &&
term->esc_args[term->esc_nargs - 1] * 10 <=
UINT_MAX - c - \'0\')
term->esc_args[term->esc_nargs - 1] =
10 * term->esc_args[term->esc_nargs - 1] +
c - \'0\';
else
term->esc_args[term->esc_nargs - 1] = UINT_MAX;
}
term->termstate = SEEN_CSI;
}else if (c == \';\') {
if (term->esc_nargs < ARGS_MAX)
term->esc_args[term->esc_nargs++] = ARG_DEFAULT;
term->termstate = SEEN_CSI;
}
……
} else
#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
switch (ANSI(c, term->esc_query)) {
case \'A\': /* CUU: move up N lines */
CLAMP(term->esc_args[0], term->rows);
move(term, term->curs.x,
term->curs.y - def(term->esc_args[0], 1), 1);
seen_disp_event(term);
break;
case \'e\': /* VPR: move down N lines */
compatibility(ANSI);
/* FALLTHROUGH */
case \'B\': /* CUD: Cursor down */
CLAMP(term->esc_args[0], term->rows);
move(term, term->curs.x,
term->curs.y + def(term->esc_args[0], 1), 1);
seen_disp_event(term);
break;
……
case \'H\': /* CUP */
case \'f\': /* HVP: set horz and vert posns at once */
if (term->esc_nargs < 2)
term->esc_args[1] = ARG_DEFAULT;
CLAMP(term->esc_args[0], term->rows);
CLAMP(term->esc_args[1], term->cols);
move(term, def(term->esc_args[1], 1) - 1,
((term->dec_om ? term->marg_t : 0) +
def(term->esc_args[0], 1) - 1),
(term->dec_om ? 2 : 0));
seen_disp_event(term);
break;
……
}


五、通过程序测试下转移的效果

是不是感觉有一种通过字符串脚本来编程控制终端的感觉?:)
tsecer@harry: cat term.esc.cpp
#include <unistd.h>
#include <stdio.h>

int main(int argc, const char *argv[])
{
for (int lin = 0; lin < 200; lin++)
{
for(int row = 0; row < 200; row++)
{
char buff[100] = {};
int icount = snprintf(buff, sizeof buff, "\\33[%d;%dH", lin, row);
write(1, buff, icount);
usleep(10000);
}
}
return 0;
}

以上是关于Vim中如何移动光标的主要内容,如果未能解决你的问题,请参考以下文章

Vim光标移动命令汇总

如何防止光标在离开 Vim 的插入模式时向后移动一个字符?

vim中,将光标移至第60行,并且删除行尾的15个字符,如何执行命令?

vim 光标移动到屏幕下一行

5.1 vim介绍 5.2 vim颜色显示和移动光标 5.3 vim一般模式下移动光标 5.4 vi

VIM实用指南光标移动大法汇总