11

I have created a few scripts in .vim/ftdetct to e.g. detect the filetype of sshconfig files based on their filename:

autocmd BufRead,BufNewFile ~/.ssh/host-* setfiletype sshconfig

Now, how can I make Vim detect the filetype based on the shebang (#!) line in files, e.g.:

#!/usr/bin/awk -f

1 Answer 1

14

If you want to introduce a file type and that file type can only be identified by examining the contents of such files then you'll need to add appropriate logic to a file named scripts.vim.

But first, let's take a step back and look at the bigger picture. (Skip to the "Solution" section, below, if you're not interested in the technical details.)

Background

File type detection is enabled with the command :filetype on. (This is typically in the user vimrc file or triggered by sourcing defaults.vim.)

The command will cause Vim to source the script named filetype.vim found in the directory named in the $VIMRUNTIME environment variable. Doing so sets up a bunch of au BufNewFile,BufRead autocommands. Since detection by filename is much more common than detection by content, the bulk of filetype.vim is made up of lines like the following:

au BufNewFile,BufRead *.clj,*.cljs,*.cljx,*.cljc  setf clojure

A couple thousand lines later we get to the part that kicks off detection by content:

au BufNewFile,BufRead *
    \ if !did_filetype() && expand("<amatch>") !~ g:ft_ignore_pat
    \ | runtime! scripts.vim | endif

The * indicates that this autocommand will be triggered for all files so some logic is needed to filter out files that already matched a file extension specific autocommand (or otherwise had their type identified). !did_filetype() guards against this as the function returns true if the FileType event has been triggered at least once prior and we negate (!) the result, thus halting further execution of the line. (We'll be using this same function in a guard block in our scripts.vim.)

Following that is a check that the filename we're dealing with doesn't match certain extensions (e.g. .zip)

If those two checks are passed, scripts.vim will be executed. But which one? The :runtime command means that ALL files named scripts.vim appearing in one of the paths specified in 'runtimepath' will be sourced/executed. There is always an instance of this file in the $VIMRUNTIME directory. We're going to achieve our goal by creating our own instance of it.

Note that, by default, the first path in 'runtimepath' is $HOME/.vim (or non-Unix equivalent). That's where our script file is going to go which means our detection logic will take precedence over that in $VIMRUNTIME or anywhere else.

(As an aside, a plugin-manager's main function is to update 'runtimepath' and add a path for each installed plugin. While these typically get inserted before any system directories, as far as I know they always come after $HOME/.vim.)

Solution

With the deep dive out of the way let's get down to business.

The standard place to put the local scripts.vim file is in your personal Vim folder (usually in $HOME) so run the following command (or your system equivalent): vim ~/.vim/scripts.vim

Assuming the file didn't exist before now, you'll want to add a guard block based on the function did_filetype() which we covered above. Follow that with a conditional block with appropriate detection logic. For the OP's use case that would look like this:

if did_filetype()   " filetype already set..
  finish        " ..don't do these checks
endif

if getline(1) =~ '^#!/usr/bin/awk -f'
  setfiletype yourfiletype
endif

If the file already existed there's presumably something like the above already in it. In that case just append your detection logic as an elseif.

" Existing two conditionals...
if {condition1}
  setfiletype filetype1
elseif {condition2}
  setfiletype filetype2
...etc...
" Add these two lines
elseif getline(1) =~ '^#!/usr/bin/awk -f'
  setfiletype yourfiletype
endif

See also :h new-filetype-scripts and :h filetypes

6
  • 1
    Thanks. Didn't knew that. Is scripts.vim a special name? Could I choose any other name for the file? Would it be possible to add that code to a file in ftdetect to group all file-detection related files?
    – Shuzheng
    Commented Jun 23, 2021 at 13:35
  • @BLayer You should probably include the explanation of scripts.vim (special filename, where it's searched for, runs with buffer loaded, etc.) as part of the answer...
    – filbranden
    Commented Jun 23, 2021 at 14:59
  • 1
    @filbranden Fair enough.
    – B Layer
    Commented Jun 23, 2021 at 22:49
  • 1
    The additions I made may convince you that you should stick with 'scripts.vim'.
    – B Layer
    Commented Jun 23, 2021 at 23:34
  • 1
    @Shuzheng No worries! Thanks. BTW, I added a lot of into to the answer after your last comment of 6/23. Not sure if you saw that so FYI.
    – B Layer
    Commented Jun 28, 2021 at 1:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.