在使用vim编辑一个文件的时候,如果能够识别出文件的类型,加上对应的高亮规则,可以使文件的查看更加醒目,这个功能几乎是使用vim浏览文件的一个核心诉求。
另外,在进行文件编辑的时候,特别是使用vim写代码的时候(典型的场景是通过vim写C/C++代码),如果能够智能缩进,还可以减少敲代码。例如,在每行的开头自动添加缩进与前一行对齐;或者是当在输入注释时(前一行是//或者/*),通常默认的缩进也会在当前行前面加上注释(//或者*),这也是很多开源软件中看到/**/风格注释中每行开头都有一个*的原因。
前面说的高亮和缩进,需要先识别出文件类型,然后根据文件类型确定语法、高亮、缩进。所以如何识别出这个文件的类型就是整个便利性的基础。不过话说回来,这些功能对vim这种编辑器来说都不是必须的,它们都是为了让使用更加便捷,所以这些功能的实现很多是通过插件来完成。
在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
在该文件中,注册了对于打开文件,读取文件之类事件的自动处理函数。它们主要通过文件名、文件后缀之类的信息来猜测文件格式。
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
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);
    }
当执行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()
从这个流程上看,该动作执行之后,触发的事件为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 */
    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;
	    }
	}
……
}
原文:https://www.cnblogs.com/tsecer/p/15003566.html