-- How it started --vim.api.nvim_create_user_command( -- "Promqlfmt", -- ',!promtool promql format --experimental "$(cat /dev/stdin)"', -- { nargs = 0, range = "%" } --) -- How it's going if vim.g.loaded_promqlfmt == 1 then return end vim.g.loaded_promqlfmt = 1 -- remove once 0.10 is released local vim_is_old = vim.version().minor < 10 and vim.version().major == 0 --- Format a PromQL query using promtool --- @param query string @query to format --- @param padding number|nil @number of spaces to add to each line, default 0 --- @param padchar string|nil @character for padding, default " " --- @return string[]|nil @formatted query, one element per line or nil local promql_format = function(query, padding, padchar) padding = padding or 0 padchar = padchar or " " local success, result = pcall(function() if vim_is_old then return { code = 0, stdout = vim.fn.system({ "promtool", "promql", "format", "--experimental", query, }), } end return vim .system( { "promtool", "promql", "format", "--experimental", query }, { text = true } ) :wait() end) if success and result and result.code == 0 then local lines = vim.fn.split(result.stdout, "\n") if padding == 0 then return lines end for i, line in ipairs(lines or {}) do lines[i] = (padchar):rep(padding) .. line end return lines else print("woops error occurred while formatting query") print(vim.inspect(result)) end end --- Get the last column of a line, ie the position of the last character --- @param bufnr number --- @param linenr number local last_col_line = function(bufnr, linenr) local line = vim.api.nvim_buf_get_lines(bufnr, linenr, linenr + 1, true)[1] return #line end --- Get the selected text in a buffer --- @param bufnr number|nil --- @return table local buf_get_selected_text = function(bufnr) local r = { text = "", start_line = nil, start_row = nil, end_line = nil, end_row = nil, } local pos1, pos2 = "'<", "'>" if vim_is_old then local pos = vim.fn.getpos("'<") pos1 = { pos[2] - 1, pos[3] - 1 + pos[4] } pos = vim.fn.getpos("'>") pos2 = { pos[2] - 1, pos[3] - 1 + pos[4] } if pos1[1] > pos2[1] or (pos1[1] == pos2[1] and pos1[2] > pos2[2]) then pos1, pos2 = pos2, pos1 end -- getpos() may return {0,0,0,0} if pos1[1] < 0 or pos1[2] < 0 then return {} end end local region = vim.region(bufnr or 0, pos1, pos2, vim.fn.visualmode(), true) local maxcol = vim.v.maxcol for line, cols in vim.spairs(region) do if r.start_line == nil then r.start_line = line r.start_row = cols[1] end local endcol = cols[2] == maxcol and -1 or cols[2] local chunk = vim.api.nvim_buf_get_text(0, line, cols[1], line, endcol, {})[1] r.text = ("%s%s\n"):format(r.text, chunk) r.end_line = line r.end_row = endcol end -- 0.9 does not support negative end_row if vim_is_old then local last_row = last_col_line(0, r.end_line) if r.end_row == -1 or r.end_row > last_row then r.end_row = last_row end else r.end_row = r.end_row > last_col_line(0, r.end_line) and -1 or r.end_row end return r end vim.api.nvim_create_user_command("Promqlfmt", function(opts) -- it was not called from a visual selection if opts.range == 0 then -- get all lines local buf_text = vim.api.nvim_buf_get_lines(0, 0, -1, true) -- format query local text_formatted = promql_format(table.concat(buf_text, "\n")) if text_formatted == nil then return end -- replace all text with the formatted version vim.api.nvim_buf_set_lines(0, 0, -1, true, text_formatted) return end local s = buf_get_selected_text() -- use the position of the first non-whitespace character of the first line -- as padding local padding = string.find( vim.api.nvim_buf_get_lines(0, s.start_line, s.start_line + 1, true)[1], "%S" ) - 1 local text_formatted = promql_format(s.text, padding) if text_formatted == nil then return end -- remove padding from first line if s.start_row ~= 0 then text_formatted[1] = text_formatted[1]:sub(padding + 1) end vim.api.nvim_buf_set_text( 0, s.start_line, s.start_row, s.end_line, s.end_row, text_formatted ) end, { nargs = 0, range = "%" })