为什么vim编辑模式下ctrl-w可以前向删除单词及按键映射的展开
一、问题
在vim的编辑模式下,我之前一直以为只能进行字符的插入操作,但是意外看到可以在编辑模式下通过ctrl-w来前向删除一个单词,并且可以通过ctrl-h来前向删除一个字符。根据通常的ASCII码内置控制方法,通过ctrl-h对应的是ASCII码的BS(backspace)字符,所以通过ctrl-h前向删除单个字符在逻辑上是可以理解的。但是,ctrl-w对应的ASCII码却是ETB (end of trans. blk),这个明显不是标准的前向删除一个单词的意思,所以它是vim内部完成的一个功能。那么vim具体是通过什么方式来完成的呢?是否是内置的标准用法,还是通过编辑器定制的方法?
二、vim读到终端输入是什么
这一点很容易确认,通过strace可以看到,当在vim下按下ctrl-w按键时,此时vim通过read读到的就是转换之后的23这个输入。
select(1, [0], [], [0], {0, 0}) = 0 (Timeout)
select(1, [0], [], [0], NULL) = 1 (in [0])
read(0, "\27", 4096) = 1
三、vim对终端的配置格式
通过对比可以看到,vim使用的终端比标准终端添加了下面几个选项
-icrnl
-onlcr
-isig -icanon -iexten -echo -echoe
为了便于查看,将这些字段的意义整理罗列下
[-]icrnl
translate carriage return to newline
* [-]onlcr
translate newline to carriage return-newline
[-]isig
enable interrupt, quit, and suspend special characters
[-]icanon
enable erase, kill, werase, and rprnt special characters
[-]iexten
enable non-POSIX special characters
[-]echo
echo input characters
[-]echoe
same as [-]crterase
[-]crterase
echo erase characters as backspace-space-backspace
这里比较关键的就是其实是关闭了icanon选项,所以终端默认的erase、kill、werase内置功能是被禁用的,也就是说,通过ctrl-w删除单词不是终端完成而是有vim自己完成的。
#下面是标准终端的配置
tsecer@harry: stty
speed 38400 baud; line = 0;
-brkint -imaxbel
#下面是vim使用的终端的配置
tsecer@harry: stty -F /dev/pts/3
speed 38400 baud; line = 0;
min = 1; time = 0;
-brkint -icrnl -imaxbel
-onlcr
-isig -icanon -iexten -echo -echoe
四、vim如何完成这个功能
通过查看Vim的源代码,可以知道这个功能是vim的内置功能。
edit.c
int
edit(
int cmdchar,
int startln, /* if set, insert at start of line */
long count)
{
……
case Ctrl_W: /* delete word before the cursor */
#ifdef FEAT_JOB_CHANNEL
if (bt_prompt(curbuf) && (mod_mask & MOD_MASK_SHIFT) == 0)
{
// In a prompt window CTRL-W is used for window commands.
// Use Shift-CTRL-W to delete a word.
stuffcharReadbuff(Ctrl_W);
restart_edit = 'A';
nomove = TRUE;
count = 0;
goto doESCkey;
}
#endif
did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space);
auto_format(FALSE, TRUE);
break;
case Ctrl_U: /* delete all inserted text in current line */
# ifdef FEAT_COMPL_FUNC
/* CTRL-X CTRL-U completes with 'completefunc'. */
if (ctrl_x_mode == CTRL_X_FUNCTION)
goto docomplete;
# endif
did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space);
auto_format(FALSE, TRUE);
inserted_space = FALSE;
break;
……
}
vim中可以通过help index查看所有的内置(按键)功能。其中可以找到关于这个ctrl-w的说明
i_CTRL-U CTRL-U delete all entered characters in the current
line
i_CTRL-V CTRL-V {char} insert next non-digit literally
i_CTRL-V_digit CTRL-V {number} insert three digit decimal number as a single
byte.
i_CTRL-W CTRL-W delete word before the cursor
五、map功能
除了这些内置功能之外,vim和包含了可以定制的键盘映射功能。可以通过imap、vmap、nmap来查看在isert、visual、normal模式下的键盘映射。
在这些map中,可能需要有一些特定的按键,最为常见的就是配合Ctrl按键。所有这些内容可以通过在vim中执行help keycode命令查看:
<k0> - <k9> keypad 0 to 9 *keypad-0* *keypad-9*
<S-...> shift-key *shift* *<S-*
<C-...> control-key *control* *ctrl* *<C-*
<M-...> alt-key or meta-key *meta* *alt* *<M-*
<A-...> same as <M-...> *<A-*
<D-...> command-key (Macintosh only) *<D-*
<t_xx> key with "xx" entry in termcap
这里看到,对于修饰符都是单个字符开始的,也就是Ctrl通过C-表示(而不是Ctrl-)表示,通常的Ctrl-W是vim文档中使用的表示方法,它通常也表示在键盘上按下Ctrl的同时按下W键。例如,当希望查找在命令模式下,ctrl-r ctrl-w代表的什么意义,可以通过
:h c_ctrl-r_ctrl-w
查找,当然,使用c_<C-R>_<C-W>也可以,只是使用ctrl-R比输入<C-r>更简单一些。从这个地方也可以看出,vim中通过通过下划线"_"表示空白,而"-"表示连接/连续。
CTRL-R CTRL-F c_CTRL-R_CTRL-F c_<C-R>_<C-F>
CTRL-R CTRL-P c_CTRL-R_CTRL-P c_<C-R>_<C-P>
CTRL-R CTRL-W c_CTRL-R_CTRL-W c_<C-R>_<C-W>
CTRL-R CTRL-A c_CTRL-R_CTRL-A c_<C-R>_<C-A>
CTRL-R CTRL-L c_CTRL-R_CTRL-L c_<C-R>_<C-L>
Insert the object under the cursor:
CTRL-F the Filename under the cursor
CTRL-P the Filename under the cursor, expanded with
'path' as in gf
CTRL-W the Word under the cursor
CTRL-A the WORD under the cursor; see WORD
CTRL-L the line under the cursor
六、vim中map的展开
src\getchar.c
static int
vgetorpeek(int advance)
{
……
/*
* Handle ":map <expr>": evaluate the {rhs} as an
* expression. Also save and restore the command line
* for "normal :".
*/
if (mp->m_expr)
{
int save_vgetc_busy = vgetc_busy;
vgetc_busy = 0;
save_m_keys = vim_strsave(mp->m_keys);
save_m_str = vim_strsave(mp->m_str);
s = eval_map_expr(save_m_str, NUL);
vgetc_busy = save_vgetc_busy;
}
else
#endif
s = mp->m_str;
……
i = ins_typebuf(s, noremap,
0, TRUE, cmd_silent || save_m_silent);
……
}
结构的定义
structs.h
/*
* Structure used for mappings and abbreviations.
*/
typedef struct mapblock mapblock_T;
struct mapblock
{
mapblock_T *m_next; /* next mapblock in list */
char_u *m_keys; /* mapped from, lhs */
char_u *m_str; /* mapped to, rhs */
char_u *m_orig_str; /* rhs as entered by the user */
int m_keylen; /* strlen(m_keys) */
int m_mode; /* valid mode */
int m_noremap; /* if non-zero no re-mapping for m_str */
char m_silent; /* <silent> used, don't echo commands */
char m_nowait; /* <nowait> used */
#ifdef FEAT_EVAL
char m_expr; /* <expr> used, m_str is an expression */
scid_T m_script_ID; /* ID of script where map was defined */
#endif
};