The mechanic that would perfect his work must first sharpen his tools.

– Confucius

Once your Vim gills grow in, trying to operate outside of your new command line haven can feel akin to being a fish out of water. Two excellent posts regarding using Vim with splits and ditching excess file explorers have already demonstrated how easy it is to navigate Vim without using external programs or plugins. However, we all encounter filetypes that aren’t suitable for editing within Vim, such as viewing images, movies, audio, PDF files, and yes, even spreadsheets. Unfortunately we’ve thus far been unsuccessful in converting all humans to plain text formats and must still suffer the treachery of .docx and .pptx formats. If you’re facing this scenario and have completed your obligatory corner crying, read on. There is hope for editing non-Vim file formats right at your finger tips.

Making use of ftdetect

If you’re still attached to a command-line based file explorer like ranger or vifm then this post is not here to shame you. You can easily type or map :vert term ranger and be on your merry way. This option of course depends on features that may not be available in your version of Vim or tools that may not be present on every system you utilize. You also might find yourself wanting to open a file with your standard Vim key bindings. Luckily, Vim has a built-in structure for filetype detection and execution. You can bolster Vim’s filetype detection with your own customized extensions using the ~/.vim/ftdetect directory. As specified in :help 'ftdetect', you simply have to create a file ~/.vim/ftdetect that corresponds to a nominal filetype. The name of this file can be whatever you want (e.g. text). For instance:

:!mkdir -p ~/.vim/ftdetect/text.vim

Then edit that file and place the following:

autocommand BufRead,BufNewFile *.txt,*.md,*.mkd,*.markdown,*.mdwn set filetype=text

Now whenever Vim reads a buffer or new file with the extensions *.txt, et al. it will automatically assign the filetype of text to the buffer. Note that the ftdetect files are only meant for filetype detection and not for multiple lines of complex commands.

Carrying out actions on a given filetype

When Vim recognizes that a buffer belongs to a particular filetype it executes a corresponding ftplugin (if one exists). You can :view several of these ftplugin files within Vim’s runtime directory:

:view $VIMRUNTIME/ftplugin/

You can find information for writing your own ftplugin files at :help write-filetype-plugin, or just read on. As specified in the help file, an ftplugin should start with the following:

if exists("b:did_ftplugin")
  finish
endif
let b:did_ftplugin = 1

to prevent the ftplugin from running multiple times on the same buffer. You can then specify commands to be executed on the filetype in question when detected. If you find this a bit cumbersome, you’re not alone. You can bypass the need for such a guard by instead using the ~/.vim/after/ftplugin directory, which executes after the default ftplugin runs (if there is one).

Putting it all together

Coming back to our opening statements, how do we make this work for external formats? For starters, make your ftdetect file:

" Place this in ~/.vim/ftdetect/video.vim
au BufRead,BufNewFile *.avi,*.mp4,*.mkv,*.mov,*.mpg set filetype=video

then create your ftplugin file:

" Place this in ~/.vim/after/ftplugin/video.vim
silent execute "!mplayer " . shellescape(expand("%:p")) . " &>/dev/null &" | buffer# | bdelete# | redraw! | syntax on

In this example we’re using mplayer to open the video file, but you could just as easily use any other video player. This silently executes an !external-program on the current buffer and places it in the background (&), discarding any output or errors to /dev/null. The |s could easily be separated onto multiple lines and act as a list of sequential commands to switch back to the previous buffer (buffer#), delete the buffer containing the binary file (bdelete#), and then ensure that the screen was not garbled in the process by redrawing and ensuring syntax highlighting is on.

You could also consider using a system() call with xdg-open, open, or explorer, depending on what is available on your system. The previous example is not fully cross-platform, but with a few tweaks you can make this work on any system. For instance, consider this function:

" What command to use
function! s:Cmd() abort
    " Linux/BSD
    if executable("xdg-open")
        return "xdg-open"
    endif
    " MacOS
    if executable("open")
        return "open"
    endif
    " Windows
    return "explorer"
endfunction

You could then use this function in conjunction with a system() call to make cross-platform a reality.

" Place this in ~/.vim/ftdetect/audio.vim
au BufRead,BufNewFile *.mp3,*.flac,*.wav,*.ogg set filetype=audio
" Place this in ~/.vim/after/ftplugin/audio.vim
" insert or source Cmd() function here
call system(<SID>Cmd() . " " . expand("%:p")) | buffer# | bdelete# | redraw! | syntax on

You can see how the ftdetect and ftplugin directories can be your friend. Here’s an example of opening a video with Vim: