vim中⽂件类型识别、语法⾼亮及缩进实现流程
⼀、⽂件类型
在使⽤vim编辑⼀个⽂件的时候,如果能够识别出⽂件的类型,加上对应的⾼亮规则,可以使⽂件的查看更加醒⽬,这个功能⼏乎是使⽤vim 浏览⽂件的⼀个核⼼诉求。
另外,在进⾏⽂件编辑的时候,特别是使⽤vim写代码的时候(典型的场景是通过vim写C/C++代码),如果能够智能缩进,还可以减少敲代码。例如,在每⾏的开头⾃动添加缩进与前⼀⾏对齐;或者是当在输⼊注释时(前⼀⾏是//或者/*),通常默认的缩进也会在当前⾏前⾯加上注释(//或者*),这也是很多开源软件中看到/**/风格注释中每⾏开头都有⼀个*的原因。
前⾯说的⾼亮和缩进,需要先识别出⽂件类型,然后根据⽂件类型确定语法、⾼亮、缩进。所以如何识别出这个⽂件的类型就是整个便利性的基础。不过话说回来,这些功能对vim这种编辑器来说都不是必须的,它们都是为了让使⽤更加便捷,所以这些功能的实现很多是通过插件来完成。
⼆、vim的filetype命令
在vim执⾏filetype命令时,vim执⾏的函数为ex_filetype,其中⽐较关键的时,它会到filetype.vim脚本⽂件并执⾏,这个filetype.vim⽂件名是在代码中写死的。
/*
* ":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)
{
……
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);
}
}
……
}
其中⽤到的⼀些宏
#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
三、filetype.vim的主要内容
在该⽂件中,注册了对于打开⽂件,读取⽂件之类事件的⾃动处理函数。它们主要通过⽂件名、⽂件后缀之类的信息来猜测⽂件格式。runtime\filetype.vim
" Shell scripts (sh, ksh, bash, bash2, csh); Allow .profile_foo etc.
" Gentoo ebuilds and Arch Linux PKGBUILDs are actually bash scripts
au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-
]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call dist#ft#SetFileTypeSH("bash")
au BufNewFile,BufRead .kshrc*,*.ksh call dist#ft#SetFileTypeSH("ksh")
au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call dist#ft#SetFileTypeSH(getline(1))
当然也有⼀部分是通过⽂件的前⼏⾏来判断/确认
" Shell script (Arch Linux) or PHP file (Drupal)
au BufNewFile,BufRead *.install
\ if getline(1) =~ '<?php' |
\ setf php |
\ else |
\ call dist#ft#SetFileTypeSH("bash") |
\ endif
还有⼀些脚本类型⽂件的判断,通过第⼀⾏中的"#!"来判断脚本类型的。这也意味着可以对于"#!"开头的⽂件,vim通常能很好的识别出来脚本类型。
在脚本类型scripts.vim⽂件检测中
let s:line1 = getline(1)
if s:line1 =~# "^#!"
" A script that starts with "#!".
" Check for a line like "#!/usr/bin/env VAR=val bash". Turn it into
" "#!/usr/bin/bash" to make matching easier.
if s:line1 =~# '^#!\s*\S*\<env\s'
let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g')
let s:line1 = substitute(s:line1, '\<env\s\+', '', '')
endif
四、syntax on指令的执⾏
vim-8.1\src\syntax.c
static void
syn_cmd_onoff(exarg_T *eap, char *name)
{
char_u buf[100];
eap->nextcmd = check_nextcmd(eap->arg);
if (!eap->skip)
{
STRCPY(buf, "so ");
vim_snprintf((char *)buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
do_cmdline_cmd(buf);
}
}
其中⾼亮⽂件夹
#ifndef SYNTAX_FNAME
# define SYNTAX_FNAME "$VIMRUNTIME/syntax/%s.vim"
#endif
在syntax.vim脚本中,注册了FileType事件,也就是当设置了⽂件类型之后会执⾏的命令,这个命令会触发set syntax=XXX命令的执⾏,⽽该命令进⽽触发颜⾊的⾼亮。
vim-8.1\runtime\syntax\syntax.vim
" Load the Syntax autocommands and set the default methods for highlighting.
runtime syntax/synload.vim
……
" Set up the connection between FileType and Syntax autocommands.
" This makes the syntax automatically set when the file type is detected.
augroup syntaxset
au! FileType * exe "set syntax=" . expand("<amatch>")
augroup END
if (save_ei != NULL)
{
au_event_restore(save_ei);
apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn,
curbuf->b_fname, TRUE, curbuf);
}
五、缩进(indent)
当执⾏filetype indent on时,执⾏的命令为:
static void
ex_filetype(exarg_T *eap)
{
……
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;
}
}
……
}
在indent.vim脚本中,也注册了对于⽂件类型设置的侦听。
runtime\indent.vim
augroup filetypeindent
au FileType * call s:LoadIndent()
func! s:LoadIndent()
六、通过set ft=XXXX触发的事件流
从这个流程上看,该动作执⾏之后,触发的事件为EVENT_FILETYPE⾃动事件,如果希望处理这个事件,可以注册对于该事件的⾃动命令。由于indent和syntax都注册了对这⾥抛出的FileType事件的侦听,所以当设置了⽂件类型,语法和缩进开启的情况下,它们会⾃动⽣效。
vim-8.1\src\option.c
static char_u *
did_set_string_option(
int opt_idx, /* index in options[] table */
char_u **varp, /* pointer to the option variable */
vim命令进入编辑模式int new_value_alloced, /* new value was allocated */
char_u *oldval, /* previous value of the option */
char_u *errbuf, /* buffer for errors, or NULL */
int opt_flags) /* OPT_LOCAL and/or OPT_GLOBAL */
{
……
else if (varp == &(curbuf->b_p_ft))
{
/
* 'filetype' is set, trigger the FileType autocommand.
* Skip this when called from a modeline and the filetype was
* already set to this value. */
if (!(opt_flags & OPT_MODELINE) || value_changed)
{
static int ft_recursive = 0;
++ft_recursive;
did_filetype = TRUE;
// Only pass TRUE for "force" when the value changed or not
// used recursively, to avoid endless recurrence.
apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname, value_changed || ft_recursive == 1, curbuf);
--ft_recursive;
/* Just in case the old "curbuf" is now invalid. */
if (varp != &(curbuf->b_p_ft))
varp = NULL;
}
}
……
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。