I've been reading documentation and trying experiments to register a new filetype in vim, and apply desired indentation rules -- all to no avail.

As an example, say I want to register *.test as filetype "test", and I want to auto-indent 2 spaces after a newline after an open parenthesis curly-brace.

All the documentation I've read converge on these actions:

  1. Create ~/.vim/ftdetect/test.vim.
  2. Create ~/.vim/ftplugin/test.vim.

There are differences in the various documentation's instructions on the ftplugin file's content, e.g. set vs. setlocal -- I've tried to exhaust all the variations that I've found, but it just doesn't seem to make a difference.

The end-result of all my trials is that when I edit a *.test file, I get no auto-indentation.
I.e. this is my desired behavior:

// foo.test
void foo() {
  // I want the cursor to start at 2-spaced indented because of the
  // opening curly-brace above.

...but this is the observed behavior

// foo.test
void foo() {
// The cursor starts here, in spite of the opening curly-brace above.

When I run :set ft? in vim, it responds with filetype=test -- I assume this means that it is "recognizing" *.test files, so I'm at a complete loss why it's not applying indentation rules.

I do not have any other ftdetect or ftplugin files -- I newly created ~/.vim/ftdetect/ and ~/.vim/ftplugin/ as part of what I described above.
In spite of this, auto-indentation is working correctly for C-family files (*.c, *.cpp, *.h, etc.) and "well-known" filetypes.
I don't understand why auto-indentation works for these "well-known" filetypes when they have no explicit ftdetect or ftplugin files.

Please help: I want vim to apply 2-space auto-indentation to *.test -- the same as it is applying to C-family files.

My newly-created ~/.vim/ftdetect/test.vim:

au BufRead,BufNewFile *.test            set filetype=test

My newly-created ~/.vim/ftplugin/test.vim:

autocmd FileType test setlocal expandtab
autocmd FileType test setlocal shiftwidth=2
autocmd FileType test setlocal softtabstop=2
autocmd FileType test setlocal cino=(0
autocmd FileType test setlocal syntax=on

My ~/.vimrc:

execute pathogen#infect()

filetype plugin indent on
syntax on

set termguicolors
 if has('nvim')
" https://github.com/neovim/neovim/wiki/FAQ
set guicursor=n-v-c:block-Cursor/lCursor-blinkon0,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor

filetype plugin on
colorscheme Base2Tone_EveningDark

set expandtab                   " Expand tab to spaces
set hlsearch
set number                      " Line numbers

set backspace=indent,eol,start
set cino=(0                     " Indent N chars from the line w/ unclosed paren
set laststatus=2                " Status-line always
set scrolloff=4                 " Start scrolling when cursor is N lines from top/bottom
set shiftwidth=2                " Number of spaces for each step of autoindent
set softtabstop=2               " Number of spaces that <tab> counts for
set textwidth=80

set statusline=
set statusline+=\%F
set statusline+=\%=%l/%L        " Line n/N
set statusline+=%4v             " virtual column
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*

set formatoptions+=q            " Allow formatting of comments with "gq"
set formatoptions+=t            " Auto-wrap using textwidth, auto-insert comment leader
set formatoptions+=j            " Remove comment leader when joining lines

set t_ut=

let g:syntastic_always_populate_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 1
let g:syntastic_c_config_file = '.syntastic_c_config'

My vim version:

$ vim --version
VIM - Vi IMproved 8.1 (2018 May 18, compiled Nov 23 2022 14:54:38)
Included patches: 1-875, 878, 881, 883-884, 936, 948, 1046, 1365-1368, 1382, 1401, 4120, 4151-4152, 4214, 4218, 4397, 4428, 4899, 4919, 4921, 4977, 5023-5024, 805, 5043, 5063, 5126
Extra patches: 8.2.3402, 8.2.3403, 8.2.3409, 8.2.3428, 9.0.0490, 9.0.0530, 9.0.0614
Modified by [email protected]
Compiled by [email protected]
Huge version without GUI.  Features included (+) or not (-):
+acl               +extra_search      +mouse_netterm     +tag_old_static
+arabic            +farsi             +mouse_sgr         -tag_any_white
+autocmd           +file_in_path      -mouse_sysmouse    -tcl
+autochdir         +find_in_path      +mouse_urxvt       +termguicolors
-autoservername    +float             +mouse_xterm       +terminal
-balloon_eval      +folding           +multi_byte        +terminfo
+balloon_eval_term -footer            +multi_lang        +termresponse
-browse            +fork()            -mzscheme          +textobjects
++builtin_terms    +gettext           +netbeans_intg     +textprop
+byte_offset       -hangul_input      +num64             +timers
+channel           +iconv             +packages          +title
+cindent           +insert_expand     +path_extra        -toolbar
-clientserver      +job               -perl              +user_commands
-clipboard         +jumplist          +persistent_undo   +vartabs
+cmdline_compl     +keymap            +postscript        +vertsplit
+cmdline_hist      +lambda            +printer           +virtualedit
+cmdline_info      +langmap           +profile           +visual
+comments          +libcall           -python            +visualextra
+conceal           +linebreak         -python3           +viminfo
+cryptv            +lispindent        +quickfix          +vreplace
+cscope            +listcmds          +reltime           +wildignore
+cursorbind        +localmap          +rightleft         +wildmenu
+cursorshape       -lua               -ruby              +windows
+dialog_con        +menu              +scrollbind        +writebackup
+diff              +mksession         +signs             -X11
+digraphs          +modify_fname      +smartindent       -xfontset
-dnd               +mouse             +startuptime       -xim
-ebcdic            -mouseshape        +statusline        -xpm
+emacs_tags        +mouse_dec         -sun_workshop      -xsmp
+eval              +mouse_gpm         +syntax            -xterm_clipboard
+ex_extra          -mouse_jsbterm     +tag_binary        -xterm_save
   system vimrc file: "$VIM/vimrc"
     user vimrc file: "$HOME/.vimrc"
 2nd user vimrc file: "~/.vim/vimrc"
      user exrc file: "$HOME/.exrc"
       defaults file: "$VIMRUNTIME/defaults.vim"
  fall-back for $VIM: "/usr/share/vim"
Compilation: gcc -c -I. -Iproto -DHAVE_CONFIG_H   -Wdate-time  -g -O2 -fdebug-prefix-map=/build/vim-mKzl1a/vim-8.1.0875=. -fstack-protector-strong -Wformat -Werror=format-security -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
Linking: gcc   -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -o vim        -lm -ltinfo -lnsl  -lselinux -lacl -lattr -lgpm -ldl

2 Answers 2

  1. Do not prepend autocmd
  2. Set cindent option
  3. Do not set syntax option
  • 2
    In particular, the ftplugin file, should simply set the options that need to be set (e.g. :setlocal sw=2 etc) Commented May 3, 2023 at 6:35
  • 1
    @ChristianBrabandt Well, it is not prohibited to do some other stuff including autocmd registration. But the rule of the thumb is "to be buffer-local only". E.g. au <buffer> etc.
    – Matt
    Commented May 3, 2023 at 11:07
  • No, but it will most likely work differently than expected. Because by the time the autocommand is source, the file has been already loaded, so it is too late to become active Commented May 3, 2023 at 11:17
  • @ChristianBrabandt Too late for another FileType, but not for some other events, I mean.
    – Matt
    Commented May 3, 2023 at 11:57
  • yes, but still probably not what was expected. Commented May 3, 2023 at 12:26

Everything is "working as advertised".

To expand on @Matt's answer…

There are two ways to execute commands, set options, etc. for a given filetype.

  • One is to use autocommands on the FileType event:

    autocommand FileType test setlocal expandtab

    where the command :setlocal expandtab is executed when the FileType event is triggered with value test.

    This is generally what people use in their vimrc when they want to keep all their stuff together in a single file.

  • The other is to simply put the desired commands in a dedicated "ftplugin":

    " in ftplugin/test.vim
    setlocal expandtab

    where ftplugin/test.vim is sourced automatically by Vim when the FileType event is triggered with value test.

    This is rather obviously less verbose than the first method, but it also has other advantages:

    • it uses Vim's built-in mechanism in the most canonical way possible
    • it doesn't add n more autocommands to an already towering pile of autocommands, which can hurt performance if it is not done correctly.

Basically, what you tried doesn't work because it is a mish-mash of the two methods above, where you add FileType autocommands where they don't make sense.

Here is what should happen:

  1. The FileType event is triggered with value test.
  2. ftplugin/test.vim is sourced.
  3. A bunch of options are set for the current buffer.

Here is what actually happens:

  1. The FileType event is triggered with value test.

  2. ftplugin/test.vim is sourced.

  3. A number of autocommands are added for the FileType event with value test.

  4. None of the desired options are set for the current buffer and the number of test autocommands augments as you edit more .test files:

    :e 1.test
      - no options
      - 4 "test" autocommands
    :e 2.test
      - options are set because of the autocommands
      - 8 "test" autocommands
    :e 3.test
      - options are set 2 times because of the autocommands
      - 12 "test" autocommands
    :e 4.test
      - options are set 3 times because of the autocommands
      - 16 "test" autocommands

That is not good.

The fix is thus to write your ftplugin properly:

setlocal expandtab
setlocal shiftwidth=2
setlocal softtabstop=2
setlocal cinoptions=(0

Note that the following command is incorrect for a few reasons:

setlocal syntax=on
  • You are confusing the :help :syntax command and the :help 'syntax' option.
  • Syntax highlighting is a global feature, so there is no point in trying to make it local.
  • The syntax option is local anyway so setlocal is not necessary.
  • You are assigning the on syntax to your test buffer, which is certainly not the desired outcome.


  • If you want to enable syntax highlighting, put syntax on in your vimrc, where it belongs.
  • If you want to assign a specific syntax different from the one that is automatically inferred from the filetype to the current buffer, put set syntax=<whatever> in your ftplugin. Since you didn't hint otherwise, you probably don't need this.
  • set syntax=test is only needed when globally using command syntax manual instead of syntax on. Otherwise, one will end up setting the syntax option (and, also, sourcing syntax script) twice in a row.
    – Matt
    Commented May 3, 2023 at 11:02
  • Good point, I will amend the answer.
    – romainl
    Commented May 3, 2023 at 12:10
  • Thank you for the detailed explanation! In the "The fix is thus to write your ftplugin properly" section of your answer, I found it was also necessary to add setlocal cindent, which I got from @Matt 's answer. Between both your answers, I can confirm that ftdetect and ftplugin are indeed "working as advertised" :)
    – StoneThrow
    Commented May 3, 2023 at 13:38

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.