vim中从复制粘贴到文件类型识别及语法高亮
一、拷贝/粘贴
拷贝粘贴可能是计算机中最重要的一个操作,该操作发明者“Larry Tesler”于2020年逝世,但是这个操作的提出者还是因为这个操作被缅怀和记忆。在常规模式(normal mode)下,大家都知道通过yank+paste进行操作,但是有时候在命令模式(command mode)下也有这种拷贝粘贴的需求。典型的场景是要进行文本替换的时候,要把当前已经存在的单词替换为另一个单词,这个时候如果要把这个命令拷贝再粘贴过来就有点麻烦了,最好有一个额外的操作来完成这个功能。
二、命令
通过搜索可以知道这个功能是通过“ctrl-r ctrl-w”来将文本区光标下单词复制粘贴到命令行,这个不是重点,重点是在看这个命令的帮助时可以看到,这个ctrl-r引导的是一组寄存器操作,这个r代表Register的意图非常明显。但是在这个命令后,还可以看到其中有其它的操作,并且不是很理解它们的意思。例如在这个空格的后面CTRL-P、CTRL-A等:
|c_CTRL-R_CTRL-R| CTRL-R CTRL-R {0-9a-z"%#*:= CTRL-F CTRL-P CTRL-W CTRL-A}
insert the contents of a register or object
under the cursor literally
三、help help的说明
通过help help,可以知道其中最为关键的有下面几条:
1、如果定义一个tag,将这个tag放在一对星号之间
2、如果引用一个已经存在的tag,将它们放在一对竖线之间
3、如果引用一个vim命令,可以将它们放在一对反引号之间
4、如果引用一个vim选项,可以将它们放在一对单引号之间
这里定义它们的意义在于可以使用vim内置的tag功能进行跳转,也就是当把光标放在这些特殊字符上之后,通过tag的快捷键“ctrl+]”来跳转到对应的定义,从而可以实现交叉引用。
TAGS
To define a help tag, place the name between asterisks (*tag-name*). The
tag-name should be different from all the Vim help tag names and ideally
should begin with the name of the Vim plugin. The tag name is usually right
aligned on a line.
When referring to an existing help tag and to create a hot-link, place the
name between two bars (|) eg. help-writing.
When referring to a Vim command and to create a hot-link, place the
name between two backticks, eg. inside :filetype. You will see this is
highlighted as a command, like a code block (see below).
When referring to a Vim option in the help file, place the option name between
two single quotes, eg. 'statusline'
四、vim如何高亮
对于文件中不同单词和片段如何高亮,是通过vim发行版本中自带的syntax文件完成。例如,在vim源代码的runtime\syntax文件夹下可以看到help.vim文件,其中对这些特殊语法的识别:
syn match helpBar contained "|"
syn match helpBacktick contained "`"
syn match helpStar contained "\*"
……
syn match helpOption "'[a-z]\{2,\}'"
syn match helpOption "'t_..'"
其对应的正则表达式描述了help文档中对于这些语法规则的描述。
当然,其中还可以看到一些其它的描述
syn match helpSpecial "CTRL-."
syn match helpSpecial "CTRL-Break"
syn match helpSpecial "CTRL-PageUp"
syn match helpSpecial "CTRL-PageDown"
syn match helpSpecial "CTRL-Insert"
syn match helpSpecial "CTRL-Del"
syn match helpSpecial "CTRL-{char}"
……
hi def link helpBar Ignore
hi def link helpBacktick Ignore
hi def link helpStar Ignore
它们通常可能并一定不对应tag,但是对于vim来说有特殊意义。
vim的帮助文件中,执行
set ft?
可以看到输出格式为
filetype=help
说明文件类型为vim的help文件格式。
其中看到,对于helpBacktick都是设置语法类型为Ignore,也就是在vim的help模式下这些字符是不可见的。可以通过
set ft=txt
将原始的文件内容显示出来,可以看到它们显示的原始形势。
五、如何识别文件类型
对于vim的help文件,虽然它的后缀是.txt格式,但是它却是vim特有的help文件格式,那么vim是如何知道这个是一个特殊的vim的help格式呢?也就是这里比语法解析更原始的概念是识别出一个文件是什么语法格式?
同样是在vim源代码的runtime\filetype.vim文件中,可以看到对于文件类型的识别:
" Cynlib
" .cc and .cpp files can be C++ or Cynlib.
au BufNewFile,BufRead *.cc
\ if exists("cynlib_syntax_for_cc")|setf cynlib|else|setf cpp|endif
au BufNewFile,BufRead *.cpp
\ if exists("cynlib_syntax_for_cpp")|setf cynlib|else|setf cpp|endif
" C++
au BufNewFile,BufRead *.cxx,*.c++,*.hh,*.hxx,*.hpp,*.ipp,*.moc,*.tcc,*.inl setf cpp
if has("fname_case")
au BufNewFile,BufRead *.C,*.H setf cpp
endif
……
" Vim help file
au BufNewFile,BufRead $VIMRUNTIME/doc/*.txt setf help
……
" Use the filetype detect plugins. They may overrule any of the previously
" detected filetypes.
runtime! ftdetect/*.vim
……
大致来说,是通过文件后缀来判断文件的类型,例如cpp后缀认为就是一个cpp文件,通过setf cpp命令设置。
六、当执行filetype时,vim在执行什么
通过vim的代码可以看到,当执行filetype on的时候,执行的时运行根文件夹下的"filetype.vim"文件。
vim-8.1-using\src\os_dos.h
#ifndef FILETYPE_FILE
# define FILETYPE_FILE "filetype.vim"
#endif
#ifndef FTPLUGIN_FILE
# define FTPLUGIN_FILE "ftplugin.vim"
#endif
#ifndef INDENT_FILE
# define INDENT_FILE "indent.vim"
#endif
vim-8.1-using\src\ex_docmd.c
static int filetype_detect = FALSE;
static int filetype_plugin = FALSE;
static int filetype_indent = FALSE;
/*
* ":filetype [plugin] [indent] {on,off,detect}"
* on: Load the filetype.vim file to install autocommands for file types.
* off: Load the ftoff.vim file to remove all autocommands for file types.
* plugin on: load filetype.vim and ftplugin.vim
* plugin off: load ftplugof.vim
* indent on: load filetype.vim and indent.vim
* indent off: load indoff.vim
*/
static void
ex_filetype(exarg_T *eap)
{
char_u *arg = eap->arg;
int plugin = FALSE;
int indent = FALSE;
if (*eap->arg == NUL)
{
/* Print current status. */
smsg((char_u *)"filetype detection:%s plugin:%s indent:%s",
filetype_detect ? "ON" : "OFF",
filetype_plugin ? (filetype_detect ? "ON" : "(on)") : "OFF",
filetype_indent ? (filetype_detect ? "ON" : "(on)") : "OFF");
return;
}
/* Accept "plugin" and "indent" in any order. */
for (;;)
{
if (STRNCMP(arg, "plugin", 6) == 0)
{
plugin = TRUE;
arg = skipwhite(arg + 6);
continue;
}
if (STRNCMP(arg, "indent", 6) == 0)
{
indent = TRUE;
arg = skipwhite(arg + 6);
continue;
}
break;
}
if (STRCMP(arg, "on") == 0 || STRCMP(arg, "detect") == 0)
{
if (*arg == 'o' || !filetype_detect)
{
source_runtime((char_u *)FILETYPE_FILE, DIP_ALL);
filetype_detect = TRUE;
if (plugin)
{
source_runtime((char_u *)FTPLUGIN_FILE, DIP_ALL);
filetype_plugin = TRUE;
}
if (indent)
{
source_runtime((char_u *)INDENT_FILE, DIP_ALL);
filetype_indent = TRUE;
}
}
if (*arg == 'd')
{
(void)do_doautocmd((char_u *)"filetypedetect BufRead", TRUE, NULL);
do_modelines(0);
}
}
else if (STRCMP(arg, "off") == 0)
{
if (plugin || indent)
{
if (plugin)
{
source_runtime((char_u *)FTPLUGOF_FILE, DIP_ALL);
filetype_plugin = FALSE;
}
if (indent)
{
source_runtime((char_u *)INDOFF_FILE, DIP_ALL);
filetype_indent = FALSE;
}
}
else
{
source_runtime((char_u *)FTOFF_FILE, DIP_ALL);
filetype_detect = FALSE;
}
}
else
EMSG2(_(e_invarg2), arg);
}
七、命令行中ctrl-R之后特殊寄存器的意义
可以看到,这个地方有特定操作
ops.c
/*
* If "regname" is a special register, return TRUE and store a pointer to its
* value in "argp".
*/
int
get_spec_reg(
int regname,
char_u **argp,
int *allocated, /* return: TRUE when value was allocated */
int errmsg) /* give error message when failing */
{
int cnt;
*argp = NULL;
*allocated = FALSE;
switch (regname)
{
case '%': /* file name */
if (errmsg)
check_fname(); /* will give emsg if not set */
*argp = curbuf->b_fname;
return TRUE;
case '#': /* alternate file name */
*argp = getaltfname(errmsg); /* may give emsg if not set */
return TRUE;
#ifdef FEAT_EVAL
case '=': /* result of expression */
*argp = get_expr_line();
*allocated = TRUE;
return TRUE;
#endif
case ':': /* last command line */
if (last_cmdline == NULL && errmsg)
EMSG(_(e_nolastcmd));
*argp = last_cmdline;
return TRUE;
case '/': /* last search-pattern */
if (last_search_pat() == NULL && errmsg)
EMSG(_(e_noprevre));
*argp = last_search_pat();
return TRUE;
case '.': /* last inserted text */
*argp = get_last_insert_save();
*allocated = TRUE;
if (*argp == NULL && errmsg)
EMSG(_(e_noinstext));
return TRUE;
#ifdef FEAT_SEARCHPATH
case Ctrl_F: /* Filename under cursor */
case Ctrl_P: /* Path under cursor, expand via "path" */
if (!errmsg)
return FALSE;
*argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP
| (regname == Ctrl_P ? FNAME_EXP : 0), 1L, NULL);
*allocated = TRUE;
return TRUE;
#endif
case Ctrl_W: /* word under cursor */
case Ctrl_A: /* WORD (mnemonic All) under cursor */
if (!errmsg)
return FALSE;
cnt = find_ident_under_cursor(argp, regname == Ctrl_W
? (FIND_IDENT|FIND_STRING) : FIND_STRING);
*argp = cnt ? vim_strnsave(*argp, cnt) : NULL;
*allocated = TRUE;
return TRUE;
case Ctrl_L: /* Line under cursor */
if (!errmsg)
return FALSE;
*argp = ml_get_buf(curwin->w_buffer,
curwin->w_cursor.lnum, FALSE);
return TRUE;
case '_': /* black hole: always empty */
*argp = (char_u *)"";
return TRUE;
}
return FALSE;
}
八、help在线帮助中tag如何查找
在vim安装文件的doc文件夹中可以看到有一个tags文件,其中包含了一些关键字对应需要执行的查找命令。例如
:set options.txt /*:set*
:set+= options.txt /*:set+=*
:set-! options.txt /*:set-!*