Overview

Just a quick post here. I already integrated a SPARQL language server using Neovim’s native LSP support to get diagnostics for the query I’m editing, but that language server isn’t capable of formatting. This post contains the steps I took to add “format on save” functionality for SPARQL queries in Neovim without using any plugins.

Format SPARQL queries on save

See my post on Integrating a SPARQL Language Server with Neovim’s LSP Client if you’re interested in seeing diagnostic information while editing your SPARQL queries with Neovim.

Finding a Formatter

Before writing the configuration for Neovim to run the formatter, I needed to find something to do the actual formatting. Ultimately, I ended up using this really great npm package called sparql-formatter. This package also comes with a nice CLI that I ended up leveraging:

Made with VHS

Wiring up Neovim

Next, I set up an autocmd (autocommand) to run sparql-formatter on files with extensions .sparql or .rq whenever they are saved.

commands.lua
vim.api.nvim_create_autocmd("BufWritePost", {
group = vim.api.nvim_create_augroup("formatting", { clear = true }),
pattern = { "*.sparql", "*.rq" },
callback = function()
local bufnr = vim.api.nvim_get_current_buf()
local sparql = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), "\n")
local process_sub = string.format('<(echo "%s")', sparql)
vim.fn.jobstart(string.format("sparql-formatter %s", process_sub), {
stdout_buffered = true,
stderr_buffered = true,
on_stdout = function(_, data, _)
if table.concat(data) ~= "" and sparql ~= table.concat(data, "\n") then
vim.api.nvim_buf_set_lines(bufnr, 1, -1, false, data)
local current_file_path = vim.api.nvim_buf_get_name(0)
vim.cmd(":w")
print(string.format("Formatted %s", current_file_path))
end
end,
on_stderr = function(_, data, _)
if data and table.concat(data) ~= "" then
print(table.concat(data, "\n"))
print("Unable to format query")
end
end,
})
end,
})

Let’s break it down.

Create the Autocommand

The vim.api.nvim_create_autocmd API creates an autocommand for the event BufWritePost (triggered after writing the buffer to disk). It specifies an autocommand group (formatting) for the autocommand to organize related commands and clears the group before defining new commands. The autocommand is triggered for files with .sparql and .rq extensions.

Define the Callback Function for the Autocommand

The callback function (assigned to the callback parameter) is executed when the autocommand is triggered. It gets the current buffer number (bufnr) and reads the contents of the buffer into a variable named sparql. I then worked on formatting the buffer contents within this callback.

Formating the Buffer Contents

I used process substitution to pass the contents of my Neovim buffer (presumably containing a valid SPARQL query) to the sparql-formatter command.

The vim.fn.jobstart API is then used to start a job (asynchronous process).

  • The on_stdout and on_stderr parameters are assigned callback functions that handle the standard output and standard error emitted from the job, respectively.
Handling StdOut

In the on_stdout callback, if the formatted data is not empty and different from the original SPARQL query, it updates the buffer with the formatted data and writes the buffer to disk, effectively saving the formatting changes. It also prints a message indicating that the file has been formatted.

Handling StdErr

In the on_stderr callback, if there is any error output, it prints the error message and notifies that the SPARQL couldn’t be formatted.

The Final Product

That’s it! Autocommands are extremely powerful and really let you automate easily within Neovim. All it took was roughly 30 lines of Lua to auto format SPARQL queries on save with no Neovim plugins.

Format SPARQL queries on save