summaryrefslogtreecommitdiffstats
path: root/lua/dotfiles/format.lua
blob: 72d0248075d5db22fa0be3ece97fe11ebec381f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
-- Format engine. Features register a stdin formatter command per filetype; the engine runs the
-- registered formatter over the buffer on write, when formatting is enabled for it (vim.b.format,
-- toggled by :FormatToggle). Formatters are registered in plugin/50-format.lua.
--
-- API:
--   register(filetype, cmd) — `cmd` is a command list run with the buffer contents on stdin; its
--                             stdout replaces the buffer. Call once per filetype to share a command.

local M = {}

-- Registered formatter commands, keyed by filetype.
M.formatters = {}

-- Run `cmd` with `lines` piped on stdin; return its stdout as a list of lines, or nil on failure.
local function run(cmd, lines)
	local r = vim.system(cmd, { stdin = lines, text = true }):wait()
	if r.code ~= 0 then
		vim.notify(cmd[1] .. " error:\n" .. r.stderr, vim.log.levels.WARN)
		return nil
	end
	return vim.split(r.stdout, "\n", { trimempty = true })
end

-- Register the stdin formatter command `cmd` for `filetype`.
function M.register(filetype, cmd)
	assert(type(filetype) == "string", "format.register: filetype must be a string")
	assert(type(cmd) == "table", "format.register: cmd must be a command list")
	M.formatters[filetype] = cmd
end

-- Format buffer `ev.buf` with its filetype's formatter when formatting is enabled for it.
-- Meant as a 'BufWritePre' handler; preserves the cursor/window view across the rewrite.
local function format_buffer(ev)
	if vim.b[ev.buf].format ~= true then
		return
	end
	local ft = vim.bo[ev.buf].filetype
	local cmd = M.formatters[ft]
	if not cmd then
		vim.notify("No formatter registered for " .. ft, vim.log.levels.ERROR)
		return
	end
	local lines_in = vim.api.nvim_buf_get_lines(ev.buf, 0, -1, false)
	local lines_out = run(cmd, lines_in)
	if not lines_out or vim.deep_equal(lines_in, lines_out) then
		return
	end
	local view = vim.fn.winsaveview()
	vim.api.nvim_buf_set_lines(ev.buf, 0, -1, false, lines_out)
	vim.fn.winrestview(view)
end

vim.api.nvim_create_autocmd("BufWritePre", {
	desc = "Format buffer",
	group = vim.g.dotfiles.augroup,
	callback = format_buffer,
})

return M