-- 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