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;
}

上一篇:prometheus grafana sql 常用函数参数


下一篇:2021-07-08