diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua new file mode 100644 index 0000000000..2079006234 --- /dev/null +++ b/runtime/lua/editorconfig.lua @@ -0,0 +1,246 @@ +local M = {} + +M.properties = {} + +--- Modified version of the builtin assert that does not include error position information +--- +---@param v any Condition +---@param message string Error message to display if condition is false or nil +---@return any v if not false or nil, otherwise an error is displayed +--- +---@private +local function assert(v, message) + return v or error(message, 0) +end + +--- Show a warning message +--- +---@param msg string Message to show +--- +---@private +local function warn(msg, ...) + vim.notify(string.format(msg, ...), vim.log.levels.WARN, { + title = 'editorconfig', + }) +end + +function M.properties.charset(bufnr, val) + assert( + vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val), + 'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"' + ) + if val == 'utf-8' or val == 'utf-8-bom' then + vim.bo[bufnr].fileencoding = 'utf-8' + vim.bo[bufnr].bomb = val == 'utf-8-bom' + elseif val == 'utf-16be' then + vim.bo[bufnr].fileencoding = 'utf-16' + else + vim.bo[bufnr].fileencoding = val + end +end + +function M.properties.end_of_line(bufnr, val) + vim.bo[bufnr].fileformat = assert( + ({ lf = 'unix', crlf = 'dos', cr = 'mac' })[val], + 'end_of_line must be one of "lf", "crlf", or "cr"' + ) +end + +function M.properties.indent_style(bufnr, val, opts) + assert(val == 'tab' or val == 'space', 'indent_style must be either "tab" or "space"') + vim.bo[bufnr].expandtab = val == 'space' + if val == 'tab' and not opts.indent_size then + vim.bo[bufnr].shiftwidth = 0 + vim.bo[bufnr].softtabstop = 0 + end +end + +function M.properties.indent_size(bufnr, val, opts) + if val == 'tab' then + vim.bo[bufnr].shiftwidth = 0 + vim.bo[bufnr].softtabstop = 0 + else + local n = assert(tonumber(val), 'indent_size must be a number') + vim.bo[bufnr].shiftwidth = n + vim.bo[bufnr].softtabstop = -1 + if not opts.tab_width then + vim.bo[bufnr].tabstop = n + end + end +end + +function M.properties.tab_width(bufnr, val) + vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number') +end + +function M.properties.max_line_length(bufnr, val) + local n = tonumber(val) + if n then + vim.bo[bufnr].textwidth = n + else + assert(val == 'off', 'max_line_length must be a number or "off"') + vim.bo[bufnr].textwidth = 0 + end +end + +function M.properties.trim_trailing_whitespace(bufnr, val) + assert( + val == 'true' or val == 'false', + 'trim_trailing_whitespace must be either "true" or "false"' + ) + if val == 'true' then + vim.api.nvim_create_autocmd('BufWritePre', { + group = 'editorconfig', + buffer = bufnr, + callback = function() + local view = vim.fn.winsaveview() + vim.api.nvim_command('silent! undojoin') + vim.api.nvim_command('silent keepjumps keeppatterns %s/\\s\\+$//e') + vim.fn.winrestview(view) + end, + }) + else + vim.api.nvim_clear_autocmds({ + event = 'BufWritePre', + group = 'editorconfig', + buffer = bufnr, + }) + end +end + +function M.properties.insert_final_newline(bufnr, val) + assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"') + vim.bo[bufnr].fixendofline = val == 'true' + vim.bo[bufnr].endofline = val == 'true' +end + +--- Modified version of |glob2regpat()| that does not match path separators on *. +--- +--- This function replaces single instances of * with the regex pattern [^/]*. However, the star in +--- the replacement pattern also gets interpreted by glob2regpat, so we insert a placeholder, pass +--- it through glob2regpat, then replace the placeholder with the actual regex pattern. +--- +---@param glob string Glob to convert into a regular expression +---@return string Regular expression +--- +---@private +local function glob2regpat(glob) + local placeholder = '@@PLACEHOLDER@@' + return ( + string.gsub( + vim.fn.glob2regpat( + vim.fn.substitute( + string.gsub(glob, '{(%d+)%.%.(%d+)}', '[%1-%2]'), + '\\*\\@