Template files for nvim
Over the years I’ve picked up various tweaks for my nvim
configuration. One
of them is template file support. This article is a short introduction to what
they are, how do they work and how I recently extended them to support
snippet expansion.
BufNewFile ¶
nvim
supports a BufNewFile
autocommand
that’s triggered when starting to
edit a file that doesn’t exist.
I use this to implement template files. If I run :e /tmp/newfile.sh
within
nvim
the BufNewFile
autocommand
triggers and looks for a template in
~/.config/nvim/templates/<ext>.tpl
. If it exists it reads the file contents
into the buffer. The <ext>.tpl
is derived from the filename, in this case it
would be sh.tpl
.
sh.tpl
looks like this:
#!/usr/bin/env bash
set -Eeuo pipefail
For the longest time I had set this up in a vimscript autocmd definition like this:
autocmd BufNewFile * silent! 0r $HOME/.config/nvim/templates/%:e.tpl
Lua and exact match ¶
At some point I wanted to have template files which are exact matches, mostly
for pom.xml
files for Maven. Because of that I turned the above vimscript
one-liner into Lua code that also checks if there is an exact match.
pom.xml.tpl
in this case:
vim.api_nvim_create_autocmd("BufNewFile", {
group = vim.api.nvim_create_augroup("templates", { clear = true }),
desc = "Load template file",
callback = function(args)
local home = os.getenv("HOME")
-- fnamemodify with `:t` gets the tail of the file path: the actual filename
-- See :help fnamemodify
local fname = vim.fn.fnamemodify(args.file, ":t")
local tmpl = home .. "/.config/nvim/templates/" .. fname ..".tpl"
-- fs_stat is used to check if the file exists
if vim.uv.fs_stat(tmpl) then
-- See :help :read
-- 0 is the range:
-- This reads as: "Insert the file <tmpl> below the specified line (0)"
vim.cmd("0r " .. tmpl)
else
-- fnamemodify with `:e` gets the filename extension
local ext = vim.fn.fnamemodify(args.file, ":e")
tmpl = home .. "/.config/nvim/templates/" .. ext ..".tpl"
if vim.uv.fs_stat(tmpl) then
vim.cmd("0r " .. tmpl)
end
end
end
})
The template itself looked like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
</properties>
<build>
</build>
<dependencies>
</dependencies>
</project>
Snippets ¶
Some of my templates ended up having a couple of TODO
markers, which I’d
manually need to update. With the addition of vim.snippet.expand
in nvim I
saw the opportunity to do something about that and extended the BufNewFile
autocommand to expand the template’s content as snippet:
vim.api.nvim_create_autocmd("BufNewFile", {
group = vim.api.nvim_create_augroup("templates", { clear = true }),
desc = "Load template file",
callback = function(args)
local home = os.getenv("HOME")
local fname = vim.fn.fnamemodify(args.file, ":t")
local ext = vim.fn.fnamemodify(args.file, ":e")
local candidates = { fname, ext }
local uv = vim.uv
for _, candidate in ipairs(candidates) do
local tmpl = table.concat({ home, "/.config/nvim/templates/", candidate, ".tpl" })
if uv.fs_stat(tmpl) then
vim.cmd("0r " .. tmpl)
return
end
end
for _, candidate in ipairs(candidates) do
local tmpl = table.concat({ home, "/.config/nvim/templates/", candidate, ".stpl" })
local f = io.open(tmpl, "r")
if f then
local content = f:read("*a")
vim.snippet.expand(content)
return
end
end
end
})
The script now tries:
<filename>.tpl
<filename_extension>.tpl
<filename>.stpl
<filename_extension>.stpl
Where .tpl
files are read as is into the buffer, and .stpl
files
treated as snippet which expand via vim.snippet.expand
.
My new pom.xml.stpl
looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>${1:group}</groupId>
<artifactId>${2:artifact}</artifactId>
<version>${3:0.1.0}</version>
<packaging>${4:jar}</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>${5:21}</maven.compiler.source>
<maven.compiler.target>${5:21}</maven.compiler.target>
</properties>
<build>
</build>
<dependencies>
</dependencies>
</project>
With this in place opening a new pom.xml
will start in snippet mode with
group, artifact, version, jar, and the JDK verison highlighted and ready to
jump through each via <tab>
to fill in the correct values.