From 56ef7344f03b62fc2108972196c6e48fea5737b2 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Thu, 17 Sep 2026 11:17:02 +0200 Subject: [PATCH] Vendor selectionhighlight, bracketmatch, colorpreview, navigate, editorconfig (2.1.8-7) Five more plugins from lite-xl-plugins @ 31eef29, all default VSCode behaviors: - selectionhighlight: highlights other occurrences of the selected word - bracketmatch: highlights the bracket matching the one under the cursor - colorpreview: inline swatches for #hex/rgb()/hsl() color literals - navigate: Go Back / Go Forward cursor history (alt+left/right) - editorconfig: applies .editorconfig files; vendored as the whole upstream directory (parser + test harness, lite-xl test editorconfig); builds on the bundled trimwhitespace plugin All depend on core only (editorconfig additionally on trimwhitespace, already shipped). Verified against the built package: all plugins load, navigate registers its commands, and an .editorconfig with indent_size = 2 is applied to an opened file. Pinned to lite-xl-plugins @ 31eef29 (see debian/extra/README.source). --- .gitattributes | 4 + debian/changelog | 12 + debian/extra/README.source | 9 +- debian/extra/plugins/bracketmatch.lua | 278 +++++++++ debian/extra/plugins/colorpreview.lua | 88 +++ debian/extra/plugins/editorconfig/README.md | 61 ++ debian/extra/plugins/editorconfig/init.lua | 441 ++++++++++++++ debian/extra/plugins/editorconfig/parser.lua | 553 ++++++++++++++++++ debian/extra/plugins/editorconfig/runtest.lua | 63 ++ .../plugins/editorconfig/tests/glob/braces.in | 71 +++ .../editorconfig/tests/glob/brackets.in | 51 ++ .../plugins/editorconfig/tests/glob/init.lua | 241 ++++++++ .../editorconfig/tests/glob/question.in | 7 + .../plugins/editorconfig/tests/glob/star.in | 12 + .../editorconfig/tests/glob/star_star.in | 15 + .../editorconfig/tests/glob/utf8char.in | 6 + .../extra/plugins/editorconfig/tests/init.lua | 143 +++++ .../editorconfig/tests/parser/basic.in | 16 + .../plugins/editorconfig/tests/parser/bom.in | 6 + .../editorconfig/tests/parser/comments.in | 47 ++ .../tests/parser/comments_and_newlines.in | 4 + .../tests/parser/comments_only.in | 1 + .../plugins/editorconfig/tests/parser/crlf.in | 6 + .../editorconfig/tests/parser/empty.in | 0 .../editorconfig/tests/parser/init.lua | 107 ++++ .../editorconfig/tests/parser/limits.in | 13 + .../tests/parser/newlines_only.in | 2 + .../editorconfig/tests/parser/whitespace.in | 48 ++ .../tests/properties/indent_size_default.in | 11 + .../editorconfig/tests/properties/init.lua | 42 ++ .../tests/properties/lowercase_names.in | 6 + .../tests/properties/lowercase_values.in | 15 + .../tests/properties/tab_width_default.in | 9 + debian/extra/plugins/navigate.lua | 170 ++++++ debian/extra/plugins/selectionhighlight.lua | 47 ++ debian/install | 5 + 36 files changed, 2606 insertions(+), 4 deletions(-) create mode 100644 debian/extra/plugins/bracketmatch.lua create mode 100644 debian/extra/plugins/colorpreview.lua create mode 100644 debian/extra/plugins/editorconfig/README.md create mode 100644 debian/extra/plugins/editorconfig/init.lua create mode 100644 debian/extra/plugins/editorconfig/parser.lua create mode 100644 debian/extra/plugins/editorconfig/runtest.lua create mode 100644 debian/extra/plugins/editorconfig/tests/glob/braces.in create mode 100644 debian/extra/plugins/editorconfig/tests/glob/brackets.in create mode 100644 debian/extra/plugins/editorconfig/tests/glob/init.lua create mode 100644 debian/extra/plugins/editorconfig/tests/glob/question.in create mode 100644 debian/extra/plugins/editorconfig/tests/glob/star.in create mode 100644 debian/extra/plugins/editorconfig/tests/glob/star_star.in create mode 100644 debian/extra/plugins/editorconfig/tests/glob/utf8char.in create mode 100644 debian/extra/plugins/editorconfig/tests/init.lua create mode 100644 debian/extra/plugins/editorconfig/tests/parser/basic.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/bom.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/comments.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/comments_and_newlines.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/comments_only.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/crlf.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/empty.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/init.lua create mode 100644 debian/extra/plugins/editorconfig/tests/parser/limits.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/newlines_only.in create mode 100644 debian/extra/plugins/editorconfig/tests/parser/whitespace.in create mode 100644 debian/extra/plugins/editorconfig/tests/properties/indent_size_default.in create mode 100644 debian/extra/plugins/editorconfig/tests/properties/init.lua create mode 100644 debian/extra/plugins/editorconfig/tests/properties/lowercase_names.in create mode 100644 debian/extra/plugins/editorconfig/tests/properties/lowercase_values.in create mode 100644 debian/extra/plugins/editorconfig/tests/properties/tab_width_default.in create mode 100644 debian/extra/plugins/navigate.lua create mode 100644 debian/extra/plugins/selectionhighlight.lua diff --git a/.gitattributes b/.gitattributes index 8cb51a8..bd563a6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,7 @@ winlib/* linguist-vendored src/lib/* linguist-vendored resources/icons/icon.inl linguist-vendored + +# Vendored test fixtures must keep their original bytes (crlf.in tests +# CRLF handling; normalizing it would corrupt the fixture). +debian/extra/plugins/editorconfig/tests/** -text diff --git a/debian/changelog b/debian/changelog index 5feb73d..0c9fca9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +lite-xl (2.1.8-7) stonking; urgency=medium + + * Vendor five more plugins from lite-xl-plugins @ 31eef29: + - selectionhighlight: highlights other occurrences of the selected word + - bracketmatch: highlights the bracket matching the one under the cursor + - colorpreview: draws inline swatches for #hex/rgb()/hsl() color literals + - navigate: Go Back / Go Forward cursor history (alt+left/right) + - editorconfig: applies .editorconfig files (ships its parser and test + harness; uses the bundled trimwhitespace plugin) + + -- Valentin Haudiquet Thu, 17 Sep 2026 11:30:00 +0200 + lite-xl (2.1.8-6) stonking; urgency=medium * Vendor ephemeral_tabs (lite-xl-plugins @ 31eef29): files opened from the diff --git a/debian/extra/README.source b/debian/extra/README.source index ea613cb..dfae461 100644 --- a/debian/extra/README.source +++ b/debian/extra/README.source @@ -1,10 +1,11 @@ Vendored from upstream, pinned by commit: - lite-xl-plugins https://github.com/lite-xl/lite-xl-plugins @ 31eef29 (master, 2026-09) - plugins/: ephemeral_tabs (VSCode-style preview tabs), gitstatus, gitopen, - fontconfig, open_ext, select_colorscheme, - smoothcaret, nerdicons, language_* (all 104; none overlap the ones bundled - in the core data/ directory) + plugins/: ephemeral_tabs (VSCode-style preview tabs), selectionhighlight, + bracketmatch, colorpreview, navigate, editorconfig (whole directory, + incl. its test harness), gitstatus, gitopen, fontconfig, open_ext, + select_colorscheme, smoothcaret, nerdicons, language_* (all 104; none + overlap the ones bundled in the core data/ directory) - lite-xl-gitpanel (own plugin, source tree lite-xl-gitpanel) @ 0731a63 (main, 2026-09) plugins/: gitpanel -- git panel integrated into the treeview pane (changes, staging, commits, history) with a toolbar toggle button; diff --git a/debian/extra/plugins/bracketmatch.lua b/debian/extra/plugins/bracketmatch.lua new file mode 100644 index 0000000..4565f4f --- /dev/null +++ b/debian/extra/plugins/bracketmatch.lua @@ -0,0 +1,278 @@ +--- mod-version:3 +local core = require "core" +local style = require "core.style" +local command = require "core.command" +local keymap = require "core.keymap" +local DocView = require "core.docview" +local config = require "core.config" +local common = require "core.common" + +-- Colors can be configured as follows: +-- underline color = `style.bracketmatch_color` +-- bracket color = `style.bracketmatch_char_color` +-- background color = `style.bracketmatch_block_color` +-- frame color = `style.bracketmatch_frame_color` + +config.plugins.bracketmatch = common.merge({ + -- highlight the current bracket too + highlight_both = true, + -- can be "underline", "block", "frame", "none" + style = "underline", + -- color the bracket + color_char = false, + -- the size of the lines used in "underline" and "frame" + line_size = math.ceil(1 * SCALE), + -- The config specification used by the settings gui + config_spec = { + name = "Bracket Match", + { + label = "Highlight Both", + description = "Highlight the current bracket too.", + path = "highlight_both", + type = "toggle", + default = true + }, + { + label = "Style", + description = "The visual indicator for pair brackets.", + path = "style", + type = "selection", + default = "underline", + values = { + {"Underline", "underline"}, + {"Block", "block"}, + {"Frame", "frame"}, + {"None", "none"} + } + }, + { + label = "Colorize Bracket", + description = "Change the color of the matching brackets.", + path = "color_char", + type = "toggle", + default = false + }, + { + label = "Line Size", + description = "Height of the underline on matching brackets.", + path = "line_size", + type = "number", + default = 1, + min = 1, + step = 1, + get_value = function(value) + return math.floor(value / SCALE) + end, + set_value = function(value) + return math.ceil(value * SCALE) + end + } + } +}, config.plugins.bracketmatch) + + +local bracket_maps = { + -- [ ] ( ) { } + { [91] = 93, [40] = 41, [123] = 125, direction = 1 }, + -- ] [ ) ( } { + { [93] = 91, [41] = 40, [125] = 123, direction = -1 }, +} + + +local function get_token_at(doc, line, col) + local column = 0 + for _,type,text in doc.highlighter:each_token(line) do + column = column + #text + if column >= col then return type, text end + end +end + + +local function get_matching_bracket(doc, line, col, line_limit, open_byte, close_byte, direction) + local end_line = line + line_limit * direction + local depth = 0 + + while line ~= end_line do + local byte = doc.lines[line]:byte(col) + if byte == open_byte and get_token_at(doc, line, col) ~= "comment" then + depth = depth + 1 + elseif byte == close_byte and get_token_at(doc, line, col) ~= "comment" then + depth = depth - 1 + if depth == 0 then return line, col end + end + + local prev_line, prev_col = line, col + line, col = doc:position_offset(line, col, direction) + if line == prev_line and col == prev_col then + break + end + end +end + + +local state = {} +local select_adj = 0 + +local function update_state(line_limit) + line_limit = line_limit or math.huge + + -- reset if we don't have a document (eg. DocView isn't focused) + local doc = core.active_view.doc + if not doc then + state = {} + return + end + + -- early exit if nothing has changed since the last call + local line, col = doc:get_selection() + local change_id = doc:get_change_id() + if state.doc == doc and state.line == line and state.col == col + and state.change_id == change_id and state.limit == line_limit then + return + end + + -- find matching bracket if we're on a bracket + local line2, col2 + for _, map in ipairs(bracket_maps) do + for i = 0, -1, -1 do + local line, col = doc:position_offset(line, col, i) + local open = doc.lines[line]:byte(col) + local close = map[open] + if close and get_token_at(doc, line, col) ~= "comment" then + -- i == 0 if the cursor is on the left side of a bracket (or -1 when on right) + select_adj = i + 1 -- if i == 0 then select_adj = 1 else select_adj = 0 end + line2, col2 = get_matching_bracket(doc, line, col, line_limit, open, close, map.direction) + goto found + end + end + end + ::found:: + + -- update + state = { + change_id = change_id, + doc = doc, + line = line, + col = col, + line2 = line2, + col2 = col2, + limit = line_limit, + } +end + + +local update = DocView.update + +function DocView:update(...) + update(self, ...) + update_state(100) +end + + +local function redraw_char(dv, x, y, line, col, bg_color, char_color) + local x1 = x + dv:get_col_x_offset(line, col) + local x2 = x + dv:get_col_x_offset(line, col + 1) + local lh = dv:get_line_height() + local token = get_token_at(dv.doc, line, col) + if not char_color then + char_color = style.syntax[token] + end + local font = style.syntax_fonts[token] or dv:get_font() + local char = string.sub(dv.doc.lines[line], col, col) + + if not bg_color then + -- redraw background + core.push_clip_rect(x1, y, x2 - x1, lh) + local dlt = DocView.draw_line_text + DocView.draw_line_text = function() end + dv:draw_line_body(line, x, y) + DocView.draw_line_text = dlt + core.pop_clip_rect() + else + renderer.draw_rect(x1, y, x2 - x1, lh, bg_color) + end + renderer.draw_text(font, char, x1, y + dv:get_line_text_y_offset(), char_color) +end + + +local function draw_decoration(dv, x, y, line, col, width) + local conf = config.plugins.bracketmatch + local color = style.bracketmatch_color or style.syntax["function"] + local char_color = style.bracketmatch_char_color + or (conf.style == "block" and style.background or style.syntax["keyword"]) + local block_color = style.bracketmatch_block_color or style.line_number2 + local frame_color = style.bracketmatch_frame_color or style.line_number2 + + local h = conf.line_size + + if conf.color_char or conf.style == "block" then + for i = 1, width, 1 do + redraw_char(dv, x, y, line, col + i - 1, + conf.style == "block" and block_color, conf.color_char and char_color) + end + end + if conf.style == "underline" then + local x1 = x + dv:get_col_x_offset(line, col) + local x2 = x + dv:get_col_x_offset(line, col + width) + local lh = dv:get_line_height() + + renderer.draw_rect(x1, y + lh - h, x2 - x1, h, color) + elseif conf.style == "frame" then + local x1 = x + dv:get_col_x_offset(line, col) + local x2 = x + dv:get_col_x_offset(line, col + width) + local lh = dv:get_line_height() + + renderer.draw_rect(x1, y + lh - h, x2 - x1, h, frame_color) + renderer.draw_rect(x1, y, x2 - x1, h, frame_color) + renderer.draw_rect(x1, y, h, lh, frame_color) + renderer.draw_rect(x2, y, h, lh, frame_color) + end +end + + +local draw_line_text = DocView.draw_line_text + +function DocView:draw_line_text(line, x, y) + local lh = draw_line_text(self, line, x, y) + local width = 1 + if self.doc == state.doc and state.line2 then + if line == state.line and config.plugins.bracketmatch.highlight_both then + local offset = 0 + if state.line == state.line2 and math.abs(state.col + select_adj - 1 - state.col2) == 1 then + width = 2 + if state.col > state.col2 then + offset = -1 + end + if state.col2 > state.col + select_adj then + offset = 1 + end + end + draw_decoration(self, x, y, line, state.col + select_adj + offset - 1, width) + end + if line == state.line2 and width == 1 then + draw_decoration(self, x, y, line, state.col2, width) + end + end + return lh +end + + +command.add("core.docview", { + ["bracket-match:move-to-matching"] = function(dv) + update_state() + if state.line2 then + dv.doc:set_selection(state.line2, state.col2) + end + end, + ["bracket-match:select-to-matching"] = function(dv) + update_state() + if state.line2 then + dv.doc:set_selection(state.line, state.col, state.line2, state.col2 + select_adj) + end + end, +}) + +keymap.add { + ["ctrl+m"] = "bracket-match:move-to-matching", + ["ctrl+shift+m"] = "bracket-match:select-to-matching", +} diff --git a/debian/extra/plugins/colorpreview.lua b/debian/extra/plugins/colorpreview.lua new file mode 100644 index 0000000..0aa7663 --- /dev/null +++ b/debian/extra/plugins/colorpreview.lua @@ -0,0 +1,88 @@ +-- mod-version:3 +local config = require "core.config" +local common = require "core.common" +local DocView = require "core.docview" + + +config.plugins.colorpreview = common.merge({ + enabled = true, + -- The config specification used by the settings gui + config_spec = { + name = "Color Preview", + { + label = "Enable", + description = "Enable or disable the color preview feature.", + path = "enabled", + type = "toggle", + default = true + } + } +}, config.plugins.colorpreview) + +local white = { common.color "#ffffff" } +local black = { common.color "#000000" } +local tmp = {} + + +local function draw_color_previews(self, line, x, y, ptn, base, nibbles) + local text = self.doc.lines[line] + local s, e = 0, 0 + + while true do + s, e = text:find(ptn, e + 1) + if not s then break end + + local str = text:sub(s, e) + local r, g, b, a = str:match(ptn) + r, g, b = tonumber(r, base), tonumber(g, base), tonumber(b, base) + a = tonumber(a or "", base) + if a ~= nil then + if base ~= 16 then + a = a * 0xff + end + else + a = 0xff + end + + -- #123 becomes #112233 + if nibbles then + r = r * 16 + g = g * 16 + b = b * 16 + end + + local x1 = x + self:get_col_x_offset(line, s) + local x2 = x + self:get_col_x_offset(line, e + 1) + local oy = self:get_line_text_y_offset() + + local text_color = math.max(r, g, b) < 128 and white or black + tmp[1], tmp[2], tmp[3], tmp[4] = r, g, b, a + + local l1, _, l2, _ = self.doc:get_selection(true) + + if not (self.doc:has_selection() and line >= l1 and line <= l2) then + renderer.draw_rect(x1, y, x2 - x1, self:get_line_height(), tmp) + renderer.draw_text(self:get_font(), str, x1, y + oy, text_color) + end + end +end + + +local draw_line_text = DocView.draw_line_text + +function DocView:draw_line_text(line, x, y) + local lh = draw_line_text(self, line, x, y) + if config.plugins.colorpreview.enabled then + draw_color_previews(self, line, x, y, + "#(%x%x)(%x%x)(%x%x)(%x?%x?)%f[%W]", + 16 + ) + -- support #fff css format + draw_color_previews(self, line, x, y, "#(%x)(%x)(%x)%f[%W]", 16, true) + draw_color_previews(self, line, x, y, + "rgba?%((%d+)%D+(%d+)%D+(%d+)[%s,]-([%.%d]-)%s-%)", + nil + ) + end + return lh +end diff --git a/debian/extra/plugins/editorconfig/README.md b/debian/extra/plugins/editorconfig/README.md new file mode 100644 index 0000000..c3abd51 --- /dev/null +++ b/debian/extra/plugins/editorconfig/README.md @@ -0,0 +1,61 @@ +# EditorConfig + +This plugin implements the [EditorConfig](https://editorconfig.org/) spec +purely on lua by leveraging lua patterns and the regex engine on lite-xl. +Installing additional dependencies is not required. + +The EditorConfig spec was implemented as best understood, +if you find any bugs please report them on this repository +[issue tracker](https://github.com/lite-xl/lite-xl-plugins/issues). + +## Implemented Features + +Global options: + +* root - prevents upward searching of .editorconfig files + +Applied to documents indent info: + +* indent_style +* indent_size +* tab_width + +Applied on document save: + +* end_of_line - if set to `cr` it is ignored +* trim_trailing_whitespace +* insert_final_newline boolean + +## Not implemented + +* charset - this feature would need the encoding + [PR](https://github.com/lite-xl/lite-xl/pull/1161) or + [plugin](https://github.com/jgmdev/lite-xl-encoding) + +## Extras + +* Supports multiple project directories +* Implements hot reloading, so modifying an .editorconfig file from within + the editor will re-apply all rules to currently opened files. + +## Testing + +This plugin includes a test suite to check how well the .editorconfig parser +is working. + +The [editorconfig-core-test](https://github.com/editorconfig/editorconfig-core-test) +glob, parser and properties cmake tests where ported and we are getting a 100% +pass rate. + +If you are interested in running the test suite, from the terminal execute +the following: + +```sh +lite-xl test editorconfig +``` + +To inspect the generated sections and regex rules: + +```sh +lite-xl test editorconfig --parsers +``` diff --git a/debian/extra/plugins/editorconfig/init.lua b/debian/extra/plugins/editorconfig/init.lua new file mode 100644 index 0000000..a3df02c --- /dev/null +++ b/debian/extra/plugins/editorconfig/init.lua @@ -0,0 +1,441 @@ +-- mod-version:3 +-- +-- EditorConfig plugin for Lite XL +-- @copyright Jefferson Gonzalez +-- @license MIT +-- +-- Note: this plugin needs to be loaded after detectindent plugin, +-- since the name editorconfig.lua is ordered after detectindent.lua +-- there shouldn't be any issues. Just a reminder for the future in +-- case of a plugin that could also handle document identation type +-- and size, and has a name with more weight than this plugin. +-- +local core = require "core" +local common = require "core.common" +local config = require "core.config" +local trimwhitespace = require "plugins.trimwhitespace" +local Doc = require "core.doc" +local Parser = require "plugins.editorconfig.parser" + +---@class config.plugins.editorconfig +---@field debug boolean +config.plugins.editorconfig = common.merge({ + debug = false, + -- The config specification used by the settings gui + config_spec = { + name = "EditorConfig", + { + label = "Debug", + description = "Display debugging messages on the log.", + path = "debug", + type = "toggle", + default = false + } + } +}, config.plugins.editorconfig) + +---Cache of .editorconfig options to reduce parsing for every opened file. +---@type table +local project_configs = {} + +---Keep track of main project directory so when changed we can assign a new +---.editorconfig object if neccesary. +---@type string +local main_project = core.project_dir + +---Functionality that will be exposed by the plugin. +---@class plugins.editorconfig +local editorconfig = {} + +---Load global .editorconfig options for a project. +---@param project_dir string +---@return boolean loaded +function editorconfig.load(project_dir) + local editor_config = project_dir .. "/" .. ".editorconfig" + local file = io.open(editor_config) + if file then + file:close() + project_configs[project_dir] = Parser.new(editor_config) + return true + end + return false +end + +---Helper to add or substract final new line, it also makes final new line +---visble which lite-xl does not. +---@param doc core.doc +---@param raw? boolean If true does not register change on undo stack +---@return boolean handled_new_line +local function handle_final_new_line(doc, raw) + local handled = false + ---@diagnostic disable-next-line + if doc.insert_final_newline then + handled = true + if doc.lines[#doc.lines] ~= "\n" then + if not raw then + doc:insert(#doc.lines, math.huge, "\n") + else + table.insert(doc.lines, "\n") + end + end + ---@diagnostic disable-next-line + elseif type(doc.insert_final_newline) == "boolean" then + handled = true + if trimwhitespace.trim_empty_end_lines then + trimwhitespace.trim_empty_end_lines(doc, raw) + -- TODO: remove this once 2.1.1 is released + else + for _=#doc.lines, 1, -1 do + local l = #doc.lines + if l > 1 and doc.lines[l] == "\n" then + local current_line = doc:get_selection() + if current_line == l then + doc:set_selection(l-1, math.huge, l-1, math.huge) + end + if not raw then + doc:remove(l-1, math.huge, l, math.huge) + else + table.remove(doc.lines, l) + end + end + end + end + end + return handled +end + +---Split the given relative path by / or \ separators. +---@param path string The path to split +---@return table +local function split_path(path) + local result = {}; + for match in (path.."/"):gmatch("(.-)".."[\\/]") do + table.insert(result, match); + end + return result; +end + +---Check if the given file path exists. +---@param file_path string +local function file_exists(file_path) + local file = io.open(file_path, "r") + if not file then return false end + file:close() + return true +end + +---Merge a config options to target if they don't already exists on target. +---@param config_target? plugins.editorconfig.parser.section +---@param config_from? plugins.editorconfig.parser.section +local function merge_config(config_target, config_from) + if config_target and config_from then + for name, value in pairs(config_from) do + if type(config_target[name]) == "nil" then + config_target[name] = value + end + end + end +end + +---Scan for .editorconfig files from current file path to upper project path +---if root attribute is not found first and returns matching config. +---@param file_path string +---@return plugins.editorconfig.parser.section? +local function recursive_get_config(file_path) + local project_dir = "" + + local root_config + for path, editor_config in pairs(project_configs) do + if common.path_belongs_to(file_path, path) then + project_dir = path + root_config = editor_config:getConfig( + common.relative_path(path, file_path) + ) + break + end + end + + if project_dir == "" then + for _, project in ipairs(core.project_directories) do + if common.path_belongs_to(file_path, project.name) then + project_dir = project.name + break + end + end + end + + local relative_file_path = common.relative_path(project_dir, file_path) + local dir = common.dirname(relative_file_path) + + local editor_config = {} + local config_found = false + if not dir and root_config then + editor_config = root_config + config_found = true + elseif dir then + local path_list = split_path(dir) + local root_found = false + for p=#path_list, 1, -1 do + local path = project_dir .. "/" .. table.concat(path_list, "/", 1, p) + if file_exists(path .. "/" .. ".editorconfig") then + ---@type plugins.editorconfig.parser + local parser = Parser.new(path .. "/" .. ".editorconfig") + local pconfig = parser:getConfig(common.relative_path(path, file_path)) + if pconfig then + merge_config(editor_config, pconfig) + config_found = true + end + if parser.root then + root_found = true + break + end + end + end + if not root_found and root_config then + merge_config(editor_config, root_config) + config_found = true + end + end + + -- clean unset options + if config_found then + local all_unset = true + for name, value in pairs(editor_config) do + if value == "unset" then + editor_config[name] = nil + else + all_unset = false + end + end + if all_unset then config_found = false end + end + + return config_found and editor_config or nil +end + +---Apply editorconfig rules to given doc if possible. +---@param doc core.doc +function editorconfig.apply(doc) + if not doc.abs_filename and not doc.filename then return end + local file_path = doc.abs_filename or (main_project .. "/" .. doc.filename) + local options = recursive_get_config(file_path) + if options then + if config.plugins.editorconfig.debug then + core.log_quiet( + "[EditorConfig]: %s applied %s", + file_path, common.serialize(options, {pretty = true}) + ) + end + local indent_type, indent_size = doc:get_indent_info() + if options.indent_style then + if options.indent_style == "tab" then + indent_type = "hard" + else + indent_type = "soft" + end + end + + if options.indent_size and options.indent_size == "tab" then + if options.tab_width then + options.indent_size = options.tab_width + else + options.indent_size = config.indent_size or 2 + end + end + + if options.indent_size then + indent_size = options.indent_size + end + + if doc.indent_info then + doc.indent_info.type = indent_type + doc.indent_info.size = indent_size + doc.indent_info.confirmed = true + else + doc.indent_info = { + type = indent_type, + size = indent_size, + confirmed = true + } + end + + if options.end_of_line then + if options.end_of_line == "crlf" then + doc.crlf = true + elseif options.end_of_line == "lf" then + doc.crlf = false + end + end + + if options.trim_trailing_whitespace then + doc.trim_trailing_whitespace = true + elseif options.trim_trailing_whitespace == false then + doc.trim_trailing_whitespace = false + else + doc.trim_trailing_whitespace = nil + end + + if options.insert_final_newline then + doc.insert_final_newline = true + elseif options.insert_final_newline == false then + doc.insert_final_newline = false + else + doc.insert_final_newline = nil + end + + if + ( + type(doc.trim_trailing_whitespace) == "boolean" + or + type(doc.insert_final_newline) == "boolean" + ) + -- TODO: remove this once 2.1.1 is released + and + trimwhitespace.disable + then + trimwhitespace.disable(doc) + end + + handle_final_new_line(doc, true) + end +end + +---Applies .editorconfig options to all open documents if possible. +function editorconfig.apply_all() + for _, doc in ipairs(core.docs) do + editorconfig.apply(doc) + end +end + +-------------------------------------------------------------------------------- +-- Load .editorconfig on all projects loaded at startup and apply it +-------------------------------------------------------------------------------- +core.add_thread(function() + local loaded = false + + -- scan all opened project directories + if core.project_directories then + for i=1, #core.project_directories do + local found = editorconfig.load(core.project_directories[i].name) + if found then loaded = true end + end + end + + -- if an editorconfig was found then try to apply it to opened docs + if loaded then + editorconfig.apply_all() + end +end) + +-------------------------------------------------------------------------------- +-- Override various core project loading functions for .editorconfig scanning +-------------------------------------------------------------------------------- +local core_open_folder_project = core.open_folder_project +function core.open_folder_project(directory) + core_open_folder_project(directory) + if project_configs[main_project] then project_configs[main_project] = nil end + main_project = core.project_dir + editorconfig.load(main_project) +end + +local core_remove_project_directory = core.remove_project_directory +function core.remove_project_directory(path) + local out = core_remove_project_directory(path) + if project_configs[path] then project_configs[path] = nil end + return out +end + +local core_add_project_directory = core.add_project_directory +function core.add_project_directory(directory) + local out = core_add_project_directory(directory) + editorconfig.load(directory) + return out +end + +-------------------------------------------------------------------------------- +-- Hook into the core.doc to apply editor config options +-------------------------------------------------------------------------------- +local doc_new = Doc.new +function Doc:new(...) + doc_new(self, ...) + editorconfig.apply(self) +end + +---Cloned trimwitespace plugin until it is exposed for other plugins. +---@param doc core.doc +local function trim_trailing_whitespace(doc) + if trimwhitespace.trim then + trimwhitespace.trim(doc) + return + end + + -- TODO: remove this once 2.1.1 is released + local cline, ccol = doc:get_selection() + for i = 1, #doc.lines do + local old_text = doc:get_text(i, 1, i, math.huge) + local new_text = old_text:gsub("%s*$", "") + + -- don't remove whitespace which would cause the caret to reposition + if cline == i and ccol > #new_text then + new_text = old_text:sub(1, ccol - 1) + end + + if old_text ~= new_text then + doc:insert(i, 1, new_text) + doc:remove(i, #new_text + 1, i, math.huge) + end + end +end + +local doc_save = Doc.save +function Doc:save(...) + local new_file = self.new_file + + ---@diagnostic disable-next-line + if self.trim_trailing_whitespace then + trim_trailing_whitespace(self) + end + + local lc = #self.lines + local handle_new_line = handle_final_new_line(self) + + -- remove the unnecesary visible \n\n or the disabled \n + if handle_new_line then + self.lines[lc] = self.lines[lc]:gsub("\n$", "") + end + + doc_save(self, ...) + + -- restore the visible \n\n or disabled \n + if handle_new_line then + self.lines[lc] = self.lines[lc] .. "\n" + end + + if common.basename(self.abs_filename) == ".editorconfig" then + -- blindlessly reload related project .editorconfig options + for _, project in ipairs(core.project_directories) do + if common.path_belongs_to(self.abs_filename, project.name) then + editorconfig.load(project.name) + break + end + end + -- re-apply editorconfig options to all open files + editorconfig.apply_all() + elseif new_file then + -- apply editorconfig options for file that was previously unsaved + editorconfig.apply(self) + end +end + +-------------------------------------------------------------------------------- +-- Run the test suite if requested on CLI with: lite-xl test editorconfig +-------------------------------------------------------------------------------- +for i, argument in ipairs(ARGS) do + if argument == "test" and ARGS[i+1] == "editorconfig" then + require "plugins.editorconfig.runtest" + os.exit() + end +end + + +return editorconfig diff --git a/debian/extra/plugins/editorconfig/parser.lua b/debian/extra/plugins/editorconfig/parser.lua new file mode 100644 index 0000000..b0ec689 --- /dev/null +++ b/debian/extra/plugins/editorconfig/parser.lua @@ -0,0 +1,553 @@ +-- Lua parser implementation of the .editorconfig spec as best understood. +-- @copyright Jefferson Gonzalez +-- @license MIT + +local core = require "core" +local config = require "core.config" + +local STANDALONE = false +for i, argument in ipairs(ARGS) do + if argument == "test" and ARGS[i+1] == "editorconfig" then + STANDALONE = true + end +end + +---Logger that will output using lite-xl logging functions or print to +---terminal if the parser is running in standalone mode. +---@param type "log" | "error" +---@param format string +---@param ... any +local function log(type, format, ...) + if not STANDALONE then + core[type]("[EditorConfig]: " .. format, ...) + else + print("[" .. type:upper() .. "]: " .. string.format(format, ...)) + end +end + +---Represents an .editorconfig path rule/expression. +---@class plugins.editorconfig.parser.rule +---Path expression as found between square brackets. +---@field expression string | table +---The expression converted to a regex. +---@field regex string | table +---@field regex_compiled any? | table +---@field negation boolean Indicates that the expression is a negation. +---@field ranges table List of ranges found on the expression. + +---Represents a section of the .editorconfig with all its config options. +---@class plugins.editorconfig.parser.section +---@field rule plugins.editorconfig.parser.rule +---@field equivalent_rules plugins.editorconfig.parser.rule[] +---@field indent_style "tab" | "space" +---@field indent_size integer +---@field tab_width integer +---@field end_of_line "lf" | "cr" | "crlf" +---@field charset "latin1" | "utf-8" | "utf-8-bom" | "utf-16be" | "utf-16le" +---@field trim_trailing_whitespace boolean +---@field insert_final_newline boolean + +---EditorConfig parser class and filename config matching. +---@class plugins.editorconfig.parser +---@field config_path string +---@field sections plugins.editorconfig.parser.section[] +---@field root boolean +local Parser = {} +Parser.__index = Parser + +---Constructor +---@param config_path string +---@return plugins.editorconfig.parser +function Parser.new(config_path) + local self = {} + setmetatable(self, Parser) + self.config_path = config_path + self.sections = {} + self.root = false + self:read() + return self +end + +--- char to hex cache and automatic converter +---@type table +local hex_value = {} +setmetatable(hex_value, { + __index = function(t, k) + local v = rawget(t, k) + if v == nil then + v = string.format("%x", string.byte(k)) + rawset(t, k, v) + end + return v + end +}) + +---Simplifies managing rules with other inner rules like {...} which can +---contain escaped \\{ \\} and expressions that are easier handled after +---converting the escaped special characters to \xXX counterparts. +---@param value string +---@return string escaped_values +local function escapes_to_regex_hex(value) + local escaped_chars = {} + for char in value:ugmatch("\\(.)") do + table.insert(escaped_chars, char) + end + for _, char in ipairs(escaped_chars) do + value = value:ugsub("\\" .. char, "\\x" .. hex_value[char]) + end + return value +end + +---An .editorconfig path expression to regex conversion rule. +---@class rule +---@field rule string Lua pattern. +---Callback conversion function. +---@field conversion fun(match:string, section:plugins.editorconfig.parser.section):string + +---List of conversion rules applied to brace expressions. +---@type rule[] +local RULES_BRACES = { + { rule = "^%(", conversion = function() return "\\(" end }, + { rule = "^%)", conversion = function() return "\\)" end }, + { rule = "^%.", conversion = function() return "\\." end }, + { rule = "^\\%[", conversion = function() return "\\[" end }, + { rule = "^\\%]", conversion = function() return "\\]" end }, + { rule = "^\\!", conversion = function() return "!" end }, + { rule = "^\\;", conversion = function() return ";" end }, + { rule = "^\\#", conversion = function() return "#" end }, + { rule = "^\\,", conversion = function() return "," end }, + { rule = "^\\{", conversion = function() return "{" end }, + { rule = "^\\}", conversion = function() return "}" end }, + { rule = "^,", conversion = function() return "|" end }, + { rule = "^\\%*", conversion = function() return "\\*" end }, + { rule = "^%*", conversion = function() return "[^\\/]*" end }, + { rule = "^%*%*", conversion = function() return ".*" end }, + { rule = "^%?", conversion = function() return "." end }, + { rule = "^{}", conversion = function() return "{}" end }, + { rule = "^{[^,]+}", conversion = function(match) return match end }, + { rule = "^%b{}", + conversion = function(match) + local out = match:ugsub("%(", "\\(") + :ugsub("%)", "\\)") + :ugsub("%.", "\\.") + :ugsub("\\%[", "[\\[]") + :ugsub("\\%]", "[\\]]") + :ugsub("^\\!", "!") + :ugsub("^\\;", ";") + :ugsub("^\\#", "#") + -- negation chars list + :ugsub("%[!(%a+)%]", "[^%1]") + :ugsub("\\\\", "[\\]") + -- escaped braces + :ugsub("\\{", "[{]") + :ugsub("\\}", "[}]") + -- non escaped braces + :ugsub("{([^%]])", "(%1") + :ugsub("}([^%]])", ")%1") + :ugsub("^{", "(") + :ugsub("}$", ")") + -- escaped globs + :ugsub("\\%*", "[\\*]") + :ugsub("\\%?", "[\\?]") + -- non escaped globs + :ugsub("%*%*", "[*][*]") -- prevent this glob from expanding to next sub + :ugsub("%*([^%]])", "[^\\/]*%1") + :ugsub("%[%*%]%[%*%]", ".*") + :ugsub("%?([^%]])", ".%1") + -- escaped comma + :ugsub("\\,", "[,]") + -- non escaped comma + :ugsub(",([^%]])", "|%1") + return out + end + }, + { rule = "^%[[^/%]]*%]", + conversion = function(match) + local negation = match:umatch("^%[!") + local chars = match:umatch("^%[!?(.-)%]") + chars = chars:ugsub("^%-", "\\-"):ugsub("%-$", "\\-") + local out = "" + if negation then + out = "[^"..chars.."]" + else + out = "["..chars.."]" + end + return out + end + }, +} + +---List of conversion rules applied to .editorconfig path expressions. +---@type rule[] +local RULES = { + -- normalize escaped .editorconfig special chars or keep them escaped + { rule = "^\\x[a-fA-F][a-fA-F]", conversion = function(match) return match end }, + { rule = "^\\%*", conversion = function() return "\\*" end }, + { rule = "^\\%?", conversion = function() return "\\?" end }, + { rule = "^\\{", conversion = function() return "{" end }, + { rule = "^\\}", conversion = function() return "}" end }, + { rule = "^\\%[", conversion = function() return "\\[" end }, + { rule = "^\\%]", conversion = function() return "\\]" end }, + { rule = "^\\!", conversion = function() return "!" end }, + { rule = "^\\;", conversion = function() return ";" end }, + { rule = "^\\#", conversion = function() return "#" end }, + -- escape special chars + { rule = "^%.", conversion = function() return "\\." end }, + { rule = "^%(", conversion = function() return "\\(" end }, + { rule = "^%)", conversion = function() return "\\)" end }, + { rule = "^%[[^/%]]*%]", + conversion = function(match) + local negation = match:umatch("^%[!") + local chars = match:umatch("^%[!?(.-)%]") + chars = chars:ugsub("^%-", "\\-"):ugsub("%-$", "\\-") + local out = "" + if negation then + out = "[^"..chars.."]" + else + out = "["..chars.."]" + end + return out + end + }, + -- Is this negation rule valid? + { rule = "^!%w+", + conversion = function(match) + local chars = match:umatch("%w+") + return "[^"..chars.."]" + end + }, + -- escape square brackets + { rule = "^%[", conversion = function() return "\\[" end }, + { rule = "^%]", conversion = function() return "\\]" end }, + -- match any characters + { rule = "^%*%*", conversion = function() return ".*" end }, + -- match any characters excluding path separators, \ not needed but just in case + { rule = "^%*", conversion = function() return "[^\\/]*" end }, + -- match optional character, doesn't matters what or should only be a \w? + { rule = "^%?", conversion = function() return "[^/]" end }, + -- threat empty braces literally + { rule = "^{}", conversion = function() return "{}" end }, + -- match a number range + { rule = "^{%-?%d+%.%.%-?%d+}", + conversion = function(match, section) + local min, max = match:umatch("(-?%d+)%.%.(-?%d+)") + min = tonumber(min) + max = tonumber(max) + if min and max then + if not section.rule.ranges then section.rule.ranges = {} end + table.insert(section.rule.ranges, { + math.min(min, max), + math.max(min, max) + }) + end + local minus = "" + if min < 0 or max < 0 then minus = "\\-?" end + return "(? +---@param value string +---@return table +local function regex_result_to_table(offsets, value) + local result = {} + local offset_fix = 0 + if not regex.find_offsets then + offset_fix = 1 + end + for i=3, #offsets, 2 do + table.insert(result, value:sub(offsets[i], offsets[i+1]-offset_fix)) + end + return result +end + +---Get a matching config for the given filename or nil if nothing found. +---@param file_name string +---@param defaults? boolean Set indent size to defaults when needed, +---@return plugins.editorconfig.parser.section? +function Parser:getConfig(file_name, defaults) + if PLATFORM == "Windows" then + file_name = file_name:gsub("\\", "/") + end + + local regex_match = regex.match + if regex.find_offsets then + regex_match = regex.find_offsets + end + + local properties = {} + + local found = false + for _, section in ipairs(self.sections) do + if section.rule.regex_compiled then + local negation = section.rule.negation + -- default rule + local matched = {regex_match(section.rule.regex_compiled, file_name)} + -- try equivalent rules if available + if not matched[1] and section.equivalent_rules then + for _, esection in ipairs(section.equivalent_rules) do + matched = {regex_match(esection.regex_compiled, file_name)} + if matched[1] then + break + end + end + end + if (matched[1] and not negation) or (not matched[1] and negation) then + local ranges_match = true + if section.rule.ranges then + local results = regex_result_to_table(matched, file_name) + if #results < #section.rule.ranges then + ranges_match = false + else + for i, range in ipairs(section.rule.ranges) do + local number = tonumber(results[i]) + if not number then + ranges_match = false + break + end + if number < range[1] or number > range[2] then + ranges_match = false + break + end + end + end + end + if ranges_match then + found = true + for name, value in pairs(section) do + if name ~= "rule" and name ~= "equivalent_rules" then + properties[name] = value + end + end + end + end + end + end + + if found and defaults then + if properties.indent_style and properties.indent_style == "space" then + if properties.indent_size and not properties.tab_width then + properties.tab_width = 4 + end + elseif properties.indent_style and properties.indent_style == "tab" then + if not properties.tab_width and not properties.indent_size then + properties.indent_size = "tab" + elseif properties.tab_width then + properties.indent_size = properties.tab_width + end + end + end + + return found and properties or nil +end + +---Get a matching config for the given filename or nil if nothing found. +---@param file_name string +---@return string +function Parser:getConfigString(file_name) + local out = "" + local properties = self:getConfig(file_name, true) + if properties then + local config_sorted = {} + for name, value in pairs(properties) do + table.insert(config_sorted, {name = name, value = value}) + end + table.sort(config_sorted, function(a, b) + return a.name < b.name + end) + for _, value in ipairs(config_sorted) do + out = out .. value.name .. "=" .. tostring(value.value) .. "\n" + end + end + return out +end + +return Parser diff --git a/debian/extra/plugins/editorconfig/runtest.lua b/debian/extra/plugins/editorconfig/runtest.lua new file mode 100644 index 0000000..85378cd --- /dev/null +++ b/debian/extra/plugins/editorconfig/runtest.lua @@ -0,0 +1,63 @@ +local core = require "core" +local tests = require "plugins.editorconfig.tests" + +-- disable print buffer for immediate output +io.stdout:setvbuf "no" + +-- overwrite to print into stdout +function core.error(format, ...) + print(string.format(format, ...)) +end + +function core.log(format, ...) + print(string.format(format, ...)) +end + +function core.log_quiet(format, ...) + print(string.format(format, ...)) +end + +-- check if --parsers flag was given to only output the path expressions and +-- their conversion into regular expressions. +local PARSERS = false +for _, argument in ipairs(ARGS) do + if argument == "--parsers" then + PARSERS = true + end +end + +if not PARSERS then + require "plugins.editorconfig.tests.glob" + require "plugins.editorconfig.tests.parser" + require "plugins.editorconfig.tests.properties" + + tests.run() +else + -- Globs + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/braces.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/brackets.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/question.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/star.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/star_star.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/utf8char.in") + + -- Parser + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/basic.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/bom.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments_and_newlines.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments_only.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/crlf.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/empty.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/limits.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/newlines_only.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/whitespace.in") + + -- Properties + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/indent_size_default.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/lowercase_names.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/lowercase_values.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/tab_width_default.in") + + tests.run_parsers() +end diff --git a/debian/extra/plugins/editorconfig/tests/glob/braces.in b/debian/extra/plugins/editorconfig/tests/glob/braces.in new file mode 100644 index 0000000..0400aeb --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/braces.in @@ -0,0 +1,71 @@ +; test { and } + +root=true + +; word choice +[*.{py,js,html}] +choice=true + +; single choice +[{single}.b] +choice=single + +; empty choice +[{}.c] +empty=all + +; choice with empty word +[a{b,c,}.d] +empty=word + +; choice with empty words +[a{,b,,c,}.e] +empty=words + +; no closing brace +[{.f] +closing=false + +; nested braces +[{word,{also},this}.g] +nested=true + +; nested braces, adjacent at start +[{{a,b},c}.k] +nested_start=true + +; nested braces, adjacent at end +[{a,{b,c}}.l] +nested_end=true + +; closing inside beginning +[{},b}.h] +closing=inside + +; opening inside beginning +[{{,b,c{d}.i] +unmatched=true + +; escaped comma +[{a\,b,cd}.txt] +comma=yes + +; escaped closing brace +[{e,\},f}.txt] +closing=yes + +; escaped backslash +[{g,\\,i}.txt] +backslash=yes + +; patterns nested in braces +[{some,a{*c,b}[ef]}.j] +patterns=nested + +; numeric braces +[{3..120}] +number=true + +; alphabetical +[{aardvark..antelope}] +words=a diff --git a/debian/extra/plugins/editorconfig/tests/glob/brackets.in b/debian/extra/plugins/editorconfig/tests/glob/brackets.in new file mode 100644 index 0000000..f44def2 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/brackets.in @@ -0,0 +1,51 @@ +; test [ and ] + +root=true + +; Character choice +[[ab].a] +choice=true + +; Negative character choice +[[!ab].b] +choice=false + +; Character range +[[d-g].c] +range=true + +; Negative character range +[[!d-g].d] +range=false + +; Range and choice +[[abd-g].e] +range_and_choice=true + +; Choice with dash +[[-ab].f] +choice_with_dash=true + +; Close bracket inside +[[\]ab].g] +close_inside=true + +; Close bracket outside +[[ab]].g] +close_outside=true + +; Negative close bracket inside +[[!\]ab].g] +close_inside=false + +; Negative¬close bracket outside +[[!ab]].g] +close_outside=false + +; Slash inside brackets +[ab[e/]cd.i] +slash_inside=true + +; Slash after an half-open bracket +[ab[/c] +slash_half_open=true diff --git a/debian/extra/plugins/editorconfig/tests/glob/init.lua b/debian/extra/plugins/editorconfig/tests/glob/init.lua new file mode 100644 index 0000000..f1214c3 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/init.lua @@ -0,0 +1,241 @@ +local tests = require "plugins.editorconfig.tests" + +-- Tests for * + +-- matches a single characters +tests.add("star_single_ML", "glob/star.in", "ace.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- matches zero characters +tests.add("star_zero_ML", "glob/star.in", "ae.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- matches multiple characters +tests.add("star_multiple_ML", "glob/star.in", "abcde.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- does not match path separator +tests.add("star_over_slash", "glob/star.in", "a/e.c", "^[ \t\n\r]*keyc=valuec[ \t\n\r]*$") + +-- star after a slash +tests.add("star_after_slash_ML", "glob/star.in", "Bar/foo.txt", "keyb=valueb[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- star matches a dot file after slash +tests.add("star_matches_dot_file_after_slash_ML", "glob/star.in", "Bar/.editorconfig", "keyb=valueb[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- star matches a dot file +tests.add("star_matches_dot_file", "glob/star.in", ".editorconfig", "^keyc=valuec[ \t\n\r]*$") + +-- Tests for ? + +-- matches a single character +tests.add("question_single", "glob/question.in", "some.c", "^key=value[ \t\n\r]*$") + +-- does not match zero characters +tests.add("question_zero", "glob/question.in", "som.c", "^[ \t\n\r]*$") + +-- does not match multiple characters +tests.add("question_multiple", "glob/question.in", "something.c", "^[ \t\n\r]*$") + +-- does not match slash +tests.add("question_slash", "glob/question.in", "som/.c", "^[ \t\n\r]*$") + +-- Tests for [ and ] + +-- close bracket inside +tests.add("brackets_close_inside", "glob/brackets.in", "].g", "^close_inside=true[ \t\n\r]*$") + +-- close bracket outside +tests.add("brackets_close_outside", "glob/brackets.in", "b].g", "^close_outside=true[ \t\n\r]*$") + +-- negative close bracket inside +tests.add("brackets_nclose_inside", "glob/brackets.in", "c.g", "^close_inside=false[ \t\n\r]*$") + +-- negative close bracket outside +tests.add("brackets_nclose_outside", "glob/brackets.in", "c].g", "^close_outside=false[ \t\n\r]*$") + +-- character choice +tests.add("brackets_choice", "glob/brackets.in", "a.a", "^choice=true[ \t\n\r]*$") + +-- character choice 2 +tests.add("brackets_choice2", "glob/brackets.in", "c.a", "^[ \t\n\r]*$") + +-- negative character choice +tests.add("brackets_nchoice", "glob/brackets.in", "c.b", "^choice=false[ \t\n\r]*$") + +-- negative character choice 2 +tests.add("brackets_nchoice2", "glob/brackets.in", "a.b", "^[ \t\n\r]*$") + +-- character range +tests.add("brackets_range", "glob/brackets.in", "f.c", "^range=true[ \t\n\r]*$") + +-- character range 2 +tests.add("brackets_range2", "glob/brackets.in", "h.c", "^[ \t\n\r]*$") + +-- negative character range +tests.add("brackets_nrange", "glob/brackets.in", "h.d", "^range=false[ \t\n\r]*$") + +-- negative character range 2 +tests.add("brackets_nrange2", "glob/brackets.in", "f.d", "^[ \t\n\r]*$") + +-- range and choice +tests.add("brackets_range_and_choice", "glob/brackets.in", "e.e", + "^range_and_choice=true[ \t\n\r]*$") + +-- character choice with a dash +tests.add("brackets_choice_with_dash", "glob/brackets.in", "-.f", + "^choice_with_dash=true[ \t\n\r]*$") + +-- slash inside brackets +tests.add("brackets_slash_inside1", "glob/brackets.in", "ab/cd.i", + "^[ \t\n\r]*$") +tests.add("brackets_slash_inside2", "glob/brackets.in", "abecd.i", + "^[ \t\n\r]*$") +tests.add("brackets_slash_inside3", "glob/brackets.in", "ab[e/]cd.i", + "^slash_inside=true[ \t\n\r]*$") +tests.add("brackets_slash_inside4", "glob/brackets.in", "ab[/c", + "^slash_half_open=true[ \t\n\r]*$") + +-- Tests for { and } + +-- word choice +tests.add("braces_word_choice1", "glob/braces.in", "test.py", "^choice=true[ \t\n\r]*$") +tests.add("braces_word_choice2", "glob/braces.in", "test.js", "^choice=true[ \t\n\r]*$") +tests.add("braces_word_choice3", "glob/braces.in", "test.html", "^choice=true[ \t\n\r]*$") +tests.add("braces_word_choice4", "glob/braces.in", "test.pyc", "^[ \t\n\r]*$") + +-- single choice +tests.add("braces_single_choice", "glob/braces.in", "{single}.b", "^choice=single[ \t\n\r]*$") +tests.add("braces_single_choice_negative", "glob/braces.in", ".b", "^[ \t\n\r]*$") + +-- empty choice +tests.add("braces_empty_choice", "glob/braces.in", "{}.c", "^empty=all[ \t\n\r]*$") +tests.add("braces_empty_choice_negative", "glob/braces.in", ".c", "^[ \t\n\r]*$") + +-- choice with empty word +tests.add("braces_empty_word1", "glob/braces.in", "a.d", "^empty=word[ \t\n\r]*$") +tests.add("braces_empty_word2", "glob/braces.in", "ab.d", "^empty=word[ \t\n\r]*$") +tests.add("braces_empty_word3", "glob/braces.in", "ac.d", "^empty=word[ \t\n\r]*$") +tests.add("braces_empty_word4", "glob/braces.in", "a,.d", "^[ \t\n\r]*$") + +-- choice with empty words +tests.add("braces_empty_words1", "glob/braces.in", "a.e", "^empty=words[ \t\n\r]*$") +tests.add("braces_empty_words2", "glob/braces.in", "ab.e", "^empty=words[ \t\n\r]*$") +tests.add("braces_empty_words3", "glob/braces.in", "ac.e", "^empty=words[ \t\n\r]*$") +tests.add("braces_empty_words4", "glob/braces.in", "a,.e", "^[ \t\n\r]*$") + +-- no closing brace +tests.add("braces_no_closing", "glob/braces.in", "{.f", "^closing=false[ \t\n\r]*$") +tests.add("braces_no_closing_negative", "glob/braces.in", ".f", "^[ \t\n\r]*$") + +-- nested braces +tests.add("braces_nested1", "glob/braces.in", "word,this}.g", "^[ \t\n\r]*$") +tests.add("braces_nested2", "glob/braces.in", "{also,this}.g", "^[ \t\n\r]*$") +tests.add("braces_nested3", "glob/braces.in", "word.g", "^nested=true[ \t\n\r]*$") +tests.add("braces_nested4", "glob/braces.in", "{also}.g", "^nested=true[ \t\n\r]*$") +tests.add("braces_nested5", "glob/braces.in", "this.g", "^nested=true[ \t\n\r]*$") + +-- nested braces, adjacent at start +tests.add("braces_nested_start1", "glob/braces.in", "{{a,b},c}.k", "^[ \t\n\r]*$") +tests.add("braces_nested_start2", "glob/braces.in", "{a,b}.k", "^[ \t\n\r]*$") +tests.add("braces_nested_start3", "glob/braces.in", "a.k", "^nested_start=true[ \t\n\r]*$") +tests.add("braces_nested_start4", "glob/braces.in", "b.k", "^nested_start=true[ \t\n\r]*$") +tests.add("braces_nested_start5", "glob/braces.in", "c.k", "^nested_start=true[ \t\n\r]*$") + +-- nested braces, adjacent at end +tests.add("braces_nested_end1", "glob/braces.in", "{a,{b,c}}.l", "^[ \t\n\r]*$") +tests.add("braces_nested_end2", "glob/braces.in", "{b,c}.l", "^[ \t\n\r]*$") +tests.add("braces_nested_end3", "glob/braces.in", "a.l", "^nested_end=true[ \t\n\r]*$") +tests.add("braces_nested_end4", "glob/braces.in", "b.l", "^nested_end=true[ \t\n\r]*$") +tests.add("braces_nested_end5", "glob/braces.in", "c.l", "^nested_end=true[ \t\n\r]*$") + +-- closing inside beginning +tests.add("braces_closing_in_beginning", "glob/braces.in", "{},b}.h", "^closing=inside[ \t\n\r]*$") + +-- missing closing braces +tests.add("braces_unmatched1", "glob/braces.in", "{{,b,c{d}.i", "^unmatched=true[ \t\n\r]*$") +tests.add("braces_unmatched2", "glob/braces.in", "{.i", "^[ \t\n\r]*$") +tests.add("braces_unmatched3", "glob/braces.in", "b.i", "^[ \t\n\r]*$") +tests.add("braces_unmatched4", "glob/braces.in", "c{d.i", "^[ \t\n\r]*$") +tests.add("braces_unmatched5", "glob/braces.in", ".i", "^[ \t\n\r]*$") + +-- escaped comma +tests.add("braces_escaped_comma1", "glob/braces.in", "a,b.txt", "^comma=yes[ \t\n\r]*$") +tests.add("braces_escaped_comma2", "glob/braces.in", "a.txt", "^[ \t\n\r]*$") +tests.add("braces_escaped_comma3", "glob/braces.in", "cd.txt", "^comma=yes[ \t\n\r]*$") + +-- escaped closing brace +tests.add("braces_escaped_brace1", "glob/braces.in", "e.txt", "^closing=yes[ \t\n\r]*$") +tests.add("braces_escaped_brace2", "glob/braces.in", "}.txt", "^closing=yes[ \t\n\r]*$") +tests.add("braces_escaped_brace3", "glob/braces.in", "f.txt", "^closing=yes[ \t\n\r]*$") + +-- escaped backslash +tests.add("braces_escaped_backslash1", "glob/braces.in", "g.txt", "^backslash=yes[ \t\n\r]*$") +if PLATFORM ~= "Windows" then +tests.add("braces_escaped_backslash2", "glob/braces.in", "\\.txt", "^backslash=yes[ \t\n\r]*$") +end +tests.add("braces_escaped_backslash3", "glob/braces.in", "i.txt", "^backslash=yes[ \t\n\r]*$") + +-- patterns nested in braces +tests.add("braces_patterns_nested1", "glob/braces.in", "some.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested2", "glob/braces.in", "abe.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested3", "glob/braces.in", "abf.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested4", "glob/braces.in", "abg.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested5", "glob/braces.in", "ace.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested6", "glob/braces.in", "acf.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested7", "glob/braces.in", "acg.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested8", "glob/braces.in", "abce.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested9", "glob/braces.in", "abcf.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested10", "glob/braces.in", "abcg.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested11", "glob/braces.in", "ae.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested12", "glob/braces.in", ".j", "^[ \t\n\r]*$") + +-- numeric brace range +tests.add("braces_numeric_range1", "glob/braces.in", "1", "^[ \t\n\r]*$") +tests.add("braces_numeric_range2", "glob/braces.in", "3", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range3", "glob/braces.in", "15", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range4", "glob/braces.in", "60", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range5", "glob/braces.in", "5a", "^[ \t\n\r]*$") +tests.add("braces_numeric_range6", "glob/braces.in", "120", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range7", "glob/braces.in", "121", "^[ \t\n\r]*$") +tests.add("braces_numeric_range8", "glob/braces.in", "060", "^[ \t\n\r]*$") + +-- alphabetical brace range: letters should not be considered for ranges +tests.add("braces_alpha_range1", "glob/braces.in", "{aardvark..antelope}", "^words=a[ \t\n\r]*$") +tests.add("braces_alpha_range2", "glob/braces.in", "a", "^[ \t\n\r]*$") +tests.add("braces_alpha_range3", "glob/braces.in", "aardvark", "^[ \t\n\r]*$") +tests.add("braces_alpha_range4", "glob/braces.in", "agreement", "^[ \t\n\r]*$") +tests.add("braces_alpha_range5", "glob/braces.in", "antelope", "^[ \t\n\r]*$") +tests.add("braces_alpha_range6", "glob/braces.in", "antimatter", "^[ \t\n\r]*$") + + +-- Tests for ** + +-- test EditorConfig files with UTF-8 characters larger than 127 +tests.add("utf_8_char", "glob/utf8char.in", "中文.txt", "^key=value[ \t\n\r]*$") + +-- matches over path separator +tests.add("star_star_over_separator1", "glob/star_star.in", "a/z.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator2", "glob/star_star.in", "amnz.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator3", "glob/star_star.in", "am/nz.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator4", "glob/star_star.in", "a/mnz.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator5", "glob/star_star.in", "amn/z.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator6", "glob/star_star.in", "a/mn/z.c", "^key1=value1[ \t\n\r]*$") + +tests.add("star_star_over_separator7", "glob/star_star.in", "b/z.c", "^key2=value2[ \t\n\r]*$") +tests.add("star_star_over_separator8", "glob/star_star.in", "b/mnz.c", "^key2=value2[ \t\n\r]*$") +tests.add("star_star_over_separator9", "glob/star_star.in", "b/mn/z.c", "^key2=value2[ \t\n\r]*$") +tests.add("star_star_over_separator10", "glob/star_star.in", "bmnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator11", "glob/star_star.in", "bm/nz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator12", "glob/star_star.in", "bmn/z.c", "^[ \t\n\r]*$") + +tests.add("star_star_over_separator13", "glob/star_star.in", "c/z.c", "^key3=value3[ \t\n\r]*$") +tests.add("star_star_over_separator14", "glob/star_star.in", "cmn/z.c", "^key3=value3[ \t\n\r]*$") +tests.add("star_star_over_separator15", "glob/star_star.in", "c/mn/z.c", "^key3=value3[ \t\n\r]*$") +tests.add("star_star_over_separator16", "glob/star_star.in", "cmnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator17", "glob/star_star.in", "cm/nz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator18", "glob/star_star.in", "c/mnz.c", "^[ \t\n\r]*$") + +tests.add("star_star_over_separator19", "glob/star_star.in", "d/z.c", "^key4=value4[ \t\n\r]*$") +tests.add("star_star_over_separator20", "glob/star_star.in", "d/mn/z.c", "^key4=value4[ \t\n\r]*$") +tests.add("star_star_over_separator21", "glob/star_star.in", "dmnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator22", "glob/star_star.in", "dm/nz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator23", "glob/star_star.in", "d/mnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator24", "glob/star_star.in", "dmn/z.c", "^[ \t\n\r]*$") diff --git a/debian/extra/plugins/editorconfig/tests/glob/question.in b/debian/extra/plugins/editorconfig/tests/glob/question.in new file mode 100644 index 0000000..e2af52a --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/question.in @@ -0,0 +1,7 @@ +; test ? + +root=true + +[som?.c] +key=value + diff --git a/debian/extra/plugins/editorconfig/tests/glob/star.in b/debian/extra/plugins/editorconfig/tests/glob/star.in new file mode 100644 index 0000000..c7d874f --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/star.in @@ -0,0 +1,12 @@ +; test * + +root=true + +[a*e.c] +key=value + +[Bar/*] +keyb=valueb + +[*] +keyc=valuec diff --git a/debian/extra/plugins/editorconfig/tests/glob/star_star.in b/debian/extra/plugins/editorconfig/tests/glob/star_star.in new file mode 100644 index 0000000..c8f2c99 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/star_star.in @@ -0,0 +1,15 @@ +; test ** + +root=true + +[a**z.c] +key1=value1 + +[b/**z.c] +key2=value2 + +[c**/z.c] +key3=value3 + +[d/**/z.c] +key4=value4 diff --git a/debian/extra/plugins/editorconfig/tests/glob/utf8char.in b/debian/extra/plugins/editorconfig/tests/glob/utf8char.in new file mode 100644 index 0000000..6fe89b0 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/glob/utf8char.in @@ -0,0 +1,6 @@ +; test EditorConfig files with UTF-8 characters larger than 127 + +root = true + +[中文.txt] +key = value diff --git a/debian/extra/plugins/editorconfig/tests/init.lua b/debian/extra/plugins/editorconfig/tests/init.lua new file mode 100644 index 0000000..654067b --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/init.lua @@ -0,0 +1,143 @@ +local Parser = require "plugins.editorconfig.parser" + +local tests = {} + +---@class tests.test +---@field name string Name of test +---@field config string Path to config file +---@field in_match string A path to test against the config +---@field out_match string A regex to match against the result + +---Registered tests +---@type tests.test[] +tests.list = {} + +--- parsers cache +---@type table +local parsers = {} +setmetatable(parsers, { + __index = function(t, k) + local v = rawget(t, k) + if v == nil then + v = Parser.new(k) + rawset(t, k, v) + end + return v + end +}) + +---Adds color to given text on non windows systems. +---@param text string +---@param color "red" | "green" | "yellow" +---@return string colorized_text +local function colorize(text, color) + if PLATFORM ~= "Windows" then + if color == "green" then + return "\27[92m"..text.."\27[0m" + elseif color == "red" then + return "\27[91m"..text.."\27[0m" + elseif color == "yellow" then + return "\27[93m"..text.."\27[0m" + end + end + return text +end + +local PASSED = colorize("PASSED", "green") +local FAILED = colorize("FAILED", "red") + +---Runs an individual test (executed by tests.run()) +---@param name string Test name +---@param config_path string Relative path to tests diretory for a [config].in +---@param in_match string Filename to match +---@param out_match string | table Result to match regex +function tests.check_config(name, config_path, in_match, out_match, pos, total) + if type(out_match) == "string" then + out_match = { out_match } + end + local parser = parsers[USERDIR .. "/plugins/editorconfig/tests/" .. config_path] + local config = parser:getConfigString(in_match) + local passed = true + for _, match in ipairs(out_match) do + if not regex.match(match, config) then + passed = false + break + end + end + if pos then + pos = "[" .. pos .. "/" .. total .. "] " + else + pos = "" + end + if passed then + print(pos .. string.format("%s - %s - '%s': %s", name, in_match, config_path, PASSED)) + else + print(pos .. string.format("%s - %s - '%s': %s", name, in_match, config_path, FAILED)) + print(config) + end + return passed +end + +---Register a new test to be run later. +---@param name string Test name +---@param config_path string Relative path to tests diretory for a [config].in +---@param in_match string Filename to match +---@param out_match string | table Result to match regex +function tests.add(name, config_path, in_match, out_match) + table.insert(tests.list, { + name = name, + config = config_path, + in_match = in_match, + out_match = out_match + }) +end + +---Runs all registered tests and outputs the results to terminal. +function tests.run() + print "=========================================================" + print "Running Tests" + print "=========================================================" + local failed = 0 + local passed = 0 + local total = #tests.list + for i, test in ipairs(tests.list) do + local res = tests.check_config( + test.name, test.config, test.in_match, test.out_match, i, total + ) + if res then passed = passed + 1 else failed = failed + 1 end + end + print "=========================================================" + print ( + string.format( + "%s %s %s", + colorize("Total tests: " .. #tests.list, "yellow"), + colorize("Passed: " .. passed, "green"), + colorize("Failed: " .. failed, "red") + ) + ) + print "=========================================================" +end + +function tests.add_parser(config_path) + return parsers[config_path] +end + +function tests.run_parsers() + print "=========================================================" + print "Running Parsers" + print "=========================================================" + + for config, parser in pairs(parsers) do + print "---------------------------------------------------------" + print(string.format("%s results:", config)) + for _, section in ipairs(parser.sections) do + print(string.format("\nPath expression: %s", section.rule.expression)) + print(string.format("Regex: %s", section.rule.regex)) + print(string.format("Negation: %s", section.rule.negation and "true" or "false")) + print(string.format("Ranges: %s\n", section.rule.ranges and #section.rule.ranges or "0")) + end + print "---------------------------------------------------------" + end +end + +return tests diff --git a/debian/extra/plugins/editorconfig/tests/parser/basic.in b/debian/extra/plugins/editorconfig/tests/parser/basic.in new file mode 100644 index 0000000..3033b9a --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/basic.in @@ -0,0 +1,16 @@ +[*.a] +option1=value1 + +; repeat section +[*.a] +option2=value2 + +[*.b] +option1 = a +option2 = a + +[b.b] +option2 = b + +[*.b] +option1 = c diff --git a/debian/extra/plugins/editorconfig/tests/parser/bom.in b/debian/extra/plugins/editorconfig/tests/parser/bom.in new file mode 100644 index 0000000..8bde201 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/bom.in @@ -0,0 +1,6 @@ +; test EditorConfig files with BOM + +root = true + +[*] +key = value diff --git a/debian/extra/plugins/editorconfig/tests/parser/comments.in b/debian/extra/plugins/editorconfig/tests/parser/comments.in new file mode 100644 index 0000000..c49fba8 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/comments.in @@ -0,0 +1,47 @@ +; test comments + +root = true + +[test3.c] +; Comment before properties ignored +key=value + +[test4.c] +key1=value1 +; Comment between properties ignored +key2=value2 + +; Semicolon or hash at end of value read as part of value +[test5.c] +key1=value; not comment +key2=value # not comment + +; Backslash before a semicolon or hash is part of the value +[test6.c] +key1=value \; not comment +key2=value \# not comment + +; Escaped semicolon in section name +[test\;.c] +key=value + +[test9.c] +# Comment before properties ignored +key=value + +[test10.c] +key1=value1 +# Comment between properties ignored +key2=value2 + +# Octothorpe at end of value read as part of value +[test11.c] +key=value# not comment + +# Escaped octothorpe in value +[test12.c] +key=value \# not comment + +# Escaped octothorpe in section name +[test\#.c] +key=value diff --git a/debian/extra/plugins/editorconfig/tests/parser/comments_and_newlines.in b/debian/extra/plugins/editorconfig/tests/parser/comments_and_newlines.in new file mode 100644 index 0000000..35fc023 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/comments_and_newlines.in @@ -0,0 +1,4 @@ + +# Just comments + +# ... and newlines diff --git a/debian/extra/plugins/editorconfig/tests/parser/comments_only.in b/debian/extra/plugins/editorconfig/tests/parser/comments_only.in new file mode 100644 index 0000000..9592ed2 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/comments_only.in @@ -0,0 +1 @@ +# Just a comment, nothing else \ No newline at end of file diff --git a/debian/extra/plugins/editorconfig/tests/parser/crlf.in b/debian/extra/plugins/editorconfig/tests/parser/crlf.in new file mode 100644 index 0000000..ec582d2 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/crlf.in @@ -0,0 +1,6 @@ +; test EditorConfig files with CRLF line separators + +root = true + +[*] +key = value diff --git a/debian/extra/plugins/editorconfig/tests/parser/empty.in b/debian/extra/plugins/editorconfig/tests/parser/empty.in new file mode 100644 index 0000000..e69de29 diff --git a/debian/extra/plugins/editorconfig/tests/parser/init.lua b/debian/extra/plugins/editorconfig/tests/parser/init.lua new file mode 100644 index 0000000..cf473e5 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/init.lua @@ -0,0 +1,107 @@ +local tests = require "plugins.editorconfig.tests" + +-- Basic parser tests + +-- test repeat sections +tests.add("repeat_sections_ML", "parser/basic.in", "a.a", "option1=value1[ \t]*[\n\r]+option2=value2[ \t\n\r]*") +tests.add("basic_cascade_ML", "parser/basic.in", "b.b", "option1=c[ \t]*[\n\r]+option2=b[ \t\n\r]*") + +-- Tests for whitespace parsing + +-- test no whitespaces in property assignment +tests.add("no_whitespace", "parser/whitespace.in", "test1.c", "^key=value[ \t\n\r]*$") + +-- test single spaces around equals sign +tests.add("single_spaces_around_equals", "parser/whitespace.in", "test2.c", + "^key=value[ \t\n\r]*$") + +-- test multiple spaces around equals sign +tests.add("multiple_spaces_around_equals", "parser/whitespace.in", "test3.c", + "^key=value[ \t\n\r]*$") + +-- test spaces before property name +tests.add("spaces_before_property_name", "parser/whitespace.in", "test4.c", + "^key=value[ \t\n\r]*$") + +-- test spaces before after property value +tests.add("spaces_after_property_value", "parser/whitespace.in", "test5.c", + "^key=value[ \t\n\r]*$") + +-- test blank lines between properties +tests.add("blank_lines_between_properties_ML", "parser/whitespace.in", "test6.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*") + +-- test spaces in section name +tests.add("spaces_in_section_name", "parser/whitespace.in", " test 7 ", + "^key=value[ \t\n\r]*$") + +-- test spaces before section name are ignored +tests.add("spaces_before_section_name", "parser/whitespace.in", "test8.c", + "^key=value[ \t\n\r]*$") + +-- test spaces after section name +tests.add("spaces_after_section_name", "parser/whitespace.in", "test9.c", "^key=value[ \t\n\r]*$") + +-- test spaces at beginning of line between properties +tests.add("spaces_before_middle_property_ML", "parser/whitespace.in", "test10.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t]*[\n\r]+key3=value3[ \t\n\r]*") + +-- Tests for comment parsing + +-- test comments ignored before properties +tests.add("comment_before_props", "parser/comments.in", "test3.c", + "^key=value[ \t\n\r]*$") + +-- test comments ignored between properties +tests.add("comment_between_props_ML", "parser/comments.in", "test4.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*") + +-- test semicolons and hashes at end of property value are included in value +tests.add("semicolon_or_hash_in_property", "parser/comments.in", "test5.c", + "^key1=value; not comment[\n\r]+key2=value # not comment[ \t\n\r]*$") + +-- test that backslashes before semicolons and hashes in property values +-- are included in value. +-- NOTE: [\\] matches a single literal backslash. +tests.add("backslashed_semicolon_or_hash_in_property", "parser/comments.in", "test6.c", + "^key1=value [\\\\]; not comment[\n\r]+key2=value [\\\\]# not comment[ \t\n\r]*$") + +-- test escaped semicolons are included in section names +tests.add("escaped_semicolon_in_section", "parser/comments.in", "test;.c", + "^key=value[ \t\n\r]*$") + +-- test octothorpe comments ignored before properties +tests.add("octothorpe_comment_before_props", "parser/comments.in", "test9.c", + "^key=value[ \t\n\r]*$") + +-- test octothorpe comments ignored between properties +tests.add("octothorpe_comment_between_props_ML", "parser/comments.in", "test10.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*") + +-- test octothorpe at end of property value are included in value +tests.add("octothorpe_in_value", "parser/comments.in", "test11.c", + "^key=value# not comment[ \t\n\r]*$") + +-- test escaped octothorpes are included in section names +tests.add("escaped_octothorpe_in_section", "parser/comments.in", "test#.c", + "^key=value[ \t\n\r]*$") + +-- test EditorConfig files with BOM at the head +tests.add("bom_at_head", "parser/bom.in", "a.c", "^key=value[ \t\n\r]*$") + +-- test EditorConfig files with CRLF line separators +tests.add("crlf_linesep", "parser/crlf.in", "a.c", "^key=value[ \t\n\r]*$") + +-- Test minimum supported lengths of section name, key and value +tests.add("min_supported_key_length", "parser/limits.in", "test1", + "^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=v1024[ \t\n\r]*$") +tests.add("min_supported_value_length", "parser/limits.in", "test2", + "^k4096=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[ \t\n\r]*$") +tests.add("min_supported_section_name_length", "parser/limits.in", "test3", + "^k1024=v1024[ \t\n\r]*$") + +-- Empty .editorconfig files +tests.add("empty_editorconfig_file", "parser/empty.in", "test4", "^[ \t\n\r]*$") +tests.add("newlines_only_editorconfig_file", "parser/newlines_only.in", "test4", "^[ \t\n\r]*$") +tests.add("comments_only_editorconfig_file", "parser/comments_only.in", "test4", "^[ \t\n\r]*$") +tests.add("comments_and_newlines_editorconfig_file", "parser/comments_and_newlines.in", "test4", "^[ \t\n\r]*$") diff --git a/debian/extra/plugins/editorconfig/tests/parser/limits.in b/debian/extra/plugins/editorconfig/tests/parser/limits.in new file mode 100644 index 0000000..d768a8c --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/limits.in @@ -0,0 +1,13 @@ +root = true + +; minimum supported key length of 1024 characters +[test1] +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=v1024 + +; minimum supported value length of 4096 characters +[test2] +k4096=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +; minimum supported section name length of 1024 characters (excluding [] brackets) +[{test3,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}] +k1024=v1024 diff --git a/debian/extra/plugins/editorconfig/tests/parser/newlines_only.in b/debian/extra/plugins/editorconfig/tests/parser/newlines_only.in new file mode 100644 index 0000000..139597f --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/newlines_only.in @@ -0,0 +1,2 @@ + + diff --git a/debian/extra/plugins/editorconfig/tests/parser/whitespace.in b/debian/extra/plugins/editorconfig/tests/parser/whitespace.in new file mode 100644 index 0000000..d1f3c5f --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/parser/whitespace.in @@ -0,0 +1,48 @@ +; test whitespace usage + +root = true + +; no whitespace +[test1.c] +key=value + +; spaces around equals +[test2.c] +key = value + +; lots of space after equals +[test3.c] +key = value + +; spaces before property name +[test4.c] + key=value + +; spaces after property value +[test5.c] +key=value + +; blank lines between properties +[test6.c] + +key1=value1 + +key2=value2 + +; spaces in section name +[ test 7 ] +key=value + +; spaces before section name + [test8.c] +key=value + +; spaces after section name +[test9.c] +key=value + +; spacing before middle property +[test10.c] +key1=value1 + key2=value2 +key3=value3 diff --git a/debian/extra/plugins/editorconfig/tests/properties/indent_size_default.in b/debian/extra/plugins/editorconfig/tests/properties/indent_size_default.in new file mode 100644 index 0000000..809fc3f --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/properties/indent_size_default.in @@ -0,0 +1,11 @@ +root = true + +[test.c] +indent_style = tab + +[test2.c] +indent_style = space + +[test3.c] +indent_style = tab +tab_width = 2 diff --git a/debian/extra/plugins/editorconfig/tests/properties/init.lua b/debian/extra/plugins/editorconfig/tests/properties/init.lua new file mode 100644 index 0000000..4ae22d0 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/properties/init.lua @@ -0,0 +1,42 @@ +local tests = require "plugins.editorconfig.tests" + +-- test tab_width default +tests.add("tab_width_default_ML", "properties/tab_width_default.in", "test.c", + "indent_size=4[ \t]*[\n\r]+indent_style=space[ \t]*[\n\r]+tab_width=4[\t\n\r]*") + +-- Tab_width should not be set to any value if indent_size is "tab" and +-- tab_width is not set +tests.add("tab_width_default_indent_size_tab_ML", "properties/tab_width_default.in", + "test2.c", "indent_size=tab[ \t]*[\n\r]+indent_style=tab[ \t\n\r]*") + +-- Test indent_size default. When indent_style is "tab", indent_size defaults to +-- "tab". +tests.add("indent_size_default_ML", "properties/indent_size_default.in", "test.c", + "indent_size=tab[ \t]*[\n\r]+indent_style=tab[ \t\n\r]*") + +-- Test indent_size default. When indent_style is "space", indent_size has no +-- default value. +tests.add("indent_size_default_space", "properties/indent_size_default.in", "test2.c", + "^indent_style=space[ \t\n\r]*$") + +-- Test indent_size default. When indent_style is "tab" and tab_width is set, +-- indent_size should default to tab_width +tests.add("indent_size_default_with_tab_width_ML", + "properties/indent_size_default.in", "test3.c", + "indent_size=2[ \t]*[\n\r]+indent_style=tab[ \t]*[\n\r]+tab_width=2[ \t\n\r]*") + +-- test that same property values are lowercased (v0.9.0 properties) +tests.add("lowercase_values1_ML", "properties/lowercase_values.in", "test1.c", + "end_of_line=crlf[ \t]*[\n\r]+indent_style=space[ \t\n\r]*") + +-- test that same property values are lowercased (v0.9.0 properties) +tests.add("lowercase_values2_ML", "properties/lowercase_values.in", "test2.c", + "charset=utf-8[ \t]*[\n\r]+insert_final_newline=true[ \t]*[\n\r]+trim_trailing_whitespace=false[ \t\n\r]*$") + +-- test that same property values are not lowercased +tests.add("lowercase_values3", "properties/lowercase_values.in", "test3.c", + "^test_property=TestValue[ \t\n\r]*$") + +-- test that all property names are lowercased +tests.add("lowercase_names", "properties/lowercase_names.in", "test.c", + "^testproperty=testvalue[ \t\n\r]*$") diff --git a/debian/extra/plugins/editorconfig/tests/properties/lowercase_names.in b/debian/extra/plugins/editorconfig/tests/properties/lowercase_names.in new file mode 100644 index 0000000..253ea8b --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/properties/lowercase_names.in @@ -0,0 +1,6 @@ +; test that property names are lowercased + +root = true + +[test.c] +TestProperty = testvalue diff --git a/debian/extra/plugins/editorconfig/tests/properties/lowercase_values.in b/debian/extra/plugins/editorconfig/tests/properties/lowercase_values.in new file mode 100644 index 0000000..1730bb2 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/properties/lowercase_values.in @@ -0,0 +1,15 @@ +; test property name lowercasing + +root = true + +[test1.c] +indent_style = Space +end_of_line = CRLF + +[test2.c] +insert_final_newline = TRUE +trim_trailing_whitespace = False +charset = UTF-8 + +[test3.c] +test_property = TestValue diff --git a/debian/extra/plugins/editorconfig/tests/properties/tab_width_default.in b/debian/extra/plugins/editorconfig/tests/properties/tab_width_default.in new file mode 100644 index 0000000..3084607 --- /dev/null +++ b/debian/extra/plugins/editorconfig/tests/properties/tab_width_default.in @@ -0,0 +1,9 @@ +root = true + +[test.c] +indent_style = space +indent_size = 4 + +[test2.c] +indent_style = tab +indent_size = tab diff --git a/debian/extra/plugins/navigate.lua b/debian/extra/plugins/navigate.lua new file mode 100644 index 0000000..d83c02f --- /dev/null +++ b/debian/extra/plugins/navigate.lua @@ -0,0 +1,170 @@ +-- mod-version:3 + +local core = require "core" +local common = require "core.common" +local command = require "core.command" +local keymap = require "core.keymap" +local Doc = require "core.doc" +local DocView = require "core.docview" + +local navigate = { + list = {}, + current = nil, + index = 0 +} + +-- +-- Private functions +-- +local function get_active_view() + if getmetatable(core.active_view) == DocView then + return core.active_view + end + return nil +end + +-- Solution to safely remove elements from array table: +-- found at https://stackoverflow.com/a/53038524 +local function array_remove(t, fnKeep) + local j, n = 1, #t; + + for i=1, n do + if (fnKeep(t, i, j)) then + if (i ~= j) then + t[j] = t[i]; + t[i] = nil; + end + j = j + 1; + else + t[i] = nil; + end + end + + return t; +end + +local function add(doc) + -- Make new navigation point last in list + if navigate.index > 0 and navigate.index < #navigate.list then + local remove_start = navigate.index + 1 + local remove_end = #navigate.list + array_remove(navigate.list, function(_, i) + if i >= remove_start and i <= remove_end then + return false + end + return true + end) + end + + local line, col = doc:get_selection() + table.insert(navigate.list, { + filename = doc.filename, + line = line, + col = col + }) + + navigate.current = navigate.list[#navigate.list] + navigate.index = #navigate.list +end + +local function open_doc(doc) + core.root_view:open_doc( + core.open_doc( + common.home_expand( + doc.filename + ) + ) + ) + + local av_doc = get_active_view().doc + local line, col = av_doc:get_selection() + if doc.line ~= line or doc.col ~= col then + av_doc:set_selection(doc.line, doc.col, doc.line, doc.col) + end +end + +-- +-- Public functions +-- +function navigate.next() + if navigate.index < #navigate.list then + navigate.index = navigate.index + 1 + navigate.current = navigate.list[navigate.index] + open_doc(navigate.current) + end +end + +function navigate.prev() + if navigate.index > 1 then + navigate.index = navigate.index - 1 + navigate.current = navigate.list[navigate.index] + open_doc(navigate.current) + end +end + +-- +-- Thread +-- +core.add_thread(function() + while true do + local av = get_active_view() + if av and av.doc and av.doc.filename then + local doc = av.doc + local line, col = doc:get_selection() + local current = navigate.current + if + not current + or + current.filename ~= doc.filename + or + current.line ~= line + then + add(doc) + else + current.col = col + end + end + coroutine.yield(0.5) + end +end) + +-- +-- Patching +-- +local doc_on_close = Doc.on_close + +function Doc:on_close() + local filename = self.filename + -- remove all positions referencing closed file + array_remove(navigate.list, function(t, i) + if t[i].filename == filename then + return false + end + return true + end) + + doc_on_close(self) +end + +-- +-- Commands +-- +command.add("core.docview", { + ["navigate:previous"] = function() + navigate.prev() + end, + + ["navigate:next"] = function() + navigate.next() + end, +}) + +-- +-- Default Keybindings +-- +keymap.add { + ["alt+left"] = "navigate:previous", + ["alt+right"] = "navigate:next", +} + +return navigate diff --git a/debian/extra/plugins/selectionhighlight.lua b/debian/extra/plugins/selectionhighlight.lua new file mode 100644 index 0000000..133dced --- /dev/null +++ b/debian/extra/plugins/selectionhighlight.lua @@ -0,0 +1,47 @@ +-- mod-version:3 +local style = require "core.style" +local DocView = require "core.docview" + +-- originally written by luveti + +local function draw_box(x, y, w, h, color) + local r = renderer.draw_rect + local s = math.ceil(SCALE) + r(x, y, w, s, color) + r(x, y + h - s, w, s, color) + r(x, y + s, s, h - s * 2, color) + r(x + w - s, y + s, s, h - s * 2, color) +end + + +local draw_line_body = DocView.draw_line_body + +function DocView:draw_line_body(line, x, y) + local line_height = draw_line_body(self, line, x, y) + local line1, col1, line2, col2 = self.doc:get_selection(true) + if line1 == line2 and col1 ~= col2 then + local selection = self.doc:get_text(line1, col1, line2, col2) + if not selection:match("^%s+$") then + local lh = self:get_line_height() + local selected_text = self.doc.lines[line1]:sub(col1, col2 - 1) + local current_line_text = self.doc.lines[line] + local last_col = 1 + while true do + local start_col, end_col = current_line_text:find( + selected_text, last_col, true + ) + if start_col == nil then break end + -- don't draw box around the selection + if line ~= line1 or start_col ~= col1 then + local x1 = x + self:get_col_x_offset(line, start_col) + local x2 = x + self:get_col_x_offset(line, end_col + 1) + local color = style.selectionhighlight or style.syntax.comment + draw_box(x1, y, x2 - x1, lh, color) + end + last_col = end_col + 1 + end + end + end + return line_height +end + diff --git a/debian/install b/debian/install index e449d39..90ebef4 100644 --- a/debian/install +++ b/debian/install @@ -4,6 +4,9 @@ debian/extra/plugins/lsp usr/share/lite-xl/plugins debian/extra/libraries/widget usr/share/lite-xl/libraries debian/extra/libraries/font_symbols_nerdfont_mono_regular.lua usr/share/lite-xl/libraries/ debian/extra/fonts/SymbolsNerdFontMono-Regular.ttf usr/share/lite-xl/libraries/font_symbols_nerdfont_mono_regular/ +debian/extra/plugins/bracketmatch.lua usr/share/lite-xl/plugins/ +debian/extra/plugins/colorpreview.lua usr/share/lite-xl/plugins/ +debian/extra/plugins/editorconfig usr/share/lite-xl/plugins debian/extra/plugins/ephemeral_tabs.lua usr/share/lite-xl/plugins/ debian/extra/plugins/fontconfig.lua usr/share/lite-xl/plugins/ debian/extra/plugins/gitopen.lua usr/share/lite-xl/plugins/ @@ -114,7 +117,9 @@ debian/extra/plugins/language_wren.lua usr/share/lite-xl/plugins/ debian/extra/plugins/language_yaml.lua usr/share/lite-xl/plugins/ debian/extra/plugins/language_zig.lua usr/share/lite-xl/plugins/ debian/extra/plugins/nerdicons.lua usr/share/lite-xl/plugins/ +debian/extra/plugins/navigate.lua usr/share/lite-xl/plugins/ debian/extra/plugins/open_ext.lua usr/share/lite-xl/plugins/ debian/extra/plugins/select_colorscheme.lua usr/share/lite-xl/plugins/ +debian/extra/plugins/selectionhighlight.lua usr/share/lite-xl/plugins/ debian/extra/plugins/settings.lua usr/share/lite-xl/plugins/ debian/extra/plugins/smoothcaret.lua usr/share/lite-xl/plugins/