diff options
| -rw-r--r-- | .editorconfig | 5 | ||||
| -rw-r--r-- | README.md | 89 | ||||
| -rw-r--r-- | edconf.lua | 208 | ||||
| -rw-r--r-- | init.lua | 4 |
4 files changed, 306 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d60c879 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.lua] +indent_style = space +indent_size = 2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b9b72c --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# vis-editorconfig + +A [vis][vis] plugin for [editorconfig][ec]. + +[vis]: https://github.com/martanne/vis +[ec]: http://editorconfig.org/ + +## Installation + +You'll need the Lua wrapper for editorconfig-core installed. This can +be done through luarocks: `luarocks install editorconfig-core` + +```shell +git clone https://github.com/seifferth/vis-editorconfig "$HOME/.config/vis/edconf" +``` + +Then add `require('edconf')` to your `visrc.lua`. + +You may use a different name for the local repository, if you like. +You could, for instance, use `$HOME/.config/vis/vis-editorconfig` and +`require('vis-editorconfig')`. Note, however, that the repository **must +not** be called `editorconfig`. Since the editorconfig-core lua library +is also called `editorconfig`, naming the repository `editorconfig` +will cause name conflicts that result in infinite recursion. + +## Functionality + +Not all editorconfig functionality is supported by vis and hence by this +plugin. At this moment, there is full support for the following settings: + +- indent_style +- indent_size +- tab_width +- insert_final_newline + +The following settings are implemented partially and / or support is +turned off by default: + +- spell_language: This is not yet part of the editorconfig specification + (cf. <https://github.com/editorconfig/editorconfig/issues/315>), but + it is implemented anyway. Since vis does not support spellchecking + natively, this plugin will only set `vis.win.file.spell_language` to + the specified value. It is then up to the spellchecking plugin to + respect that variable. +- trim_trailing_whitespace: Turned off by default, can be enabled + via `:set edconfhooks on`. +- end_of_line: Turned off by default, partial support can be enabled + via `:set edconfhooks on`. Only `crlf` and `lf` are supported, plain + `cr` is not. The implementation is also very basic. If end_of_line + is set to `crlf`, a return character will be inserted at the end of + each line that does not yet end with `crlf`. If end_of_line is set + to `lf`, return characters at the end of a line will be stripped. + While I would encourage every vis user to stick to `lf` terminated + files, this might be convenient if, for some reason, they do need + to compose `crlf` terminated files. +- max_line_length: Turned off by default, partial support can be + enabled via `:set edconfhooks on`. There is no straightforward way + to automatically wrap content that might be written in arbitrary + programming (or non-programming) languages. For that reason, + vis-editorconfig doesn't even try. If max_line_length is enabled, + vis-editorconfig issues a warning, however, indicating which lines + are longer than the specified length. Note that you will miss this + warning if you write your file with `:wq`, so if you care about it, + write it with `:w<RETURN>:q`. + +The reason those last three settings are optional and turned off by +default is their scalability. Each of those operations is implemented +as a pre-save-hook with a complexity of O(n), where n is the filesize. +Since vis is incredibly good at editing huge files efficiently, there +seems to be a very real danger that those hooks could cause the editor +to freeze just before a user's valuable changes are written to disk. + +You can turn support for those pre-save-hooks on or off at any time +by running + + :set edconfhooks on + +or + + :set edconfhooks off + +If `edconfhooks` are enabled, they will be executed as configured in +`.editorconfig`. If you want to take a less cautious approach and enable +these hooks by default, simply add an additional line below the module +import in `visrc.lua`: + + require('editorconfig/edconf') + vis:command('set edconfhooks on') -- supposing you did previously + -- require('vis') 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 diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..39291d4 --- /dev/null +++ b/init.lua @@ -0,0 +1,4 @@ +local source_str = debug.getinfo(1, 'S').source:sub(2) +local script_path = source_str:match('(.*/)') + +return dofile(script_path .. 'edconf.lua') |
