People who write LaTeX in Vim did not choose Vim for LaTeX. They already live in Vim or Neovim and want LaTeX to come to them. Bare Vim does know what a .tex file is — it ships a syntax file and a filetype plugin — but the header of that filetype plugin, ftplugin/tex.vim, still reads Last Change: Wed 19 Apr 2006. No compiling, no PDF, no \ref completion, no list of sections. All of that arrives with vimtex, the plugin by Karl Yngve Lervåg. This page covers what vimtex adds over bare Vim, the resident latexmk compilation behind \ll, the text objects that let you edit environments and commands in Vim’s own grammar, and the places where Vim and Neovim genuinely differ.
What bare Vim already knows about LaTeX
What bare Vim knows about a .tex file is colour plus three small tricks. [d jumps to the definition of a \newcommand or a \def; gf and [i read the contents of \include{...} as a filename; and with matchit enabled, % bounces between \begin{...} and \end{...}. The whole implementation fits in about forty lines of ftplugin/tex.vim, which today’s Vim 9.1 still ships with Benji Fisher’s name on it and the line Version: 1.4 / Last Change: Wed 19 Apr 2006. The syntax file syntax/tex.vim has grown to Version 121, but its header states that the runtime file is looking for a new maintainer.
Bare Vim also ships a compiler definition, compiler/tex.vim. Run :compiler tex and makeprg becomes latex -interaction=nonstopmode while errorformat is loaded with a long pattern for reading LaTeX logs, so a plain :make fills the quickfix list with your errors. In other words, even bare Vim gets you as far as “write, :make, jump to the bad line”. What it cannot do is open the PDF, move between source and PDF, complete \ref or \cite, or navigate the structure of the document. vimtex takes on all of that, and for the tex filetype it overrides Vim’s internal TeX plugin with its own.
" What bare Vim gives you, without any plugin at all.
packadd! matchit " % jumps between \begin{...} and \end{...}
compiler tex " :make runs latex and fills the quickfix listWhy a fresh .tex file becomes plaintex and vimtex never loads
The answer is simple: Vim guesses by reading the contents of the file. FTtex() in Vim 9.1’s autoload/dist/ft.vim first looks at a %&format line at the very top, then scans a thousand lines from the first non-comment line for any of \documentclass, \usepackage, \begin{, \newcommand or \renewcommand. If none of them turns up it falls back to a default, and that default — when g:tex_flavor is unset — is plain, which means the filetype becomes plaintex, not tex. An empty new file, or a fragment where you have not typed \documentclass yet, is exactly that case.
The interesting part is that the advice you usually see for this — put let g:tex_flavor = 'latex' in your vimrc — runs the other way round with vimtex. vimtex overrides the filetype detection itself in its own ftdetect/tex.vim and sets g:tex_flavor to latex for you. Its documentation says so explicitly: the override exists to prevent the surprise of .tex files being recognised as plaintex. So if vimtex is installed you do not need to write g:tex_flavor at all. Setting it to anything other than latex is how you refuse vimtex’s override — that is the case where the option earns its place.
vimtex setup, and why you must not lazy-load it
Bluntly: do not lazy-load vimtex. The reason is mechanical, not a matter of taste. Inverse search — going from the PDF back to the source — depends on :VimtexInverseSearch, a global command that the viewer calls from outside the editor. If the plugin body has not been loaded, that command does not exist. On top of that, vimtex is a filetype plugin and uses the autoload mechanism, so it is already only loaded when it is needed; there is no benefit left for a plugin manager to defer. In lazy.nvim that means lazy = false; in vim-plug it means no for clause.
Two more prerequisites. First, the editor version. Since vimtex 2.18, released in July 2026, it requires Vim 9.2 or Neovim 0.12.4 and simply will not load on anything older. If you are stuck on an older editor the right fix is to pin the v2.17 tag; let g:vimtex_version_check = 0 silences the check. Second, filetype plugin on and syntax enable. Without the first, vimtex does not load at all; without the second, everything that leans on syntax information — detecting math zones, the i$ text object — stops working. Even when you lean on Tree-sitter in Neovim, it is safest to leave Vim’s syntax features on.
call plug#begin()
Plug 'lervag/vimtex'
" Pin an older tag if you are stuck on Vim < 9.2:
" Plug 'lervag/vimtex', { 'tag': 'v2.17' }
call plug#end()
filetype plugin indent on " required (indent is optional)
syntax enable " required for math zones, i$ and friends
set encoding=utf-8 " needed in Vim, not in Neovim
let maplocalleader = ' ' " Space as <localleader>; default is backslash
let g:vimtex_view_method = 'zathura'In Neovim you write the same thing in Lua, and the settings belong inside init so that they take effect before the plugin body loads. maplocalleader is the doorway to almost every vimtex command — the default prefix is g:vimtex_mappings_prefix, whose default value is <localleader>l, and <localleader> itself defaults to a backslash — so declaring it explicitly saves confusion later. Leave it as backslash and you type \ll; change it to Space and you press Space followed by ll.
return {
"lervag/vimtex",
lazy = false, -- never lazy-load: it breaks :VimtexInverseSearch
init = function()
vim.g.maplocalleader = " "
vim.g.vimtex_view_method = "zathura" -- "skim" on macOS
vim.g.vimtex_compiler_method = "latexmk"
end,
}\ll: latexmk stays resident and the PDF follows every save
Press \ll once and latexmk starts in resident mode; press it again and it stops. It behaves as a toggle because the default compiler settings in g:vimtex_compiler_latexmk enable continuous, which uses latexmk’s continuous-preview watching underneath. From then on every save triggers a rebuild and the PDF in your viewer catches up by itself. For a one-off run there is \lS (:VimtexCompileSS), and to stop, \lk (or \lK for every project).
| Mapping | Command | What it does |
|---|---|---|
\ll | :VimtexCompile | Start or stop continuous compilation (a toggle) |
\lS | :VimtexCompileSS | A single-shot compilation, the same one-off run as CI |
\lv | :VimtexView | Open the PDF and forward-search to the cursor |
\lt | :VimtexTocOpen | Open the table-of-contents buffer (\lT toggles it) |
\le | :VimtexErrors | List errors and warnings in the quickfix window |
\lo | :VimtexCompileOutput | Show the compiler’s raw output |
\lc | :VimtexClean | Clean auxiliary files (\lC removes the output too) |
\li | :VimtexInfo | Show the detected root file, packages and other state |
\ls | :VimtexToggleMain | Toggle whether the current file counts as the root |
The options passed to latexmk by default are four: -verbose, -file-line-error, -synctex=1 and -interaction=nonstopmode. Because -synctex=1 is there from the start, the synchronization data needed by the forward and inverse search below is written out without any setup on your part. For errors, g:vimtex_quickfix_mode defaults to 2 — the quickfix window opens by itself but does not steal focus — which suits reading errors out of the corner of your eye while you write. To swap the compiler itself, set g:vimtex_compiler_method to one of latexmk (the default), latexrun, tectonic, arara, texpresso or generic.
When \ll runs the wrong engine: -pdf versus $pdf_mode
You wrote $pdf_mode = 3 in .latexmkrc and the build still refuses to go through DVI. The cause is that vimtex adds an engine flag to the command line every time. The engine comes from a lookup table, g:vimtex_compiler_latexmk_engines, whose default key _ maps to -pdf. A -pdf on the command line outranks $pdf_mode in .latexmkrc, so your configuration file is silently overruled. vimtex will deduce the engine from $pdf_mode — but, as its documentation states, only for 1 (pdfLaTeX), 4 (LuaLaTeX) and 5 (XeLaTeX). The DVI route, 3, is not among them.
The correct fix is to put a TeX-program directive at the top of the main file. Its right-hand side has to be a key of that lookup table, so LuaLaTeX is lualatex and the DVI route is pdfdvi (which maps to -pdfdvi). Then put the actual upLaTeX and dvipdfmx invocations in .latexmkrc — the classic combination for Japanese papers. latexmk configuration itself belongs to the automated-builds page, so look there for the details.
% In the main .tex file, first line: pick the key, not the binary name.
% !TeX program = pdfdvi
# .latexmkrc -- upLaTeX and dvipdfmx do the actual work
$latex = 'uplatex -synctex=1 -interaction=nonstopmode -file-line-error %O %S';
$bibtex = 'upbibtex %O %B';
$biber = 'biber --bblencoding=utf8 -u -U --output_safechars %O %S';
$makeindex = 'upmendex %O -o %D %S';
$dvipdf = 'dvipdfmx %O -o %D %S';
$max_repeat = 5;Passing -synctex=1 to $latex as well is the essential part: it carries the synchronization data through to the PDF even by way of a DVI. If you leave latexmk behind, the picture changes — switching to Tectonic is just g:vimtex_compiler_method set to tectonic, with no engine selection and no .latexmkrc involved.
Text objects: editing \begin{...} in Vim’s own grammar
This is the one irreplaceable reason to write LaTeX in Vim. Editing in Vim is built on a grammar of verb plus object: d (delete) or c (change) combined with iw (inside a word) or ap (a whole paragraph). vimtex adds the structures of LaTeX itself to that vocabulary. dae deletes everything from \begin{...} to \end{...}, cie replaces just the contents, and ci$ retypes only the inside of an inline formula. You never count lines to delete a thirty-line align environment again.
| Object | Matches | Typical use |
|---|---|---|
ie / ae | An environment (except the top-level document) | dae deletes the whole environment; cie replaces its contents |
i$ / a$ | A math environment ($...$, \[...\]) | ci$ retypes just the inside of the formula |
ic / ac | A command and its arguments | dac strips a whole \textbf{...} |
id / ad | A matching pair of delimiters | The LaTeX-aware ci(; it also grabs \left(...\right) |
iP / aP | A section | daP moves or deletes an entire section at once |
im / am | A single \item | Grabs exactly one entry of a list |
Beyond that there is a trio for rewriting structure: ds deletes a surrounding, cs changes it, ts toggles it. dse unwraps an environment and leaves the contents, cse turns an itemize into an enumerate (with completion offering the candidates), dsc/csc do the same for a command and dsd/csd for a delimiter. tse switches environments, tss toggles an environment’s starred form, tsc toggles a command’s star, and tsd moves between (...) and \left(...\right). For motion, % bounces between a matching pair, ]]/[[ go to the next and previous beginning of a section, ][/[] to the ends, ]m/[m to environments and ]n/[n to math. Put the cursor on a command and press K to open that package’s documentation.
Getting around a long document: the TOC buffer and \ref completion
Press \lt and the table of contents for the whole document opens as an ordinary buffer. Because it is an ordinary buffer you can search it with /, walk it with j and k, and press Enter to jump to that section. Even in a document split across files, vimtex follows \input and \include from the root file, so you get one table of contents spanning every chapter. It opens by default in a split fifty columns wide; appearance and behaviour are tuned through g:vimtex_toc_config. Whether the file you are editing counts as the root is toggled with \ls, and \li shows you which file vimtex has decided is the root.
Completion rides on Vim’s own machinery. In a tex buffer the omnifunc is set to vimtex#complete#omnifunc automatically (g:vimtex_complete_enabled is on by default), so pressing Ctrl-X Ctrl-O in insert mode produces candidates. Right after \cite{ you get citation keys from your .bib files and \bibitems; after \ref{, the \labels in the document; after \usepackage{, the .sty files you have installed; after \includegraphics{, filenames. Pressing that key every time is tiresome, so in practice you bridge the omnifunc into a completion engine — the omni source of nvim-cmp in Neovim, or the coc-omni extension of coc.nvim, which works in both. The division of labour never changes: vimtex produces the candidates, the completion engine decides when and how to show them.
local cmp = require("cmp")
cmp.setup({
sources = cmp.config.sources({
{ name = "omni" }, -- pulls vimtex candidates through omnifunc
}),
})What actually differs between Vim and Neovim
As far as editing goes, the text objects, the table of contents and the completion are exactly the same in both. The difference is one thing: whether something outside can call the editor back. Inverse search needs the viewer to reach into the editor, and in Vim that channel is the +clientserver feature. vimtex’s documentation says plainly that a server starts automatically on Windows or in gVim, but not for a Vim running in a terminal on Linux or macOS. Neovim has no clientserver at all and uses MessagePack-RPC instead; vimtex treats the two the same way, and in both the address lands in v:servername.
And “just start a server in terminal Vim” does not always work either. The /usr/bin/vim that ships with macOS reports -clientserver — the feature is compiled out entirely, so remote_startserver() does not exist and the snippet below does nothing. Your options there are to use a build that has +clientserver, such as MacVim or Homebrew’s Vim, or to move to Neovim. The practical conclusion of this section is the mirror image: on the Neovim side, no configuration is needed at all. Two smaller differences remain. Vim needs encoding set to utf-8 while Neovim does not, and the required editor versions are stated separately as Vim 9.2 and Neovim 0.12.4.
" Vim only, and only in a build that has +clientserver.
if empty(v:servername) && exists('*remote_startserver')
call remote_startserver('VIM')
endifChoosing a viewer, and the inverse-search command line
The most misunderstood thing here is the default of g:vimtex_view_method. It is not chosen cleverly per platform: on every OS the default is general, which falls back to a generic launch — xdg-open on Linux, open on macOS, SumatraPDF and friends on Windows. That generic viewer is named in g:vimtex_view_general_viewer. The dedicated methods on offer are zathura, zathura_simple, skim, mupdf, galley and sioyek; there is no sumatrapdf value. Using SumatraPDF on Windows goes through general. How SyncTeX itself works is covered on its own page, so what follows is configuration only.
Forward search (source → PDF) is just \lv and needs almost no configuration. The work is on the inverse-search side, where you have to tell the viewer “when I click, run this command”. The command in question is VimtexInverseSearch <line> <file>. The one thing to watch is that the placeholders for the line number and the filename are spelled differently in each viewer: zathura uses %{line} and %{input}, Skim uses %line and %file, and SumatraPDF uses %l and %f. With g:vimtex_view_method = 'zathura', vimtex launches zathura with -x and hands it that command for you, so on most systems Ctrl-click takes you back with nothing written at all.
# Linux: zathura. Ctrl-click in the PDF jumps back to the source.
set synctex true
set synctex-editor-command "nvim --headless -c 'VimtexInverseSearch %{line} %{input}'"For Skim on macOS, open the Sync tab of its preferences, set the preset to Custom and register the command and its arguments; inverse search is then Cmd-Shift-click. For SumatraPDF on Windows, put a line of the same shape into the inverse-search command-line field in its settings; inverse search is a double-click. With gVim, replace the nvim --headless part with vim -v --not-a-term -T dumb.
# macOS, Skim: Preferences > Sync > Preset: Custom
Command: nvim
Arguments: --headless -c "VimtexInverseSearch %line '%file'"
# Windows, SumatraPDF: Settings > Options > inverse search command-line
cmd /c start /min "" nvim --headless -c "VimtexInverseSearch %l '%f'"The four moves to lock in first
vimtex is not a tool you have to master before you start writing. Start continuous compilation with \ll, save, look at the right place in the PDF with \lv, and read nothing but the errors with \le — once those four are in your fingers, the rest can be added one at a time on the day you need it. With text objects, too, it is worth starting from just dae and cse and widening out to ci$ and tsd; you soon see which operations you actually perform every day.
Before you split the document across files, check one thing once: whether pressing \ll from inside a chapter still produces the same PDF. Look at \li to see which file vimtex has taken as the root; if it is wrong, switch with \ls or point at the main file with % !TeX root = main.tex. Settle that first and the build stays still while you add completion engines and snippets on top.