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).
This commit is contained in:
2026-09-17 11:18:20 +02:00
parent 273965ef0b
commit 56ef7344f0
36 changed files with 2606 additions and 4 deletions
+4
View File
@@ -5,3 +5,7 @@
winlib/* linguist-vendored winlib/* linguist-vendored
src/lib/* linguist-vendored src/lib/* linguist-vendored
resources/icons/icon.inl 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
+12
View File
@@ -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 <valentin.haudiquet@canonical.com> Thu, 17 Sep 2026 11:30:00 +0200
lite-xl (2.1.8-6) stonking; urgency=medium lite-xl (2.1.8-6) stonking; urgency=medium
* Vendor ephemeral_tabs (lite-xl-plugins @ 31eef29): files opened from the * Vendor ephemeral_tabs (lite-xl-plugins @ 31eef29): files opened from the
+5 -4
View File
@@ -1,10 +1,11 @@
Vendored from upstream, pinned by commit: Vendored from upstream, pinned by commit:
- lite-xl-plugins https://github.com/lite-xl/lite-xl-plugins @ 31eef29 (master, 2026-09) - lite-xl-plugins https://github.com/lite-xl/lite-xl-plugins @ 31eef29 (master, 2026-09)
plugins/: ephemeral_tabs (VSCode-style preview tabs), gitstatus, gitopen, plugins/: ephemeral_tabs (VSCode-style preview tabs), selectionhighlight,
fontconfig, open_ext, select_colorscheme, bracketmatch, colorpreview, navigate, editorconfig (whole directory,
smoothcaret, nerdicons, language_* (all 104; none overlap the ones bundled incl. its test harness), gitstatus, gitopen, fontconfig, open_ext,
in the core data/ directory) 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) - lite-xl-gitpanel (own plugin, source tree lite-xl-gitpanel) @ 0731a63 (main, 2026-09)
plugins/: gitpanel -- git panel integrated into the treeview pane plugins/: gitpanel -- git panel integrated into the treeview pane
(changes, staging, commits, history) with a toolbar toggle button; (changes, staging, commits, history) with a toolbar toggle button;
+278
View File
@@ -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",
}
+88
View File
@@ -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
+61
View File
@@ -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
```
+441
View File
@@ -0,0 +1,441 @@
-- mod-version:3
--
-- EditorConfig plugin for Lite XL
-- @copyright Jefferson Gonzalez <jgmdev@gmail.com>
-- @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<string, plugins.editorconfig.parser>
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
+553
View File
@@ -0,0 +1,553 @@
-- Lua parser implementation of the .editorconfig spec as best understood.
-- @copyright Jefferson Gonzalez <jgmdev@gmail.com>
-- @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<integer,string>
---The expression converted to a regex.
---@field regex string | table<integer,string>
---@field regex_compiled any? | table<integer,string>
---@field negation boolean Indicates that the expression is a negation.
---@field ranges table<integer,number> 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<string,string>
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 "(?<!0)("..minus.."[1-9]\\d*)"
end
},
-- threat single option braces literally
{ rule = "^{[^,]+}", conversion = function(match) return match end },
-- match invalid range
{ rule = "^{[^%.]+%.%.[^%.]+}", conversion = function(match) return match end },
-- match any of the strings separated by commas inside the curly braces
{ rule = "^%b{}",
conversion = function(rule, section)
rule = rule:gsub("^{", ""):gsub("}$", "")
local pos, len, exp = 1, rule:ulen(), ""
while pos <= len do
local found = false
for _, r in ipairs(RULES_BRACES) do
local match = rule:umatch(r.rule, pos)
if match then
exp = exp .. r.conversion(match, section)
pos = pos + match:ulen()
found = true
break
end
end
if not found then
exp = exp .. rule:usub(pos, pos)
pos = pos + 1
end
end
return "(" .. exp .. ")"
end
}
}
---Adds the regex equivalent of a section path expression.
---@param section plugins.editorconfig.parser.section | string
---@return plugins.editorconfig.parser.section
function Parser:rule_to_regex(section)
if type(section) == "string" then
section = {rule = {expression = section}}
end
local rule = section.rule.expression
-- match everything rule which is different from regular *
-- that doesn't matches path separators
if rule == "*" then
section.rule.regex = ".+"
section.rule.regex_compiled = regex.compile(".+")
return section
end
rule = escapes_to_regex_hex(section.rule.expression)
local pos, len, exp = 1, rule:ulen(), ""
-- if expression starts with ! it is treated entirely as a negation
local negation = rule:umatch("^%s*!")
if negation then
pos = pos + negation:ulen() + 1
end
-- apply all conversion rules by looping the path expression/rule
while pos <= len do
local found = false
for _, r in ipairs(RULES) do
local match = rule:umatch(r.rule, pos)
if match then
exp = exp .. r.conversion(match, section)
pos = pos + match:ulen()
found = true
break
end
end
if not found then
exp = exp .. rule:usub(pos, pos)
pos = pos + 1
end
end
-- force match up to the end
exp = exp .. "$"
-- allow expressions that start with * to match anything on start
if exp:match("^%[^\\/%]%*") then
exp = exp:gsub("^%[^\\/%]%*", ".*")
-- fixes two failing tests
elseif exp:match("^%[") then
exp = "^" .. exp
-- match only on root dir
elseif exp:match("^/") then
exp = exp:gsub("^/", "^")
end
-- store changes to the section rule
section.rule.regex, section.rule.negation = exp, negation
section.rule.regex_compiled = regex.compile(section.rule.regex)
if not section.rule.regex_compiled then
log(
"error",
"could not compile '[%s]' to regex '%s'",
rule, section.rule.regex
)
end
return section
end
---Parses the associated .editorconfig file and stores each section.
function Parser:read()
local file = io.open(self.config_path, "r")
self.sections = {}
if not file then
log("log", "could not read %s", self.config_path)
return
end
---@type plugins.editorconfig.parser.section
local section = {}
for line in file:lines() do
---@cast line string
-- first we try to see if the line is a rule section
local rule = ""
rule = line:umatch("^%s*%[(.+)%]%s*$")
if rule then
if section.rule then
-- save previous section and crerate new one
table.insert(self.sections, section)
section = {}
end
section.rule = {
expression = rule
}
-- convert the expression to a regex directly on the section table
self:rule_to_regex(section)
local clone = rule
if clone:match("//+") or clone:match("/%*%*/") then
section.equivalent_rules = {}
end
while clone:match("//+") or clone:match("/%*%*/") do
---@type plugins.editorconfig.parser.section[]
if clone:match("//+") then
clone = clone:ugsub("//+", "/", 1)
table.insert(section.equivalent_rules, self:rule_to_regex(clone).rule)
end
if clone:match("/%*%*/") then
clone = clone:ugsub("/%*%*/", "/", 1)
table.insert(section.equivalent_rules, self:rule_to_regex(clone).rule)
end
end
end
if not rule then
local name, value = line:umatch("^%s*(%w%S+)%s*=%s*([^\n\r]+)")
if name and value then
name = name:ulower()
-- do not lowercase property values that start with test_
if not name:match("^test_") then
value = value:ulower()
end
if value == "true" then
value = true
elseif value == "false" then
value = false
elseif math.tointeger and math.tointeger(value) then
value = math.tointeger(value)
elseif tonumber(value) then
value = tonumber(value)
end
if section.rule then
section[name] = value
elseif name == "root" and type(value) == "boolean" then
self.root = value
end
end
end
end
if section.rule then
table.insert(self.sections, section)
end
end
---Helper function that converts a regex offset results into a list
---of strings, omitting the first result which is the complete match.
---@param offsets table<integer,integer>
---@param value string
---@return table<integer, string>
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
+63
View File
@@ -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
+71
View File
@@ -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
@@ -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
+241
View File
@@ -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]*$")
@@ -0,0 +1,7 @@
; test ?
root=true
[som?.c]
key=value
+12
View File
@@ -0,0 +1,12 @@
; test *
root=true
[a*e.c]
key=value
[Bar/*]
keyb=valueb
[*]
keyc=valuec
@@ -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
@@ -0,0 +1,6 @@
; test EditorConfig files with UTF-8 characters larger than 127
root = true
[中文.txt]
key = value
+143
View File
@@ -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<string,plugins.editorconfig.parser>
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
+16
View File
@@ -0,0 +1,16 @@
[*.a]
option1=value1
; repeat section
[*.a]
option2=value2
[*.b]
option1 = a
option2 = a
[b.b]
option2 = b
[*.b]
option1 = c
+6
View File
@@ -0,0 +1,6 @@
; test EditorConfig files with BOM
root = true
[*]
key = value
@@ -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
@@ -0,0 +1,4 @@
# Just comments
# ... and newlines
@@ -0,0 +1 @@
# Just a comment, nothing else
@@ -0,0 +1,6 @@
; test EditorConfig files with CRLF line separators
root = true
[*]
key = value
+107
View File
@@ -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]*$")
@@ -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
@@ -0,0 +1,2 @@
@@ -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
@@ -0,0 +1,11 @@
root = true
[test.c]
indent_style = tab
[test2.c]
indent_style = space
[test3.c]
indent_style = tab
tab_width = 2
@@ -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]*$")
@@ -0,0 +1,6 @@
; test that property names are lowercased
root = true
[test.c]
TestProperty = testvalue
@@ -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
@@ -0,0 +1,9 @@
root = true
[test.c]
indent_style = space
indent_size = 4
[test2.c]
indent_style = tab
indent_size = tab
+170
View File
@@ -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
+47
View File
@@ -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
+5
View File
@@ -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/widget usr/share/lite-xl/libraries
debian/extra/libraries/font_symbols_nerdfont_mono_regular.lua 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/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/ephemeral_tabs.lua usr/share/lite-xl/plugins/
debian/extra/plugins/fontconfig.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/ 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_yaml.lua usr/share/lite-xl/plugins/
debian/extra/plugins/language_zig.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/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/open_ext.lua usr/share/lite-xl/plugins/
debian/extra/plugins/select_colorscheme.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/settings.lua usr/share/lite-xl/plugins/
debian/extra/plugins/smoothcaret.lua usr/share/lite-xl/plugins/ debian/extra/plugins/smoothcaret.lua usr/share/lite-xl/plugins/