summaryrefslogtreecommitdiff
path: root/edconf.lua
diff options
context:
space:
mode:
authorMike Vink <59492084+ivi-vink@users.noreply.github.com>2025-01-29 14:34:17 +0000
committerMike Vink <59492084+ivi-vink@users.noreply.github.com>2025-01-29 14:34:17 +0000
commit9f4b260f6d71fdaa8690fa98426a6e5b1cb4939b (patch)
treeed504fc95805d357fb088fd00b498039b73a5fc5 /edconf.lua
Squashed 'mut/vis/vis-editorconfig/' content from commit d2e5f6f
git-subtree-dir: mut/vis/vis-editorconfig git-subtree-split: d2e5f6f911ce03d17985a2a5e554d81263807181
Diffstat (limited to 'edconf.lua')
-rw-r--r--edconf.lua208
1 files changed, 208 insertions, 0 deletions
diff --git a/edconf.lua b/edconf.lua
new file mode 100644
index 0000000..7c92f4d
--- /dev/null
+++ b/edconf.lua
@@ -0,0 +1,208 @@
+require "vis"
+local ec = require "editorconfig"
+local M = {}
+
+-- Simple wrapper
+local function vis_set(option, value)
+ if type(value) == "boolean" then
+ if value then
+ value = "yes"
+ else
+ value = "no"
+ end
+ end
+
+ vis:command("set " .. option .. " " .. value)
+end
+
+local function set_pre_save(f, value)
+ if value == "true" then
+ vis.events.subscribe(vis.events.FILE_SAVE_PRE, f)
+ else
+ vis.events.unsubscribe(vis.events.FILE_SAVE_PRE, f)
+ end
+end
+
+local function set_file_open(f, value)
+ if value == "true" then
+ vis.events.subscribe(vis.events.FILE_OPEN, f)
+ else
+ vis.events.unsubscribe(vis.events.FILE_OPEN, f)
+ end
+end
+
+-- Custom functionality
+M.hooks_enabled = false
+vis:option_register("edconfhooks", "bool", function(value)
+ M.hooks_enabled = value
+end, "Enable optional pre-save-hooks for certain editorconfig settings")
+
+local function insert_final_newline(file)
+ -- Technically speaking, this is a pre-save-hook as well and could
+ -- therefore respect edconf_hooks_enabled. Since this function runs
+ -- blazingly fast and scales with a complexity of O(1), however,
+ -- there is no need to disable it.
+ if file.size > 0 and file:content(file.size-1, 1) ~= '\n' then
+ file:insert(file.size, '\n')
+ end
+end
+
+local function strip_final_newline(file)
+ -- In theory, this would have a complexity of O(n) as well and could
+ -- thus be made optional via edconf_hooks_enabled. On the other hand,
+ -- this is probably a very rare edge case, so stripping all trailing
+ -- newline characters is probably safe enough.
+ while file:content(file.size-1, 1) == '\n' do
+ file:delete(file.size-1, 1)
+ end
+end
+
+local function trim_trailing_whitespace(file)
+ if not M.hooks_enabled then return end
+ for i=1, #file.lines do
+ if string.match(file.lines[i], '[ \t]$') then
+ file.lines[i] = string.gsub(file.lines[i], '[ \t]*$', '')
+ end
+ end
+end
+
+local function enforce_crlf_eol(file)
+ if not M.hooks_enabled then return end
+ for i=1, #file.lines do
+ if not string.match(file.lines[i], '\r$') then
+ file.lines[i] = string.gsub(file.lines[i], '$', '\r')
+ end
+ end
+end
+
+local function enforce_lf_eol(file)
+ if not M.hooks_enabled then return end
+ for i=1, #file.lines do
+ if string.match(file.lines[i], '\r$') then
+ file.lines[i] = string.gsub(file.lines[i], '\r$', '')
+ end
+ end
+end
+
+M.max_line_length = 80 -- This is ugly, but we do want to use
+ -- single function that we can register
+ -- or unregister as needed
+local function max_line_length(file)
+ if not M.hooks_enabled then return end
+ local overlong_lines = {}
+ for i=1, #file.lines do
+ if string.len(file.lines[i]) > M.max_line_length then
+ table.insert(overlong_lines, i)
+ end
+ end
+ if #overlong_lines > 0 then
+ local lines_are = (function(x)
+ if x>1 then return "lines are" else return "line is" end
+ end)(#overlong_lines)
+ vis:info(string.format(
+ "%d %s longer than %d characters: %s",
+ #overlong_lines, lines_are, M.max_line_length,
+ table.concat(overlong_lines, ",")
+ ))
+ end
+end
+
+local OPTIONS = {
+ indent_style = function (value)
+ vis_set("expandtab", (value == "space"))
+ end,
+
+ indent_size = function (value)
+ if value ~= "tab" then -- tab_width is a synonym anyway
+ vis_set("tabwidth", value)
+ end
+ end,
+
+ tab_width = function (value)
+ vis_set("tabwidth", value)
+ end,
+
+ spelling_language = function (value, file)
+ file.spelling_language = value
+ end,
+
+ insert_final_newline = function (value)
+ -- According to the editorconfig specification, insert_final_newline
+ -- false is supposed to mean stripping the final newline, if present.
+ -- See https://editorconfig-specification.readthedocs.io/#supported-pairs
+ --
+ -- Quote: insert_final_newline Set to true ensure file ends with a
+ -- newline when saving and false to ensure it doesn’t.
+ --
+ set_pre_save(insert_final_newline, tostring(value == "true"))
+ set_pre_save(strip_final_newline, tostring(value == "false"))
+ end,
+
+ trim_trailing_whitespace = function (value)
+ set_pre_save(trim_trailing_whitespace, value)
+ end,
+
+ -- End of line is only partially implemented. While vis does not
+ -- support customized newlines, it does work well enough with crlf
+ -- newlines. Therefore, setting end_of_line=crlf will just ensure
+ -- that there is a cr at the end of each line. Setting end_of_line=lf
+ -- will strip any cr characters at the end of lines. This hopefully
+ -- eases the pain of working with crlf files a little.
+ end_of_line = function (value)
+ set_pre_save(enforce_crlf_eol, tostring(value == "crlf"))
+ set_pre_save(enforce_lf_eol, tostring(value == "lf"))
+ end,
+
+ -- There is probably no straightforward way to enforce a maximum line
+ -- length across different programming languages. If a maximum line
+ -- length is set, we can at least issue a warning, however.
+ max_line_length = function(value)
+ if value ~= "off" then
+ M.max_line_length = tonumber(value)
+ end
+ set_pre_save(max_line_length, tostring(value ~= "off"))
+ end,
+
+ -- Not supported by vis
+ -- charset
+ -- Partial support
+ -- end_of_line
+ -- max_line_length
+}
+
+-- Compatible with editorconfig-core-lua v0.3.0
+local function ec_iter(p)
+ local i = 0
+ local props, keys = ec.parse(p)
+ local n = #keys
+ return function ()
+ i = i + 1
+ if i <= n then
+ return keys[i], props[keys[i]]
+ end
+ end
+end
+
+local function ec_set_values(win)
+ if not win or not win.file or not win.file.path then return end
+ for name, value in ec_iter(win.file.path) do
+ if OPTIONS[name] then
+ OPTIONS[name](value, win.file)
+ end
+ end
+end
+
+
+vis:command_register("econfig_parse", function()
+ ec_set_values(vis.win)
+end, "(Re)parse an editorconfig file")
+
+vis.events.subscribe(vis.events.WIN_OPEN, function (win)
+ ec_set_values(win)
+end)
+
+vis.events.subscribe(vis.events.FILE_SAVE_POST, function()
+ ec_set_values(vis.win)
+end)
+
+return M