Initial commit
21
.config/lite-xl/plugins/codeplus/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Francisco Barreiras
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
41
.config/lite-xl/plugins/codeplus/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Code+
|
||||
> A quality of life plugin for the Lite XL text editor. Offering improvements such as highlighted comments and autocomplete for brackets, quotes and more.
|
||||
|
||||
## Basic usage
|
||||
|
||||
Highlight comments with special properties using the **@Todo(...)** and **@Fixme(...)** keywords for an enhanced coding experience.
|
||||
Streamline coding by auto-completing brackets, parentheses, quotation marks reducing manual effort and improving the writing code experience.
|
||||
|
||||
## Demonstration
|
||||
|
||||

|
||||
|
||||
## Instalation
|
||||
Navigate to the `data/plugins` folder and run the following command:
|
||||
```bash
|
||||
git clone https://github.com/chqs-git/code-plus.git
|
||||
```
|
||||
|
||||
Alternatively you can download and rename the `init.lua ` file to `code+.lua` and drop it into the `data/plugins` folder.
|
||||
|
||||
## Configuration
|
||||
|
||||
Using the settings plugin for Lite Xl you can easily configure your experience by changing the highlight colors for the *@todo* and *@fixme* operators.
|
||||
|
||||
If you wish to add more highlights you can simply update the following code:
|
||||
```lua
|
||||
function DocView:draw_line_text(line, x, y)
|
||||
local lh = draw_line_text(self, line, x, y)
|
||||
|
||||
if config.plugins.code_plus.enabled then
|
||||
highlight_comment(self, line, x, y, "@todo", config.plugins.code_plus.todo)
|
||||
highlight_comment(self, line, x, y, "@fixme", config.plugins.code_plus.fixme)
|
||||
-- add a new highlight! the color is just an example
|
||||
highlight_comment(self, line, x, y, "@new_tag", {common.color "#ffffff"})
|
||||
end
|
||||
return lh
|
||||
end
|
||||
```
|
||||
|
||||
To extend the already auto-completing utilities to other keywords, you can simply use the `complete` function. Create a new command for the new auto-complete utility (required) and map a *key* to the command.
|
||||
|
||||
110
.config/lite-xl/plugins/codeplus/init.lua
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
-- mod-version:3
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
config.plugins.code_plus = common.merge({
|
||||
enabled = true, --- enabled by default
|
||||
config_spec = { --- config specification used by the settings gui
|
||||
name = "Code+",
|
||||
{
|
||||
label = "Enable",
|
||||
description = "Toggle to enable this plugin.",
|
||||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Todo Color",
|
||||
description = "Define the color that highlights the todo comments.",
|
||||
path = "todo",
|
||||
type = "color",
|
||||
default = "#5592CF"
|
||||
},
|
||||
{
|
||||
label = "Fixme Color",
|
||||
description = "Defines the color that highlights the fixme comments.",
|
||||
path = "fixme",
|
||||
type = "color",
|
||||
default = "#EF6385"
|
||||
},
|
||||
}
|
||||
}, config.plugins.code_plus)
|
||||
|
||||
--- draw comments highlights
|
||||
local white = { common.color "#ffffff" }
|
||||
|
||||
local function draw_highlight(self, str, line, x, y, s, e, color)
|
||||
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()
|
||||
renderer.draw_rect(x1, y, x2 - x1, self:get_line_height(), color)
|
||||
renderer.draw_text(self:get_font(), str, x1, y + oy, white)
|
||||
end
|
||||
|
||||
|
||||
local function highlight_comment(self, line, x, y, comment, color)
|
||||
local text = self.doc.lines[line]
|
||||
local s, e = 0, 0
|
||||
|
||||
while true do
|
||||
s, e = text:lower():find(comment .. "%((.-)%)", e + 1)
|
||||
if s then
|
||||
local str = text:sub(s, e)
|
||||
draw_highlight(self, str, line, x, y, s, e, color)
|
||||
end
|
||||
|
||||
if not s then
|
||||
break
|
||||
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.code_plus.enabled then
|
||||
highlight_comment(self, line, x, y, "@todo", config.plugins.code_plus.todo)
|
||||
highlight_comment(self, line, x, y, "@fixme", config.plugins.code_plus.fixme)
|
||||
end
|
||||
return lh
|
||||
end
|
||||
|
||||
--- auto complete brackets, parantheses, etc...
|
||||
|
||||
local function complete(dv, characters)
|
||||
local doc = dv.doc
|
||||
local idx = dv.doc.last_selection
|
||||
local line1, col1 = doc:get_selection_idx(idx)
|
||||
doc:insert(line1, col1, characters)
|
||||
doc:move_to_cursor(idx, idx)
|
||||
end
|
||||
|
||||
command.add("core.docview!", {
|
||||
["code_plus:complete_brackets"] = function(dv)
|
||||
complete(dv, "[]")
|
||||
end,
|
||||
["code_plus:complete_curly_brackets"] = function(dv)
|
||||
complete(dv, "{}")
|
||||
end,
|
||||
["code_plus:complete_parantheses"] = function(dv)
|
||||
complete(dv, "()")
|
||||
end,
|
||||
["code_plus:complete_quotation_marks"] = function(dv)
|
||||
complete(dv, '""')
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["altgr+8"] = "code_plus:complete_brackets",
|
||||
["ctrl+alt+8"] = "code_plus:complete_brackets",
|
||||
["altgr+7"] = "code_plus:complete_curly_brackets",
|
||||
["ctrl+alt+7"] = "code_plus:complete_curly_brackets",
|
||||
["shift+8"] = "code_plus:complete_parantheses",
|
||||
["shift+2"] = "code_plus:complete_quotation_marks"
|
||||
}
|
||||
|
||||
12
.config/lite-xl/plugins/codeplus/manifest.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"addons": [
|
||||
{
|
||||
"id": "codeplus",
|
||||
"mod_version": "3",
|
||||
"name": "codeplus",
|
||||
"path": ".",
|
||||
"type": "plugin",
|
||||
"version": "0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
61
.config/lite-xl/plugins/editorconfig/README.md
Normal 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
.config/lite-xl/plugins/editorconfig/init.lua
Normal 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
.config/lite-xl/plugins/editorconfig/parser.lua
Normal 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
.config/lite-xl/plugins/editorconfig/runtest.lua
Normal 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
.config/lite-xl/plugins/editorconfig/tests/glob/braces.in
Normal 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
|
||||
51
.config/lite-xl/plugins/editorconfig/tests/glob/brackets.in
Normal file
|
|
@ -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
.config/lite-xl/plugins/editorconfig/tests/glob/init.lua
Normal 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
.config/lite-xl/plugins/editorconfig/tests/glob/star.in
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
; test *
|
||||
|
||||
root=true
|
||||
|
||||
[a*e.c]
|
||||
key=value
|
||||
|
||||
[Bar/*]
|
||||
keyb=valueb
|
||||
|
||||
[*]
|
||||
keyc=valuec
|
||||
15
.config/lite-xl/plugins/editorconfig/tests/glob/star_star.in
Normal file
|
|
@ -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
.config/lite-xl/plugins/editorconfig/tests/init.lua
Normal 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
.config/lite-xl/plugins/editorconfig/tests/parser/basic.in
Normal 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
.config/lite-xl/plugins/editorconfig/tests/parser/bom.in
Normal 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
.config/lite-xl/plugins/editorconfig/tests/parser/init.lua
Normal 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]*$")
|
||||
13
.config/lite-xl/plugins/editorconfig/tests/parser/limits.in
Normal file
|
|
@ -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
|
||||
232
.config/lite-xl/plugins/language_go.lua
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
-- mod-version:3
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "Go",
|
||||
files = { "%.go$" },
|
||||
comment = "//",
|
||||
block_comment = {"/*", "*/"},
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "`", "`", '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0[oO_][0-7]+i?", type = "number" },
|
||||
{ pattern = "-?0x[%x_]+i?", type = "number" },
|
||||
{ pattern = "-?%d+_%di?", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*f?i?", type = "number" },
|
||||
{ pattern = "-?%.?%d+f?i?", type = "number" },
|
||||
-- goto label
|
||||
{ pattern = "^%s+()[%a_][%w%_]*()%s*:%s$", -- this is to fix `default:`
|
||||
type = { "normal", "function", "normal" }
|
||||
},
|
||||
{ pattern = "^%s*[%a_][%w%_]*()%s*:%s$",
|
||||
type = { "function", "normal" }
|
||||
},
|
||||
-- pointer, generic and reference type
|
||||
{ pattern = "[%*~&]()[%a_][%w%_]*",
|
||||
type = { "operator", "keyword2" }
|
||||
},
|
||||
-- slice type
|
||||
{ pattern = "%[%]()[%a_][%w%_]*",
|
||||
type = { "operator", "keyword2" }
|
||||
},
|
||||
-- type coerce
|
||||
{
|
||||
pattern = "%.%(()[%a_][%w_]*()%)",
|
||||
type = { "normal", "keyword2", "normal" }
|
||||
},
|
||||
-- struct literal
|
||||
{ pattern = "[%a_][%w%_]*()%s*{%s*",
|
||||
type = { "keyword2", "normal" }
|
||||
},
|
||||
-- operators
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = ":=", type = "operator" },
|
||||
-- function calls
|
||||
{ pattern = "func()%s*[%a_][%w_]*()%f[%[(]", -- function statement
|
||||
type = {"keyword", "function", "normal"}
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "%.()[%a_][%w_]*%f[(]",
|
||||
type = { "normal", "function" }
|
||||
},
|
||||
-- type declaration
|
||||
{ pattern = "type()%s+()[%a_][%w%_]*",
|
||||
type = { "keyword", "normal", "keyword2" }
|
||||
},
|
||||
-- variable declaration
|
||||
{ pattern = "var()%s+()[%a_][%w%_]*",
|
||||
type = { "keyword", "normal", "symbol" }
|
||||
},
|
||||
-- goto
|
||||
{ pattern = "goto()%s+()[%a_][%w%_]*",
|
||||
type = { "keyword", "normal", "function" }
|
||||
},
|
||||
-- if fix
|
||||
{ pattern = "if()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- for fix
|
||||
{ pattern = "for()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- return fix
|
||||
{ pattern = "return()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- range fix
|
||||
{ pattern = "range()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- func fix
|
||||
{ pattern = "func()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- switch fix
|
||||
{ pattern = "switch()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- case fix
|
||||
{ pattern = "case()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- break fix
|
||||
{ pattern = "break()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- continue fix
|
||||
{ pattern = "continue()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- package fix
|
||||
{ pattern = "package()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- go fix
|
||||
{ pattern = "go()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- chan fix
|
||||
{ pattern = "chan()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- defer fix
|
||||
{ pattern = "defer()%s+%f[%a_]",
|
||||
type = { "keyword", "normal" }
|
||||
},
|
||||
-- field declaration
|
||||
{ pattern = "[%a_][%w%_]*()%s*():%s*%f[%w%p]",
|
||||
type = { "function", "normal", "operator" }
|
||||
},
|
||||
-- parameters or declarations
|
||||
{ pattern = "[%a_][%w%_]*()%s+()[%*~&]?()[%a_][%w%_]*",
|
||||
type = { "literal", "normal", "operator", "keyword2" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()%[%]()[%a_][%w%_]*",
|
||||
type = { "literal", "normal", "normal", "keyword2" }
|
||||
},
|
||||
-- single return type
|
||||
{
|
||||
pattern = "%)%s+%(?()[%a_][%w%_]*()%)?%s+%{",
|
||||
type = { "normal", "keyword2", "normal" }
|
||||
},
|
||||
-- sub fields
|
||||
{ pattern = "%.()[%a_][%w_]*",
|
||||
type = { "normal", "literal" }
|
||||
},
|
||||
-- every other symbol
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["elseif"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["switch"] = "keyword",
|
||||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["package"] = "keyword",
|
||||
["import"] = "keyword",
|
||||
["func"] = "keyword",
|
||||
["var"] = "keyword",
|
||||
["type"] = "keyword",
|
||||
["interface"] = "keyword",
|
||||
["select"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["range"] = "keyword",
|
||||
["chan"] = "keyword",
|
||||
["defer"] = "keyword",
|
||||
["go"] = "keyword",
|
||||
["fallthrough"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["iota"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["int64"] = "keyword2",
|
||||
["int32"] = "keyword2",
|
||||
["int16"] = "keyword2",
|
||||
["int8"] = "keyword2",
|
||||
["uint"] = "keyword2",
|
||||
["uint64"] = "keyword2",
|
||||
["uint32"] = "keyword2",
|
||||
["uint16"] = "keyword2",
|
||||
["uint8"] = "keyword2",
|
||||
["uintptr"] = "keyword2",
|
||||
["float64"] = "keyword2",
|
||||
["float32"] = "keyword2",
|
||||
["map"] = "keyword2",
|
||||
["string"] = "keyword2",
|
||||
["rune"] = "keyword2",
|
||||
["bool"] = "keyword2",
|
||||
["byte"] = "keyword2",
|
||||
["error"] = "keyword2",
|
||||
["complex64"] = "keyword2",
|
||||
["complex128"] = "keyword2",
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["nil"] = "literal",
|
||||
},
|
||||
}
|
||||
|
||||
syntax.add {
|
||||
name = "Go",
|
||||
files = { "go%.mod" },
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment"},
|
||||
{ pattern = "module() %S+()",
|
||||
type = { "keyword", "string", "normal"}
|
||||
},
|
||||
{ pattern = "go() %S+()",
|
||||
type = { "keyword", "string", "normal" }
|
||||
},
|
||||
{ pattern = "%S+() v%S+()",
|
||||
type = { "string", "keyword", "normal" }
|
||||
},
|
||||
},
|
||||
symbols = {
|
||||
["require"] = "keyword",
|
||||
["module"] = "keyword",
|
||||
["go"] = "keyword",
|
||||
}
|
||||
}
|
||||
|
||||
syntax.add {
|
||||
name = "Go",
|
||||
files = { "go%.sum" },
|
||||
patterns = {
|
||||
{ pattern = "%S+() v[^/]-() h1:()%S+()=",
|
||||
type = { "string", "keyword", "normal", "string", "normal" }
|
||||
},
|
||||
{ pattern = "%S+() v[^/]-()/%S+() h1:()%S+()=",
|
||||
type = { "string", "keyword", "string", "normal", "string", "normal" }
|
||||
},
|
||||
},
|
||||
symbols = {}
|
||||
}
|
||||
|
||||
34
.config/lite-xl/plugins/language_json.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- mod-version:3 priority:110
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "JSON",
|
||||
|
||||
files = {
|
||||
"%.json$",
|
||||
"%.cjson$",
|
||||
"%.jsonc$",
|
||||
"%.ipynb$",
|
||||
},
|
||||
|
||||
comment = "//",
|
||||
block_comment = {"/*", "*/"},
|
||||
patterns = {
|
||||
|
||||
-- cjson support
|
||||
{ pattern = "//.*", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
|
||||
{ regex = [["(?:[^"\\]|\\.)*"()\s*:]], type = { "keyword", "normal" } }, -- key
|
||||
{ regex = [["(?:[^"\\]|\\.)*"]], type = "string" }, -- value
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "null", type = "literal" },
|
||||
{ pattern = "true", type = "literal" },
|
||||
{ pattern = "false", type = "literal" }
|
||||
},
|
||||
symbols = { }
|
||||
}
|
||||
|
||||
98
.config/lite-xl/plugins/language_sh.lua
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
-- mod-version:3
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "Shell script",
|
||||
files = { "%.sh$", "%.bash$", "^%.bashrc$", "^%.bash_profile$", "^%.profile$", "%.zsh$", "%.fish$" },
|
||||
headers = "^#!.*bin.*sh\n",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
-- $# is a bash special variable and the '#' shouldn't be interpreted
|
||||
-- as a comment.
|
||||
{ pattern = "$[%a_@*#][%w_]*", type = "keyword2" },
|
||||
-- Comments
|
||||
{ pattern = "#.*\n", type = "comment" },
|
||||
-- Strings
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { '`', '`', '\\' }, type = "string" },
|
||||
-- Ignore numbers that start with dots or slashes
|
||||
{ pattern = "%f[%w_%.%/]%d[%d%.]*%f[^%w_%.]", type = "number" },
|
||||
-- Operators
|
||||
{ pattern = "[!<>|&%[%]:=*]", type = "operator" },
|
||||
-- Match parameters
|
||||
{ pattern = "%f[%S][%+%-][%w%-_:]+", type = "function" },
|
||||
{ pattern = "%f[%S][%+%-][%w%-_]+%f[=]", type = "function" },
|
||||
-- Prevent parameters with assignments from been matched as variables
|
||||
{
|
||||
pattern = "%s%-%a[%w_%-]*%s+()%d[%d%.]+",
|
||||
type = { "function", "number" }
|
||||
},
|
||||
{
|
||||
pattern = "%s%-%a[%w_%-]*%s+()%a[%a%-_:=]+",
|
||||
type = { "function", "symbol" }
|
||||
},
|
||||
-- Match variable assignments
|
||||
{ pattern = "[_%a][%w_]+%f[%+=]", type = "keyword2" },
|
||||
-- Match variable expansions
|
||||
{ pattern = "${.-}", type = "keyword2" },
|
||||
{ pattern = "$[%d$%a_@*][%w_]*", type = "keyword2" },
|
||||
-- Functions
|
||||
{ pattern = "[%a_%-][%w_%-]*[%s]*%f[(]", type = "function" },
|
||||
-- Everything else
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["case"] = "keyword",
|
||||
["in"] = "keyword",
|
||||
["esac"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
["then"] = "keyword",
|
||||
["elif"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["fi"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
["do"] = "keyword",
|
||||
["done"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["continue"] = "keyword",
|
||||
["function"] = "keyword",
|
||||
["local"] = "keyword",
|
||||
["echo"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["exit"] = "keyword",
|
||||
["alias"] = "keyword",
|
||||
["test"] = "keyword",
|
||||
["cd"] = "keyword",
|
||||
["declare"] = "keyword",
|
||||
["enable"] = "keyword",
|
||||
["eval"] = "keyword",
|
||||
["exec"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["getopts"] = "keyword",
|
||||
["hash"] = "keyword",
|
||||
["history"] = "keyword",
|
||||
["help"] = "keyword",
|
||||
["jobs"] = "keyword",
|
||||
["kill"] = "keyword",
|
||||
["let"] = "keyword",
|
||||
["mapfile"] = "keyword",
|
||||
["printf"] = "keyword",
|
||||
["read"] = "keyword",
|
||||
["readarray"] = "keyword",
|
||||
["pwd"] = "keyword",
|
||||
["select"] = "keyword",
|
||||
["set"] = "keyword",
|
||||
["shift"] = "keyword",
|
||||
["source"] = "keyword",
|
||||
["time"] = "keyword",
|
||||
["type"] = "keyword",
|
||||
["until"] = "keyword",
|
||||
["unalias"] = "keyword",
|
||||
["unset"] = "keyword",
|
||||
["true"] = "literal",
|
||||
["false"] = "literal"
|
||||
}
|
||||
}
|
||||
|
||||
40
.config/lite-xl/plugins/language_toml.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- mod-version:3
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "TOML",
|
||||
files = { "%.toml$" },
|
||||
comment = '#',
|
||||
|
||||
patterns = {
|
||||
{ pattern = "#.*", type = "comment" },
|
||||
|
||||
{ pattern = { '"""', '"""', '\\' }, type = "string" },
|
||||
{ pattern = { "'''", "'''" }, type = "string" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'" }, type = "string" },
|
||||
|
||||
{ pattern = "[%w_%.%-]+%s*%f[=]", type = "function" },
|
||||
|
||||
{ pattern = {"^%s*%[", "%]"}, type = "keyword" },
|
||||
|
||||
{ pattern = "0x[%x_]+", type = "number" },
|
||||
{ pattern = "0o[0-7_]+", type = "number" },
|
||||
{ pattern = "0b[01_]+", type = "number" },
|
||||
{ pattern = "%d[%d_]*%.?[%d_]*[eE][%-+]?[%d_]+", type = "number" },
|
||||
{ pattern = "%d[%d_]*%.?[$d_]*", type = "number" },
|
||||
{ pattern = "%f[-+%w_][-+]%f[%w%.]", type = "number" },
|
||||
|
||||
{ pattern = "[%+%-:TZ]", type = "operator" },
|
||||
{ pattern = "%a+", type = "symbol" },
|
||||
},
|
||||
|
||||
symbols = {
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
|
||||
["nan"] = "number",
|
||||
["inf"] = "number"
|
||||
},
|
||||
}
|
||||
154
.config/lite-xl/plugins/language_yaml.lua
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
-- mod-version:3
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
local yaml_bracket_list = {
|
||||
patterns = {
|
||||
-- comments
|
||||
{ pattern = { "#", "\n"}, type = "comment" },
|
||||
-- strings
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
-- keys
|
||||
{
|
||||
pattern = "[%w%d]+%g+()%s*():()%s",
|
||||
type = { "keyword2", "normal", "operator", "normal" }
|
||||
},
|
||||
-- variables
|
||||
{ pattern = "%$%a%w+", type = "keyword" },
|
||||
{ pattern = "%$%{%{.-%}%}", type = "keyword" },
|
||||
-- numeric place holders
|
||||
{ pattern = "%-?%.inf", type = "number" },
|
||||
{ pattern = "%.NaN", type = "number" },
|
||||
-- numbers
|
||||
{ pattern = "[%+%-]?0%d+", type = "number" },
|
||||
{ pattern = "[%+%-]?0x%x+", type = "number" },
|
||||
{ pattern = "[%+%-]?%d+[,%.eE:%+%d]*%d+", type = "number" },
|
||||
{ pattern = "[%+%-]?%d+", type = "number" },
|
||||
-- others
|
||||
{ pattern = ",", type = "operator" },
|
||||
{ pattern = "%w+", type = "string" },
|
||||
{
|
||||
pattern = "[_%(%)%*@~`!%%%^&=%+%-\\;%.><%?/%s]+",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
symbols = {}
|
||||
}
|
||||
|
||||
syntax.add {
|
||||
name = "YAML",
|
||||
files = { "%.yml$", "%.yaml$" },
|
||||
comment = "#",
|
||||
space_handling = false,
|
||||
patterns = {
|
||||
--- rules that start with spaces first and those taking precedence
|
||||
-- parent and child keys
|
||||
{
|
||||
pattern = "^[%w%d]+%g+%s*%f[:]",
|
||||
type = "literal"
|
||||
},
|
||||
{
|
||||
pattern = "^%s+[%w%d]+%g+%s*%f[:]",
|
||||
type = "keyword2"
|
||||
},
|
||||
-- bracket lists after key declaration
|
||||
{
|
||||
pattern = { ":%s+%[", "%]" },
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
{
|
||||
pattern = { ":%s+{", "}" },
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
-- child key
|
||||
{
|
||||
pattern = "^%s+()[%w%d]+%g+()%s*():()%s",
|
||||
type = { "normal", "keyword2", "normal", "operator", "normal" }
|
||||
},
|
||||
-- child list element
|
||||
{
|
||||
pattern = "^%s+()%-()%s+()[%w%d]+%g+()%s*():()%s",
|
||||
type = { "normal", "operator", "normal", "keyword2", "normal", "operator", "normal" }
|
||||
},
|
||||
-- unkeyed bracket lists
|
||||
{
|
||||
pattern = { "^%s*%[", "%]" },
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
{
|
||||
pattern = { "^%s*{", "}" },
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
{
|
||||
pattern = { "^%s*%-%s*%[", "%]" },
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
{
|
||||
pattern = { "^%s*%-%s*{", "}" },
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
-- rule to optimize space handling
|
||||
{ pattern = "%s+", type = "normal" },
|
||||
--- all the other rules
|
||||
-- comments
|
||||
{ pattern = { "#", "\n"}, type = "comment" },
|
||||
-- strings
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
-- extra bracket lists rules on explicit type
|
||||
{
|
||||
pattern = { "!!%w+%s+%[", "%]"},
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
{
|
||||
pattern = { "!!%w+%s+{", "}"},
|
||||
syntax = yaml_bracket_list, type = "operator"
|
||||
},
|
||||
-- numeric place holders
|
||||
{ pattern = "%-?%.inf", type = "number" },
|
||||
{ pattern = "%.NaN", type = "number" },
|
||||
-- parent list element
|
||||
{
|
||||
pattern = "^%-()%s+()[%w%d]+%g+()%s*():()%s",
|
||||
type = { "operator", "normal", "keyword2", "normal", "operator", "normal" }
|
||||
},
|
||||
-- key label
|
||||
{
|
||||
pattern = "%&()%g+",
|
||||
type = { "keyword", "literal" }
|
||||
},
|
||||
-- key elements expansion
|
||||
{ pattern = "<<", type = "literal" },
|
||||
{
|
||||
pattern = "%*()[%w%d_]+",
|
||||
type = { "keyword", "literal" }
|
||||
},
|
||||
-- explicit data types
|
||||
{ pattern = "!!%g+", type = "keyword" },
|
||||
-- parent key
|
||||
{
|
||||
pattern = "^[%w%d]+%g+()%s*():()%s",
|
||||
type = { "literal", "normal", "operator", "normal" }
|
||||
},
|
||||
-- variables
|
||||
{ pattern = "%$%a%w+", type = "keyword" },
|
||||
{ pattern = "%$%{%{.-%}%}", type = "keyword" },
|
||||
-- numbers
|
||||
{ pattern = "[%+%-]?0%d+", type = "number" },
|
||||
{ pattern = "[%+%-]?0x%x+", type = "number" },
|
||||
{ pattern = "[%+%-]?%d+[,%.eE:%+%d]*%d+", type = "number" },
|
||||
{ pattern = "[%+%-]?%d+", type = "number" },
|
||||
-- special operators
|
||||
{ pattern = "[%*%|%!>%%]", type = "keyword" },
|
||||
{ pattern = "[%-%$:%?]+", type = "operator" },
|
||||
-- Everything else as a string
|
||||
{ pattern = "[%d%a_][%g_]*", type = "string" },
|
||||
{ pattern = "%p+", type = "string" }
|
||||
},
|
||||
symbols = {
|
||||
["true"] = "number",
|
||||
["false"] = "number",
|
||||
["y"] = "number",
|
||||
["n"] = "number"
|
||||
}
|
||||
}
|
||||
281
.config/lite-xl/plugins/lintplus/README.md
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
# lint+
|
||||
|
||||
An improved linting plugin for [Lite XL](https://github.com/lite-xl/lite-xl).
|
||||
|
||||
Includes compatibility layer for [`linter`](https://github.com/drmargarido/linters).
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
<p align="center">
|
||||
Features ErrorLens-style warnings and error messages for quickly scanning
|
||||
through code for errors.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||

|
||||
<p align="center">
|
||||
The status view shows either the first error, or the full message of the error
|
||||
under your text cursor. No mouse interaction needed!
|
||||
</p>
|
||||
|
||||
|
||||
## Motivation
|
||||
|
||||
There were a few problems I had with the existing `linter` plugin:
|
||||
|
||||
- It can only show "warnings" - there's no severity levels
|
||||
(info/hint/warning/error).
|
||||
- It doesn't show the messages after lines (ErrorLens style), you have to hover
|
||||
over the warning first.
|
||||
- It spam-runs the linter command, but Nim (and possibly other languages)
|
||||
compiles relatively slowly, which lags the editor to hell.
|
||||
- It doesn't display the first or current error message on the status view.
|
||||
|
||||
lint+ aims to fix all of the above problems.
|
||||
|
||||
### Why not just fix `linter`?
|
||||
|
||||
- It works fundamentally differently from lint+, so fixing it would be more
|
||||
costly than just making a new plugin.
|
||||
- I haven't ever made my own linter support plugin, so this was a good exercise.
|
||||
|
||||
## Installation
|
||||
|
||||
Navigate to your `plugins` folder, and clone the repository:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/liquidev/lintplus
|
||||
```
|
||||
|
||||
To enable the different linters available on the [linters](linters/)
|
||||
subdirectory, you have to load them on your lite-xl user module file (`init.lua`).
|
||||
|
||||
You can load a single linter:
|
||||
```lua
|
||||
local lintplus = require "plugins.lintplus"
|
||||
lintplus.load("luacheck")
|
||||
```
|
||||
or multiple linters by passing a table:
|
||||
```lua
|
||||
local lintplus = require "plugins.lintplus"
|
||||
lintplus.load({"php", "luacheck"})
|
||||
```
|
||||
|
||||
If you want to use plugins designed for the other `linter`, you will also need
|
||||
to enable the compatibility plugin `linter.lua` *from this repository*.
|
||||
|
||||
```sh
|
||||
$ ln -s $PWD/{lintplus/linter,linter}.lua
|
||||
```
|
||||
|
||||
Keep in mind that plugins designed for `linter` will not work as well as lint+
|
||||
plugins, because of `linter`'s lack of multiple severity levels. All warnings
|
||||
reported by `linter` linters will be reported with the `warning` level.
|
||||
|
||||
### Automatic Linting
|
||||
|
||||
To enable automatic linting upon opening/saving a file, add the following
|
||||
code tou your lite-xl user module:
|
||||
```lua
|
||||
local lintplus = require "plugins.lintplus"
|
||||
lintplus.setup.lint_on_doc_load()
|
||||
lintplus.setup.lint_on_doc_save()
|
||||
```
|
||||
This overrides `Doc.load` and `Doc.save` with some extra behavior to enable
|
||||
automatic linting.
|
||||
|
||||
## Commands
|
||||
|
||||
Available commands from the lite-xl commands palette (ctrl+shift+p):
|
||||
|
||||
* `lint+:lint` - run the appropriate linter command for the current document
|
||||
* `lint+:goto-previous-message` (alt+up) - jump to previous message on current document
|
||||
* `lint+:goto-next-message` (alt+down) - jump to next message on current document
|
||||
|
||||
## Configuration
|
||||
|
||||
lint+ itself looks for the following configuration options:
|
||||
|
||||
- `config.lint.kind_pretty_names`
|
||||
- table:
|
||||
- `info`: string = `"I"`
|
||||
- `hint`: string = `"H"`
|
||||
- `warning`: string = `"W"`
|
||||
- `error`: string = `"E"`
|
||||
- controls the prefix prepended to messages displayed on the status bar.
|
||||
for example, setting `error` to `Error` will display `Error: …` or
|
||||
`line 10 Error: …` instead of `E: …` or `line 10 E: …`.
|
||||
- `config.lint.lens_style`
|
||||
- string:
|
||||
- `"blank"`: do not draw underline on line messages
|
||||
- `"solid"`: draw single line underline on line messages (default)
|
||||
- `"dots"`: draw dotted underline on line messages (slower performance)
|
||||
- function(x, y, width, color): a custom drawing routine
|
||||
- `x`: number
|
||||
- `y`: number
|
||||
- `width`: number
|
||||
- `color`: renderer.color
|
||||
|
||||
All options are unset (`nil`) by default, so eg. setting
|
||||
`config.lint.kind_pretty_names.hint` will *not* work because
|
||||
`config.lint.kind_pretty_names` does not exist.
|
||||
|
||||
Individual plugins may also look for options in the `config.lint` table.
|
||||
Refer to each plugin's source code for more information.
|
||||
|
||||
### Styling
|
||||
|
||||
The screenshots above use a theme with extra colors for the linter's messages.
|
||||
The default color is the same color used for literals, which isn't always what
|
||||
you want. Most of the time you want to have some clear visual distinction
|
||||
between severity levels, so lint+ is fully stylable.
|
||||
|
||||
- `style.lint`
|
||||
- table:
|
||||
- `info`: Color - the color used for infos
|
||||
- `hint`: Color - the color used for hints
|
||||
- `warning`: Color - the color used for warnings
|
||||
- `error`: Color - the color used for errors
|
||||
|
||||
Example:
|
||||
|
||||
```lua
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
style.lint = {
|
||||
info = style.syntax["keyword2"],
|
||||
hint = style.syntax["function"],
|
||||
warning = style.syntax["function"],
|
||||
error = { common.color "#FF3333" }
|
||||
}
|
||||
```
|
||||
|
||||
As with config, you need to provide all or no colors.
|
||||
|
||||
## Creating new linters
|
||||
|
||||
Just like `linter`, lint+ allows you to create new linters for languages not
|
||||
supported out of the box. The API is very simple:
|
||||
|
||||
```lua
|
||||
Severity: enum {
|
||||
"info", -- suggestions on how to fix things, may be used in tandem with
|
||||
-- other messages
|
||||
"hint", -- suggestions on small things that don't affect program behavior
|
||||
"warning", -- warnings about possible mistakes that may affect behavior
|
||||
"error", -- syntax or semantic errors that prevent compilation
|
||||
}
|
||||
|
||||
LintContext: table {
|
||||
:gutter_rail(): number
|
||||
-- creates a new gutter rail and returns its index
|
||||
:gutter_rail_count(): number
|
||||
-- returns how many gutter rails have been created in this context
|
||||
-- You may create additional fields in this table, but keys prefixed with _
|
||||
-- are reserved by lint+.
|
||||
}
|
||||
|
||||
lintplus.add(linter_name: string)(linter: table {
|
||||
filename: pattern,
|
||||
procedure: table {
|
||||
command: function (filename: string): {string},
|
||||
-- Returns the lint command for the given filename.
|
||||
interpreter: (function (filename, line: string, context: LintContext):
|
||||
function ():
|
||||
nil or
|
||||
(filename: string, line, column: number,
|
||||
kind: Severity, message: string, rail: number or nil)) or "bail"
|
||||
-- Creates and returns a message iterator, which yields all messages
|
||||
-- from the line.
|
||||
-- If the return value is "bail", reading the lint command is aborted
|
||||
-- immediately. This is done as a mitigation for processes that may take
|
||||
-- too long to execute or block indefinitely.
|
||||
-- `rail` is optional and specifies the gutter rail to which the message
|
||||
-- should be attached.
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Because writing command and interpreter functions can quickly get tedious, there
|
||||
are some helpers that return pre-built functions for you:
|
||||
|
||||
```lua
|
||||
lintplus.command(cmd: {string}): function (string): {string}
|
||||
-- Returns a function that replaces `lintplus.filename` in the given table
|
||||
-- with the linted file's name.
|
||||
lintplus.interpreter(spec: table {
|
||||
info: pattern or nil,
|
||||
hint: pattern or nil,
|
||||
warning: pattern or nil,
|
||||
error: pattern or nil,
|
||||
-- Defines patterns for all the severity levels. Each pattern must have
|
||||
-- four captures: the first one being the filename, the second and third
|
||||
-- being the line and column, and the fourth being the message.
|
||||
-- When any of these are nil, the interpreter simply will not produce the
|
||||
-- given severity levels.
|
||||
strip: pattern or nil,
|
||||
-- Defines a pattern for stripping unnecessary information from the message
|
||||
-- capture from one of the previously defined patterns. When this is `nil`,
|
||||
-- nothing is stripped and the message remains as-is.
|
||||
})
|
||||
```
|
||||
|
||||
An example linter built with these primitives:
|
||||
|
||||
```lua
|
||||
lintplus.add("nim") {
|
||||
filename = "%.nim$",
|
||||
procedure = {
|
||||
command = lintplus.command {
|
||||
"nim", "check", "--listFullPaths", "--stdout", lintplus.filename
|
||||
},
|
||||
interpreter = lintplus.interpreter {
|
||||
-- The format for these three in Nim is almost exactly the same:
|
||||
hint = "(.-)%((%d+), (%d+)%) Hint: (.+)",
|
||||
warning = "(.-)%((%d+), (%d+)%) Warning: (.+)",
|
||||
error = "(.-)%((%d+), (%d+)%) Error: (.+)",
|
||||
-- We want to strip annotations like [XDeclaredButNotUsed] from the end:
|
||||
strip = "%s%[%w+%]$",
|
||||
-- Note that info was omitted. This is because all of the severity levels
|
||||
-- are optional, so eg. you don't have to provide an info pattern.
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If you want to let the user of your linter specify some extra arguments,
|
||||
`lintplus.args_command` can be used instead of `lintplus.command`:
|
||||
|
||||
```lua
|
||||
-- ...
|
||||
command = lintplus.args_command(
|
||||
{ "luacheck",
|
||||
lintplus.args,
|
||||
"--formatter=visual_studio",
|
||||
lintplus.filename },
|
||||
"luacheck_args"
|
||||
)
|
||||
-- ...
|
||||
```
|
||||
|
||||
To enable plugins for different languages, do the same thing, but with
|
||||
`lintplus_*.lua`. For example, to enable support for Nim and Rust:
|
||||
The second argument to this function is the name of the field in the
|
||||
`config.lint` table. Then, the user provides arguments like so:
|
||||
|
||||
```lua
|
||||
config.lint.luacheck_args = { "--max-line-length=80", "--std=love" }
|
||||
```
|
||||
|
||||
## Known problems
|
||||
|
||||
- Due to the fact that it shows the most severe message at the end of the
|
||||
line, displaying more than one message per line is really difficult with
|
||||
the limited horizontal real estate, so it can only display one message per
|
||||
line.
|
||||
- It is unable to underline the offending token, simply because some linter
|
||||
error messages do not contain enough information about where the error start
|
||||
and end is. It will highlight the correct line and column, though.
|
||||
|
||||
29
.config/lite-xl/plugins/lintplus/fsutil.lua
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
-- file system utilities
|
||||
|
||||
local fs = {}
|
||||
|
||||
function fs.normalize_path(path)
|
||||
if PLATFORM == "Windows" then
|
||||
return path:gsub('\\', '/')
|
||||
else
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
||||
function fs.parent_directory(path)
|
||||
path = fs.normalize_path(path)
|
||||
path = path:match("^(.-)/*$")
|
||||
local last_slash_pos = -1
|
||||
for i = #path, 1, -1 do
|
||||
if path:sub(i, i) == '/' then
|
||||
last_slash_pos = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if last_slash_pos < 0 then
|
||||
return nil
|
||||
end
|
||||
return path:sub(1, last_slash_pos - 1)
|
||||
end
|
||||
|
||||
return fs
|
||||
977
.config/lite-xl/plugins/lintplus/init.lua
Normal file
|
|
@ -0,0 +1,977 @@
|
|||
-- mod-version:3
|
||||
|
||||
-- lint+ - an improved linter for lite
|
||||
-- copyright (C) lqdev, 2020
|
||||
-- licensed under the MIT license
|
||||
|
||||
|
||||
--- STATIC CONFIG ---
|
||||
|
||||
|
||||
local kind_priority = {
|
||||
info = -1,
|
||||
hint = 0,
|
||||
warning = 1,
|
||||
error = 2,
|
||||
}
|
||||
|
||||
local default_kind_pretty_names = {
|
||||
info = "I",
|
||||
hint = "H",
|
||||
warning = "W",
|
||||
error = "E",
|
||||
}
|
||||
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
local Doc = require "core.doc"
|
||||
local DocView = require "core.docview"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
local liteipc = require "plugins.lintplus.liteipc"
|
||||
|
||||
|
||||
local lint = {}
|
||||
lint.fs = require "plugins.lintplus.fsutil"
|
||||
lint.ipc = liteipc
|
||||
|
||||
|
||||
lint.index = {}
|
||||
lint.messages = {}
|
||||
|
||||
|
||||
local LintContext = {}
|
||||
LintContext.__index = LintContext
|
||||
|
||||
|
||||
function LintContext:create_gutter_rail()
|
||||
if not self._doc then return 0 end
|
||||
local lp = self._doc.__lintplus
|
||||
lp.rail_count = lp.rail_count + 1
|
||||
return lp.rail_count
|
||||
end
|
||||
|
||||
|
||||
function LintContext:gutter_rail_count()
|
||||
if not self._doc then return 0 end
|
||||
return self._doc.__lintplus.rail_count
|
||||
end
|
||||
|
||||
|
||||
-- Can be used by other plugins to properly set the context when loading a doc
|
||||
function lint.init_doc(filename, doc)
|
||||
filename = core.project_absolute_path(filename)
|
||||
local context = setmetatable({
|
||||
_doc = doc or nil,
|
||||
_user_context = nil,
|
||||
}, LintContext)
|
||||
|
||||
if doc then
|
||||
doc.__lintplus_context = {}
|
||||
context._user_context = doc.__lintplus_context
|
||||
|
||||
doc.__lintplus = {
|
||||
rail_count = 0,
|
||||
}
|
||||
end
|
||||
|
||||
if not lint.messages[filename] then
|
||||
lint.messages[filename] = {
|
||||
context = context,
|
||||
lines = {},
|
||||
rails = {},
|
||||
}
|
||||
elseif doc then
|
||||
lint.messages[filename].context = context
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Returns an appropriate linter for the given doc, or nil if no linter is
|
||||
-- found.
|
||||
function lint.get_linter_for_doc(doc)
|
||||
if not doc.filename then
|
||||
return nil
|
||||
end
|
||||
|
||||
local file = core.project_absolute_path(doc.filename)
|
||||
for name, linter in pairs(lint.index) do
|
||||
if common.match_pattern(file, linter.filename) then
|
||||
return linter, name
|
||||
end
|
||||
if linter.syntax ~= nil then
|
||||
local header = doc:get_text(1, 1, doc:position_offset(1, 1, 128))
|
||||
local syn = syntax.get(doc.filename, header)
|
||||
for i = #linter.syntax, 1, -1 do
|
||||
local s = linter.syntax[i]
|
||||
if syn.name == s then
|
||||
return linter, name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- unused for now, because it was a bit buggy
|
||||
-- Note: Should be fixed now
|
||||
function lint.clear_messages(filename)
|
||||
filename = core.project_absolute_path(filename)
|
||||
|
||||
if lint.messages[filename] then
|
||||
lint.messages[filename].lines = {}
|
||||
lint.messages[filename].rails = {}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lint.add_message(filename, line, column, kind, message, rail)
|
||||
filename = core.project_absolute_path(filename)
|
||||
if not lint.messages[filename] then
|
||||
-- This allows us to at least store messages until context is properly
|
||||
-- set from the calling plugin.
|
||||
lint.init_doc(filename)
|
||||
end
|
||||
local file_messages = lint.messages[filename]
|
||||
local lines, rails = file_messages.lines, file_messages.rails
|
||||
lines[line] = lines[line] or {}
|
||||
if rail ~= nil then
|
||||
rails[rail] = rails[rail] or { lines_taken = {} }
|
||||
if not rails[rail].lines_taken[line] then
|
||||
rails[rail].lines_taken[line] = true
|
||||
table.insert(rails[rail], {
|
||||
line = line,
|
||||
column = column,
|
||||
kind = kind,
|
||||
})
|
||||
end
|
||||
end
|
||||
table.insert(lines[line], {
|
||||
column = column,
|
||||
kind = kind,
|
||||
message = message,
|
||||
rail = rail,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local function process_line(doc, linter, line, context)
|
||||
local file = core.project_absolute_path(doc.filename)
|
||||
|
||||
local had_messages = false
|
||||
|
||||
local iterator = linter.procedure.interpreter(file, line, context)
|
||||
if iterator == "bail" then return iterator end
|
||||
|
||||
if os.getenv("LINTPLUS_DEBUG_LINES") then
|
||||
print("lint+ | "..line)
|
||||
end
|
||||
|
||||
for rawfile, lineno, columnno, kind, message, rail in iterator do
|
||||
assert(type(rawfile) == "string")
|
||||
local absfile = core.project_absolute_path(rawfile)
|
||||
if absfile == file then -- TODO: support project-wide errors
|
||||
assert(type(lineno) == "number")
|
||||
assert(type(columnno) == "number")
|
||||
assert(type(kind) == "string")
|
||||
assert(type(message) == "string")
|
||||
assert(rail == nil or type(rail) == "number")
|
||||
|
||||
lint.add_message(absfile, lineno, columnno, kind, message, rail)
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
return had_messages
|
||||
end
|
||||
|
||||
|
||||
local function compare_message_priorities(a, b)
|
||||
return kind_priority[a.kind] > kind_priority[b.kind]
|
||||
end
|
||||
|
||||
local function compare_messages(a, b)
|
||||
if a.column == b.column then
|
||||
return compare_message_priorities(a, b)
|
||||
end
|
||||
return a.column > b.column
|
||||
end
|
||||
|
||||
local function compare_rail_messages(a, b)
|
||||
return a.line < b.line
|
||||
end
|
||||
|
||||
|
||||
function lint.check(doc)
|
||||
if doc.filename == nil then return end
|
||||
|
||||
local linter, linter_name = lint.get_linter_for_doc(doc)
|
||||
if linter == nil then
|
||||
core.error("no linter available for the given filetype")
|
||||
return
|
||||
end
|
||||
|
||||
local filename = core.project_absolute_path(doc.filename)
|
||||
local context = setmetatable({
|
||||
_doc = doc,
|
||||
_user_context = doc.__lintplus_context,
|
||||
}, LintContext)
|
||||
|
||||
doc.__lintplus = {
|
||||
rail_count = 0,
|
||||
}
|
||||
-- clear_messages(linter)
|
||||
lint.messages[filename] = {
|
||||
context = context,
|
||||
lines = {},
|
||||
rails = {},
|
||||
}
|
||||
|
||||
local function report_error(msg)
|
||||
core.log_quiet(
|
||||
"lint+/" .. linter_name .. ": " ..
|
||||
doc.filename .. ": " .. msg
|
||||
)
|
||||
end
|
||||
|
||||
local cmd, cwd = linter.procedure.command(filename), nil
|
||||
if cmd.set_cwd then
|
||||
cwd = lint.fs.parent_directory(filename)
|
||||
end
|
||||
local process = liteipc.start_process(cmd, cwd)
|
||||
core.add_thread(function ()
|
||||
-- poll the process for lines of output
|
||||
while true do
|
||||
local exit, code, errmsg = process:poll(function (line)
|
||||
process_line(doc, linter, line, context)
|
||||
end)
|
||||
if exit ~= nil then
|
||||
-- If linter exited with exit code non 0 or 1 log it
|
||||
if exit == "signal" then
|
||||
report_error(
|
||||
"linter exited with signal " .. code
|
||||
.. (errmsg and " : " .. errmsg or "")
|
||||
)
|
||||
end
|
||||
break
|
||||
end
|
||||
coroutine.yield(0)
|
||||
end
|
||||
-- after reading some lines, sort messages by priority in all files
|
||||
-- and sort rail connections by line number
|
||||
for _, file_messages in pairs(lint.messages) do
|
||||
for _, messages in pairs(file_messages.lines) do
|
||||
table.sort(messages, compare_messages)
|
||||
end
|
||||
for _, rail in pairs(file_messages.rails) do
|
||||
table.sort(rail, compare_rail_messages)
|
||||
end
|
||||
file_messages.rails_sorted = true
|
||||
core.redraw = true
|
||||
coroutine.yield(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
-- inject initialization routines to documents
|
||||
|
||||
local Doc_load, Doc_save, Doc_on_close = Doc.load, Doc.save, Doc.on_close
|
||||
|
||||
local function init_linter_for_doc(doc)
|
||||
local linter, _ = lint.get_linter_for_doc(doc)
|
||||
if linter == nil then return end
|
||||
doc.__lintplus_context = {}
|
||||
if linter.procedure.init ~= nil then
|
||||
linter.procedure.init(
|
||||
core.project_absolute_path(doc.filename),
|
||||
doc.__lintplus_context
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Doc:load(filename)
|
||||
local old_filename = self.filename
|
||||
Doc_load(self, filename)
|
||||
if old_filename ~= filename then
|
||||
init_linter_for_doc(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Doc:save(filename, abs_filename)
|
||||
local old_filename = self.filename
|
||||
Doc_save(self, filename, abs_filename)
|
||||
if old_filename ~= filename then
|
||||
init_linter_for_doc(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Doc:on_close()
|
||||
Doc_on_close(self)
|
||||
if not self.filename then return end
|
||||
local filename = core.project_absolute_path(self.filename)
|
||||
-- release Doc object for proper garbage collection
|
||||
if lint.messages[filename] then
|
||||
lint.messages[filename] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- inject hooks to Doc.insert and Doc.remove to shift messages around
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2
|
||||
or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1, true
|
||||
end
|
||||
return line1, col1, line2, col2, false
|
||||
end
|
||||
|
||||
local Doc_insert = Doc.insert
|
||||
function Doc:insert(line, column, text)
|
||||
Doc_insert(self, line, column, text)
|
||||
|
||||
if self.filename == nil then return end
|
||||
if line == math.huge then return end
|
||||
|
||||
local filename = core.project_absolute_path(self.filename)
|
||||
local file_messages = lint.messages[filename]
|
||||
local lp = self.__lintplus
|
||||
if file_messages == nil or lp == nil then return end
|
||||
|
||||
-- shift line messages downwards
|
||||
local shift = 0
|
||||
for _ in text:gmatch('\n') do
|
||||
shift = shift + 1
|
||||
end
|
||||
if shift == 0 then return end
|
||||
|
||||
local lines = file_messages.lines
|
||||
for i = #self.lines, line, -1 do
|
||||
if lines[i] ~= nil then
|
||||
if not (i == line and lines[i][1].column < column) then
|
||||
lines[i + shift] = lines[i]
|
||||
lines[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- shift rails downwards
|
||||
local rails = file_messages.rails
|
||||
for _, rail in pairs(rails) do
|
||||
for _, message in ipairs(rail) do
|
||||
if message.line >= line then
|
||||
message.line = message.line + shift
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function update_messages_after_removal(
|
||||
doc,
|
||||
line1, column1,
|
||||
line2, column2
|
||||
)
|
||||
if line1 == line2 then return end
|
||||
if line2 == math.huge then return end
|
||||
if doc.filename == nil then return end
|
||||
|
||||
local filename = core.project_absolute_path(doc.filename)
|
||||
local file_messages = lint.messages[filename]
|
||||
local lp = doc.__lintplus
|
||||
if file_messages == nil or lp == nil then return end
|
||||
|
||||
local lines = file_messages.lines
|
||||
|
||||
line1, column1, line2, column2 =
|
||||
sort_positions(line1, column1, line2, column2)
|
||||
local shift = line2 - line1
|
||||
|
||||
-- remove all messages in this range
|
||||
for i = line1, line2 do
|
||||
lines[i] = nil
|
||||
end
|
||||
|
||||
-- shift all line messages up
|
||||
for i = line1, #doc.lines do
|
||||
if lines[i] ~= nil then
|
||||
lines[i - shift] = lines[i]
|
||||
lines[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- remove all rail messages in this range
|
||||
local rails = file_messages.rails
|
||||
for _, rail in pairs(rails) do
|
||||
local remove_indices = {}
|
||||
for i, message in ipairs(rail) do
|
||||
if message.line >= line1 and message.line < line2 then
|
||||
table.insert(remove_indices, i)
|
||||
elseif message.line > line1 then
|
||||
message.line = message.line - shift
|
||||
end
|
||||
end
|
||||
for i = #remove_indices, 1, -1 do
|
||||
table.remove(rail, remove_indices[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local Doc_remove = Doc.remove
|
||||
function Doc:remove(line1, column1, line2, column2)
|
||||
update_messages_after_removal(self, line1, column1, line2, column2)
|
||||
Doc_remove(self, line1, column1, line2, column2)
|
||||
end
|
||||
|
||||
|
||||
-- inject rendering routines
|
||||
|
||||
local renderutil = require "plugins.lintplus.renderutil"
|
||||
|
||||
local function rail_width(dv)
|
||||
return dv:get_line_height() / 3 -- common.round(style.padding.x / 2)
|
||||
end
|
||||
|
||||
local function rail_spacing(dv)
|
||||
return common.round(rail_width(dv) / 4)
|
||||
end
|
||||
|
||||
local DocView_get_gutter_width = DocView.get_gutter_width
|
||||
function DocView:get_gutter_width()
|
||||
local extra_width = 0
|
||||
if self.doc.filename ~= nil then
|
||||
local file_messages = lint.messages[core.project_absolute_path(self.doc.filename)]
|
||||
if file_messages ~= nil then
|
||||
local rail_count = file_messages.context:gutter_rail_count()
|
||||
extra_width = rail_count * (rail_width(self) + rail_spacing(self))
|
||||
end
|
||||
end
|
||||
local original_width, padding = DocView_get_gutter_width(self)
|
||||
return original_width + extra_width, padding
|
||||
end
|
||||
|
||||
|
||||
local function get_gutter_rail_x(dv, index)
|
||||
return
|
||||
dv.position.x + dv:get_gutter_width() -
|
||||
(rail_width(dv) + rail_spacing(dv)) * index + rail_spacing(dv)
|
||||
end
|
||||
|
||||
|
||||
local function get_message_group_color(messages)
|
||||
if style.lint ~= nil then
|
||||
return style.lint[messages[1].kind]
|
||||
else
|
||||
local default_colors = {
|
||||
info = style.syntax["normal"],
|
||||
hint = style.syntax["function"],
|
||||
warning = style.syntax["number"],
|
||||
error = style.syntax["keyword2"]
|
||||
}
|
||||
return default_colors[messages[1].kind]
|
||||
end
|
||||
end
|
||||
|
||||
local function get_underline_y(dv, line)
|
||||
local _, y = dv:get_line_screen_position(line)
|
||||
local line_height = dv:get_line_height()
|
||||
local extra_space = line_height - dv:get_font():get_height()
|
||||
return y + line_height - extra_space / 2
|
||||
end
|
||||
|
||||
local function draw_gutter_rail(dv, index, messages)
|
||||
local rail = messages.rails[index]
|
||||
if rail == nil or #rail < 2 then return end
|
||||
|
||||
local first_message = rail[1]
|
||||
local last_message = rail[#rail]
|
||||
|
||||
local x = get_gutter_rail_x(dv, index)
|
||||
local rw = rail_width(dv)
|
||||
local start_y = get_underline_y(dv, first_message.line)
|
||||
local fin_y = get_underline_y(dv, last_message.line)
|
||||
|
||||
-- connect with lens
|
||||
local line_x = x + rw
|
||||
for i, message in ipairs(rail) do
|
||||
-- connect with lens
|
||||
local lx, _ = dv:get_line_screen_position(message.line)
|
||||
local ly = get_underline_y(dv, message.line)
|
||||
local line_messages = messages.lines[message.line]
|
||||
if line_messages ~= nil then
|
||||
local column = line_messages[1].column
|
||||
local message_left = line_messages[1].message:sub(1, column - 1)
|
||||
local line_color = get_message_group_color(line_messages)
|
||||
local xoffset = (x + rw) % 2
|
||||
local line_w = dv:get_font():get_width(message_left) - line_x + lx
|
||||
renderutil.draw_dotted_line(x + rw + xoffset, ly, line_w, 'x', line_color)
|
||||
-- draw curve
|
||||
ly = ly - rw * (i == 1 and 0 or 1) + (i ~= 1 and 1 or 0)
|
||||
renderutil.draw_quarter_circle(x, ly, rw, style.accent, i > 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- draw vertical part
|
||||
local height = fin_y - start_y + 1 - rw * 2
|
||||
renderer.draw_rect(x, start_y + rw, 1, height, style.accent)
|
||||
|
||||
end
|
||||
|
||||
local DocView_draw = DocView.draw
|
||||
function DocView:draw()
|
||||
DocView_draw(self)
|
||||
|
||||
local filename = self.doc.filename
|
||||
if filename == nil then return end
|
||||
filename = core.project_absolute_path(filename)
|
||||
local messages = lint.messages[filename]
|
||||
if messages == nil or not messages.rails_sorted then return end
|
||||
local rails = messages.rails
|
||||
|
||||
local pos, size = self.position, self.size
|
||||
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
|
||||
for i = 1, #rails do
|
||||
draw_gutter_rail(self, i, messages)
|
||||
end
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
||||
local lens_underlines = {
|
||||
|
||||
blank = function () end,
|
||||
|
||||
solid = function (x, y, width, color)
|
||||
renderer.draw_rect(x, y, width, 1, color)
|
||||
end,
|
||||
|
||||
dots = function (x, y, width, color)
|
||||
renderutil.draw_dotted_line(x, y, width, 'x', color)
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
local function draw_lens_underline(x, y, width, color)
|
||||
local lens_style = config.lint.lens_style or "solid"
|
||||
if type(lens_style) == "string" then
|
||||
local fn = lens_underlines[lens_style] or lens_underlines.blank
|
||||
fn(x, y, width, color)
|
||||
elseif type(lens_style) == "function" then
|
||||
lens_style(x, y, width, color)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_or_default(t, index, default)
|
||||
if t ~= nil and t[index] ~= nil then
|
||||
return t[index]
|
||||
else
|
||||
return default
|
||||
end
|
||||
end
|
||||
|
||||
local DocView_draw_line_text = DocView.draw_line_text
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
DocView_draw_line_text(self, idx, x, y)
|
||||
|
||||
local lp = self.doc.__lintplus
|
||||
if lp == nil then return end
|
||||
|
||||
local yy = get_underline_y(self, idx)
|
||||
local file_messages = lint.messages[core.project_absolute_path(self.doc.filename)]
|
||||
if file_messages == nil then return end
|
||||
local messages = file_messages.lines[idx]
|
||||
if messages == nil then return end
|
||||
|
||||
local underline_start = messages[1].column
|
||||
|
||||
local font = self:get_font()
|
||||
local underline_color = get_message_group_color(messages)
|
||||
local line = self.doc.lines[idx]
|
||||
local line_left = line:sub(1, underline_start - 1)
|
||||
local line_right = line:sub(underline_start, -2)
|
||||
local underline_x = font:get_width(line_left)
|
||||
local w = font:get_width('w')
|
||||
|
||||
local msg_x = x + w * 3 + underline_x + font:get_width(line_right)
|
||||
local text_y = y + self:get_line_text_y_offset()
|
||||
for i, msg in ipairs(messages) do
|
||||
local text_color = get_or_default(style.lint, msg.kind, underline_color)
|
||||
msg_x = renderer.draw_text(font, msg.message, msg_x, text_y, text_color)
|
||||
if i < #messages then
|
||||
msg_x = renderer.draw_text(font, ", ", msg_x, text_y, style.syntax.comment)
|
||||
end
|
||||
end
|
||||
|
||||
local underline_width = msg_x - x - underline_x
|
||||
draw_lens_underline(x + underline_x, yy, underline_width, underline_color)
|
||||
end
|
||||
|
||||
|
||||
local function table_add(t, d)
|
||||
for _, v in ipairs(d) do
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function kind_pretty_name(kind)
|
||||
return (config.kind_pretty_names or default_kind_pretty_names)[kind]
|
||||
end
|
||||
|
||||
|
||||
local function get_error_messages(doc, ordered)
|
||||
if not doc then return nil end
|
||||
local messages = lint.messages[core.project_absolute_path(doc.filename)]
|
||||
if not messages then return nil end
|
||||
if not ordered then return messages.lines end
|
||||
-- sort lines
|
||||
local lines = {}
|
||||
for line, _ in pairs(messages.lines) do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
table.sort(lines, function(a, b) return a < b end)
|
||||
local lines_info = {}
|
||||
-- store in array instead of dictionary to keep insertion order
|
||||
for _, line in ipairs(lines) do
|
||||
table.insert(
|
||||
lines_info,
|
||||
{line = line, table.unpack(messages.lines[line])}
|
||||
)
|
||||
end
|
||||
return lines_info
|
||||
end
|
||||
|
||||
|
||||
local function get_current_error(doc)
|
||||
local file_messages = get_error_messages(doc)
|
||||
local line, message = math.huge, nil
|
||||
for ln, messages in pairs(file_messages) do
|
||||
local msg = messages[1]
|
||||
if msg.kind == "error" and ln < line then
|
||||
line, message = ln, msg
|
||||
end
|
||||
end
|
||||
if message ~= nil then
|
||||
return line, message.kind, message.message
|
||||
end
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
|
||||
local function goto_prev_message()
|
||||
local doc = core.active_view.doc
|
||||
local current_line = doc:get_selection()
|
||||
local file_messages = get_error_messages(doc, true)
|
||||
if file_messages ~= nil then
|
||||
local prev = nil
|
||||
local found = false
|
||||
local last = nil
|
||||
for _, line_info in pairs(file_messages) do
|
||||
local line = line_info.line
|
||||
if current_line <= line then
|
||||
found = true
|
||||
end
|
||||
if not found then
|
||||
prev = line
|
||||
end
|
||||
last = line
|
||||
end
|
||||
local line = prev or last
|
||||
if line then
|
||||
doc:set_selection(line, 1, line, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function goto_next_message()
|
||||
local doc = core.active_view.doc
|
||||
local current_line = doc:get_selection()
|
||||
local file_messages = get_error_messages(doc, true)
|
||||
if file_messages ~= nil then
|
||||
local first = nil
|
||||
local next = nil
|
||||
for _, line_info in pairs(file_messages) do
|
||||
local line = line_info.line
|
||||
if not first then
|
||||
first = line
|
||||
end
|
||||
if line > current_line then
|
||||
next = line
|
||||
break
|
||||
end
|
||||
end
|
||||
local line = next or first
|
||||
if line then
|
||||
doc:set_selection(line, 1, line, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_status_view_items()
|
||||
local doc = core.active_view.doc
|
||||
local line1, _, line2, _ = doc:get_selection()
|
||||
local file_messages = get_error_messages(doc)
|
||||
if file_messages ~= nil then
|
||||
if file_messages[line1] ~= nil and line1 == line2 then
|
||||
local msg = file_messages[line1][1]
|
||||
return {
|
||||
kind_pretty_name(msg.kind), ": ",
|
||||
style.text, msg.message,
|
||||
}
|
||||
else
|
||||
local line, kind, message = get_current_error(doc)
|
||||
if line ~= nil then
|
||||
return {
|
||||
"line ", tostring(line), " ", kind_pretty_name(kind), ": ",
|
||||
style.text, message,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
if StatusView["add_item"] then
|
||||
core.status_view:add_item({
|
||||
predicate = function()
|
||||
local doc = core.active_view.doc
|
||||
if
|
||||
doc and doc.filename -- skip new files
|
||||
and
|
||||
getmetatable(core.active_view) == DocView
|
||||
and
|
||||
(
|
||||
lint.get_linter_for_doc(doc)
|
||||
or
|
||||
lint.messages[core.project_absolute_path(doc.filename)]
|
||||
)
|
||||
then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
name = "lint+:message",
|
||||
alignment = StatusView.Item.LEFT,
|
||||
get_item = get_status_view_items,
|
||||
command = function()
|
||||
local doc = core.active_view.doc
|
||||
local line = get_current_error(doc)
|
||||
if line ~= nil then
|
||||
doc:set_selection(line, 1, line, 1)
|
||||
end
|
||||
end,
|
||||
position = -1,
|
||||
tooltip = "Lint+ error message",
|
||||
separator = core.status_view.separator2
|
||||
})
|
||||
else
|
||||
local StatusView_get_items = StatusView.get_items
|
||||
function StatusView:get_items()
|
||||
local left, right = StatusView_get_items(self)
|
||||
local doc = core.active_view.doc
|
||||
|
||||
if
|
||||
doc and doc.filename -- skip new files
|
||||
and
|
||||
getmetatable(core.active_view) == DocView
|
||||
and
|
||||
(
|
||||
lint.get_linter_for_doc(doc)
|
||||
or
|
||||
lint.messages[core.project_absolute_path(doc.filename)]
|
||||
)
|
||||
then
|
||||
local items = get_status_view_items()
|
||||
if #items > 0 then
|
||||
table.insert(left, {style.dim, self.separator2, table.unpack(items)})
|
||||
end
|
||||
end
|
||||
|
||||
return left, right
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add(DocView, {
|
||||
["lint+:check"] = function ()
|
||||
lint.check(core.active_view.doc)
|
||||
end
|
||||
})
|
||||
|
||||
command.add(DocView, {
|
||||
["lint+:goto-previous-message"] = function ()
|
||||
goto_prev_message()
|
||||
end
|
||||
})
|
||||
|
||||
command.add(DocView, {
|
||||
["lint+:goto-next-message"] = function ()
|
||||
goto_next_message()
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["alt+up"] = "lint+:goto-previous-message",
|
||||
["alt+down"] = "lint+:goto-next-message"
|
||||
}
|
||||
|
||||
|
||||
--- LINTER PLUGINS ---
|
||||
|
||||
|
||||
function lint.add(name)
|
||||
return function (linter)
|
||||
lint.index[name] = linter
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- SETUP ---
|
||||
|
||||
|
||||
lint.setup = {}
|
||||
|
||||
function lint.setup.lint_on_doc_load()
|
||||
|
||||
local doc_load = Doc.load
|
||||
function Doc:load(filename)
|
||||
doc_load(self, filename)
|
||||
if not self.filename then return end
|
||||
if lint.get_linter_for_doc(self) ~= nil then
|
||||
lint.check(self)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function lint.setup.lint_on_doc_save()
|
||||
|
||||
local doc_save = Doc.save
|
||||
function Doc:save(filename, abs_filename)
|
||||
doc_save(self, filename, abs_filename)
|
||||
if lint.get_linter_for_doc(self) ~= nil then
|
||||
lint.check(self)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function lint.enable_async()
|
||||
core.error("lint+: calling enable_async() is not needed anymore")
|
||||
end
|
||||
|
||||
|
||||
--- LINTER CREATION UTILITIES ---
|
||||
|
||||
|
||||
lint.filename = {}
|
||||
lint.args = {}
|
||||
|
||||
|
||||
local function map(tab, fn)
|
||||
local result = {}
|
||||
for k, v in pairs(tab) do
|
||||
local mapped, mode = fn(k, v)
|
||||
if mode == "append" then
|
||||
table_add(result, mapped)
|
||||
elseif type(k) == "number" then
|
||||
table.insert(result, mapped)
|
||||
else
|
||||
result[k] = mapped
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
function lint.command(cmd)
|
||||
return function (filename)
|
||||
return map(cmd, function (k, v)
|
||||
if type(k) == "number" and v == lint.filename then
|
||||
return filename
|
||||
end
|
||||
return v
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lint.args_command(cmd, config_option)
|
||||
return function (filename)
|
||||
local c = map(cmd, function (k, v)
|
||||
if type(k) == "number" and v == lint.args then
|
||||
local args = lint.config[config_option] or {}
|
||||
return args, "append"
|
||||
end
|
||||
return v
|
||||
end)
|
||||
return lint.command(c)(filename)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lint.interpreter(i)
|
||||
local patterns = {
|
||||
info = i.info,
|
||||
hint = i.hint,
|
||||
warning = i.warning,
|
||||
error = i.error,
|
||||
}
|
||||
local strip_pattern = i.strip
|
||||
|
||||
return function (_, line)
|
||||
local line_processed = false
|
||||
return function ()
|
||||
if line_processed then
|
||||
return nil
|
||||
end
|
||||
for kind, patt in pairs(patterns) do
|
||||
assert(
|
||||
type(patt) == "string",
|
||||
"lint+: interpreter pattern must be a string")
|
||||
local file, ln, column, message = line:match(patt)
|
||||
if file then
|
||||
if strip_pattern then
|
||||
message = message:gsub(strip_pattern, "")
|
||||
end
|
||||
line_processed = true
|
||||
return file, tonumber(ln), tonumber(column), kind, message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lint.load(linter)
|
||||
if type(linter) == "table" then
|
||||
for _, v in ipairs(linter) do
|
||||
require("plugins.lintplus.linters." .. v)
|
||||
end
|
||||
elseif type(linter) == "string" then
|
||||
require("plugins.lintplus.linters." .. linter)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if type(config.lint) ~= "table" then
|
||||
config.lint = {}
|
||||
end
|
||||
lint.config = config.lint
|
||||
|
||||
|
||||
--- END ---
|
||||
|
||||
return lint
|
||||
388
.config/lite-xl/plugins/lintplus/json.lua
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
46
.config/lite-xl/plugins/lintplus/linter.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
-- lite-xl 1.16
|
||||
|
||||
-- linter compatibility module for lint+
|
||||
-- this module simply defines a linter.add_language function for compatibility
|
||||
-- with the existing linter module.
|
||||
-- note that linter modules are not capable of using all of lint+'s
|
||||
-- functionality: namely, they cannot use the four available levels of severity.
|
||||
-- all messages will have the warning severity level.
|
||||
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
local linter = {}
|
||||
local name_counter = 0
|
||||
|
||||
function linter.add_language(t)
|
||||
lintplus.add("compat.linter"..name_counter) {
|
||||
filename = t.file_patterns,
|
||||
procedure = {
|
||||
command = lintplus.command(
|
||||
t.command
|
||||
:gsub("%$FILENAME", "$filename")
|
||||
:gsub("%$ARGS", table.concat(t.args, ' '))
|
||||
),
|
||||
|
||||
-- can't use the lintplus interpreter simply because it doesn't work
|
||||
-- exactly as linter does
|
||||
interpreter = function (filename, line)
|
||||
local yielded_message = false
|
||||
return function ()
|
||||
if yielded_message then return nil end
|
||||
local ln, column, message = line:match(t.warning_pattern)
|
||||
if ln then
|
||||
-- we return the original filename to show all warnings
|
||||
-- because... say it with me... that's how linter works!!
|
||||
yielded_message = true
|
||||
return filename, tonumber(ln), tonumber(column), "warning", message
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
name_counter = name_counter + 1
|
||||
end
|
||||
|
||||
|
||||
return linter
|
||||
28
.config/lite-xl/plugins/lintplus/linters/luacheck.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-- luacheck plugin for lint+
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.luacheck_args: table[string]
|
||||
-- passes the specified arguments to luacheck
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
lintplus.add("luacheck") {
|
||||
filename = "%.lua$",
|
||||
procedure = {
|
||||
command = lintplus.args_command(
|
||||
{ "luacheck",
|
||||
lintplus.args,
|
||||
"--formatter",
|
||||
"visual_studio",
|
||||
lintplus.filename },
|
||||
"luacheck_args"
|
||||
),
|
||||
interpreter = lintplus.interpreter {
|
||||
warning = "(.-)%((%d+),(%d+)%) : warning .-: (.+)",
|
||||
error = "(.-)%((%d+),(%d+)%) : error .-: (.+)",
|
||||
}
|
||||
},
|
||||
}
|
||||
37
.config/lite-xl/plugins/lintplus/linters/nelua.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- Nelua plugin for lint+
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.nelua_mode: "analyze" | "lint"
|
||||
-- changes the linting mode, "analyze" (default) does a complete checking,
|
||||
-- while "lint" only checks for syntax errors.
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local core = require 'core'
|
||||
local lintplus = require 'plugins.lintplus'
|
||||
|
||||
local mode = lintplus.config.nelua_mode or "analyze"
|
||||
|
||||
if mode ~= "analyze" and mode ~= "lint" then
|
||||
core.error("lint+/nelua: invalid nelua_mode '%s'. Available modes: 'analyze', 'lint'", mode)
|
||||
mode = "lint"
|
||||
end
|
||||
|
||||
local command = lintplus.command {
|
||||
'nelua',
|
||||
'--no-color',
|
||||
'--'..mode,
|
||||
lintplus.filename
|
||||
}
|
||||
|
||||
lintplus.add 'nelua' {
|
||||
filename = '%.nelua$',
|
||||
procedure = {
|
||||
command = command,
|
||||
interpreter = lintplus.interpreter {
|
||||
error = "(.-):(%d+):(%d+):.-error: (.+)"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
49
.config/lite-xl/plugins/lintplus/linters/nim.lua
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
-- Nim plugin for lint+
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.use_nimc: bool
|
||||
-- switches the linting backend from `nim check` to `nim c`. this can
|
||||
-- eliminate certain kinds of errors but is less safe due to `nim c` allowing
|
||||
-- staticExec
|
||||
-- config.lint.nim_args: string
|
||||
-- passes the specified arguments to the lint command.
|
||||
-- extra arguments may also be passed via a nim.cfg or config.nims.
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
local nullfile
|
||||
if PLATFORM == "Windows" then
|
||||
nullfile = "NUL"
|
||||
elseif PLATFORM == "Linux" then
|
||||
nullfile = "/dev/null"
|
||||
end
|
||||
|
||||
local cmd = {
|
||||
"nim",
|
||||
"--listFullPaths",
|
||||
"--stdout",
|
||||
lintplus.args,
|
||||
}
|
||||
if nullfile == nil or not lintplus.config.use_nimc then
|
||||
table.insert(cmd, "check")
|
||||
else
|
||||
table.insert(cmd, "-o:" .. nullfile)
|
||||
table.insert(cmd, "c")
|
||||
end
|
||||
table.insert(cmd, lintplus.filename)
|
||||
|
||||
lintplus.add("nim") {
|
||||
filename = "%.nim$",
|
||||
procedure = {
|
||||
command = lintplus.args_command(cmd, "nim_args"),
|
||||
interpreter = lintplus.interpreter {
|
||||
hint = "(.-)%((%d+), (%d+)%) Hint: (.+)",
|
||||
warning = "(.-)%((%d+), (%d+)%) Warning: (.+)",
|
||||
error = "(.-)%((%d+), (%d+)%) Error: (.+)",
|
||||
strip = "%s%[%w+%]$",
|
||||
},
|
||||
},
|
||||
}
|
||||
40
.config/lite-xl/plugins/lintplus/linters/php.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- PHP lint plugin for lint+
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.php_args: {string}
|
||||
-- passes the specified arguments to php
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
lintplus.add("php") {
|
||||
filename = "%.php$",
|
||||
procedure = {
|
||||
command = lintplus.args_command(
|
||||
{
|
||||
"php",
|
||||
"-l",
|
||||
lintplus.args,
|
||||
lintplus.filename
|
||||
},
|
||||
"php_args"
|
||||
),
|
||||
interpreter = function (filename, line, context)
|
||||
local line_processed = false
|
||||
return function ()
|
||||
if line_processed then
|
||||
return nil
|
||||
end
|
||||
local message, file, line_num = line:match(
|
||||
"[%a ]+:%s*(.*)%s+in%s+(%g+)%s+on%sline%s+(%d+)"
|
||||
)
|
||||
if line_num then
|
||||
line_processed = true
|
||||
return filename, tonumber(line_num), 1, "error", message
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
}
|
||||
16
.config/lite-xl/plugins/lintplus/linters/python.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
lintplus.add("flake8") {
|
||||
filename = "%.py$",
|
||||
procedure = {
|
||||
command = lintplus.command(
|
||||
{ "flake8",
|
||||
lintplus.filename },
|
||||
"flake8_args"
|
||||
),
|
||||
interpreter = lintplus.interpreter {
|
||||
warning = "(.-):(%d+):(%d+): [FCW]%d+ (.+)",
|
||||
error = "(.-):(%d+):(%d+): E%d+ (.+)",
|
||||
}
|
||||
},
|
||||
}
|
||||
181
.config/lite-xl/plugins/lintplus/linters/rust.lua
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
-- Rust plugin for lint+
|
||||
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
|
||||
local common = require "core.common"
|
||||
local core = require "core"
|
||||
|
||||
local lintplus = require "plugins.lintplus"
|
||||
local json = require "plugins.lintplus.json"
|
||||
|
||||
|
||||
-- common functions
|
||||
|
||||
|
||||
local function no_op() end
|
||||
|
||||
|
||||
local function parent_directories(filename)
|
||||
|
||||
return function ()
|
||||
filename = lintplus.fs.parent_directory(filename)
|
||||
return filename
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- message processing
|
||||
|
||||
local function message_spans_multiple_lines(message, line)
|
||||
if #message.spans == 0 then return false end
|
||||
for _, span in ipairs(message.spans) do
|
||||
if span.line_start ~= line then
|
||||
return true
|
||||
end
|
||||
end
|
||||
for _, child in ipairs(message.children) do
|
||||
local child_spans_multiple_lines = message_spans_multiple_lines(child, line)
|
||||
if child_spans_multiple_lines then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function process_message(
|
||||
context,
|
||||
message,
|
||||
out_messages,
|
||||
rail
|
||||
)
|
||||
local msg = message.message
|
||||
local span = message.spans[1]
|
||||
|
||||
local kind do
|
||||
local l = message.level
|
||||
if l == "error" or l == "warning" then
|
||||
kind = l
|
||||
elseif l == "error: internal compiler error" then
|
||||
kind = "error"
|
||||
else
|
||||
kind = "info"
|
||||
end
|
||||
end
|
||||
|
||||
local nonprimary_spans = 0
|
||||
for _, sp in ipairs(message.spans) do
|
||||
if not sp.is_primary then
|
||||
nonprimary_spans = nonprimary_spans + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- only assign a rail if there are children or multiple non-primary spans
|
||||
if span ~= nil then
|
||||
local filename = context.workspace_root .. '/' .. span.file_name
|
||||
local line, column = span.line_start, span.column_start
|
||||
|
||||
if rail == nil then
|
||||
if message_spans_multiple_lines(message, line) then
|
||||
rail = context:create_gutter_rail()
|
||||
end
|
||||
end
|
||||
|
||||
for _, sp in ipairs(message.spans) do
|
||||
if sp.label ~= nil and not sp.is_primary then
|
||||
local s_filename = context.workspace_root .. '/' .. span.file_name
|
||||
local s_line, s_column = sp.line_start, sp.column_start
|
||||
table.insert(out_messages,
|
||||
{ s_filename, s_line, s_column, "info", sp.label, rail })
|
||||
end
|
||||
end
|
||||
|
||||
if span.suggested_replacement ~= nil then
|
||||
local suggestion = span.suggested_replacement:match("(.-)\r?\n")
|
||||
if suggestion ~= nil then
|
||||
msg = msg .. " `" .. suggestion .. '`'
|
||||
end
|
||||
end
|
||||
table.insert(out_messages, { filename, line, column, kind, msg, rail })
|
||||
end
|
||||
|
||||
for _, child in ipairs(message.children) do
|
||||
process_message(context, child, out_messages, rail)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_messages(context, event)
|
||||
-- filename, line, column, kind, message
|
||||
local messages = {}
|
||||
process_message(context, event.message, messages)
|
||||
return messages
|
||||
end
|
||||
|
||||
|
||||
-- linter
|
||||
|
||||
lintplus.add("rust") {
|
||||
filename = "%.rs$",
|
||||
procedure = {
|
||||
|
||||
init = function (filename, context)
|
||||
local process = lintplus.ipc.start_process({
|
||||
"cargo", "locate-project", "--workspace"
|
||||
}, lintplus.fs.parent_directory(filename))
|
||||
while true do
|
||||
local exit, _ = process:poll(function (line)
|
||||
local ok, process_result = pcall(json.decode, line)
|
||||
if not ok then return end
|
||||
context.workspace_root =
|
||||
lintplus.fs.parent_directory(process_result.root)
|
||||
end)
|
||||
if exit ~= nil then break end
|
||||
end
|
||||
end,
|
||||
|
||||
command = lintplus.command {
|
||||
set_cwd = true,
|
||||
"cargo", "clippy",
|
||||
"--message-format", "json",
|
||||
"--color", "never",
|
||||
-- "--tests",
|
||||
},
|
||||
|
||||
interpreter = function (filename, line, context)
|
||||
-- initial checks
|
||||
if context.workspace_root == nil then
|
||||
core.error(
|
||||
"lint+/rust: "..filename.." is not situated in a cargo crate"
|
||||
)
|
||||
return no_op
|
||||
end
|
||||
if line:match("^ *Blocking") then
|
||||
return "bail"
|
||||
end
|
||||
|
||||
local ok, event = pcall(json.decode, line)
|
||||
if not ok then return no_op end
|
||||
|
||||
if event.reason == "compiler-message" then
|
||||
local messages = get_messages(context, event)
|
||||
local i = 1
|
||||
|
||||
return function ()
|
||||
local msg = messages[i]
|
||||
if msg ~= nil then
|
||||
i = i + 1
|
||||
return table.unpack(msg)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
else
|
||||
return no_op
|
||||
end
|
||||
end,
|
||||
|
||||
},
|
||||
}
|
||||
42
.config/lite-xl/plugins/lintplus/linters/shellcheck.lua
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
-- shellcheck plugin for lint+
|
||||
|
||||
--- INSTALLATION ---
|
||||
-- In order to use this linter, please ensure you have the shellcheck binary
|
||||
-- in your path. For installation notes please see
|
||||
-- https://github.com/koalaman/shellcheck#user-content-installing
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.shellcheck_args: table[string]
|
||||
-- passes the given arguments to shellcheck.
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
lintplus.add("shellcheck") {
|
||||
filename = "%.sh$",
|
||||
syntax = {
|
||||
"Shell script",
|
||||
"shellscript",
|
||||
"bashscript",
|
||||
"Bash script",
|
||||
"Bash",
|
||||
"bash",
|
||||
},
|
||||
procedure = {
|
||||
command = lintplus.args_command(
|
||||
{ "shellcheck",
|
||||
"--format=gcc",
|
||||
lintplus.args,
|
||||
lintplus.filename
|
||||
},
|
||||
"shellcheck_args"
|
||||
),
|
||||
interpreter = lintplus.interpreter {
|
||||
info = "(.*):(%d+):(%d+): note: (.+)",
|
||||
error = "(.*):(%d+):(%d+): error: (.+)",
|
||||
warning = "(.*):(%d+):(%d+): warning: (.+)",
|
||||
}
|
||||
},
|
||||
}
|
||||
68
.config/lite-xl/plugins/lintplus/linters/v.lua
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
-- v plugin for lint+
|
||||
|
||||
--- INSTALLATION ---
|
||||
-- In order to use this linter, please ensure you have the v binary
|
||||
-- in your $PATH. For installation notes please see
|
||||
-- https://github.com/vlang/v/blob/master/doc/docs.md#installing-v-from-source
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.v_mode: "check" | "check-syntax"
|
||||
-- changes the linting mode. check scans, parses, and checks the files
|
||||
-- without compiling the program (default),
|
||||
-- check-syntax only scan and parse the files, but then stops.
|
||||
-- Useful for very quick syntax checks.
|
||||
-- config.lint.v_args: table[string]
|
||||
-- passes the given arguments to v.
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local core = require "core"
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
local mode = lintplus.config.v_mode or "check"
|
||||
if mode ~= "check" and mode ~= "check-syntax" then
|
||||
core.error("lint+/v: invalid v_mode '%s'. "..
|
||||
"available modes: 'check', 'check-syntax'")
|
||||
return
|
||||
end
|
||||
|
||||
local command
|
||||
if mode == "check" then
|
||||
command = lintplus.command {
|
||||
"v",
|
||||
"-check",
|
||||
"-nocolor",
|
||||
"-shared",
|
||||
"-message-limit", "-1",
|
||||
lintplus.args,
|
||||
lintplus.filename
|
||||
}
|
||||
elseif mode == "check-syntax" then
|
||||
command = lintplus.args_command({
|
||||
"v",
|
||||
"-check-syntax",
|
||||
"-nocolor",
|
||||
"-shared",
|
||||
"-message-limit", "-1",
|
||||
lintplus.args,
|
||||
lintplus.filename
|
||||
}, "v_args")
|
||||
end
|
||||
|
||||
lintplus.add("v") {
|
||||
filename = "%.v$",
|
||||
syntax = {
|
||||
"V",
|
||||
"v",
|
||||
"Vlang",
|
||||
"vlang",
|
||||
},
|
||||
procedure = {
|
||||
command = command,
|
||||
interpreter = lintplus.interpreter {
|
||||
error = "(.*):(%d+):(%d+): error: (.+)",
|
||||
warning = "(.*):(%d+):(%d+): warning: (.+)",
|
||||
},
|
||||
},
|
||||
}
|
||||
54
.config/lite-xl/plugins/lintplus/linters/zig.lua
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
-- Zig plugin for lint+
|
||||
|
||||
--- CONFIG ---
|
||||
|
||||
-- config.lint.zig_mode: "ast-check" | "build"
|
||||
-- changes the linting mode. ast-check is a quick'n'dirty check (default),
|
||||
-- build compiles the tests in a file (but does not run them).
|
||||
-- config.lint.zig_args: table[string]
|
||||
-- passes the given table of arguments to zig test. this does not have any
|
||||
-- effect in "ast-check" mode.
|
||||
|
||||
--- IMPLEMENTATION ---
|
||||
|
||||
local core = require "core"
|
||||
local lintplus = require "plugins.lintplus"
|
||||
|
||||
local mode = lintplus.config.zig_mode or "ast-check"
|
||||
if mode ~= "ast-check" and mode ~= "build" then
|
||||
core.error("lint+/zig: invalid zig_mode '%s'. "..
|
||||
"available modes: 'ast-check', 'build'")
|
||||
return
|
||||
end
|
||||
|
||||
local command
|
||||
if mode == "ast-check" then
|
||||
command = lintplus.command {
|
||||
"zig",
|
||||
"ast-check",
|
||||
"--color", "off",
|
||||
lintplus.filename
|
||||
}
|
||||
elseif mode == "build" then
|
||||
command = lintplus.args_command({
|
||||
"zig",
|
||||
"test",
|
||||
"--color", "off",
|
||||
"-fno-emit-bin",
|
||||
lintplus.args,
|
||||
lintplus.filename
|
||||
}, "zig_args")
|
||||
end
|
||||
|
||||
|
||||
lintplus.add("zig") {
|
||||
filename = "%.zig$",
|
||||
procedure = {
|
||||
command = command,
|
||||
interpreter = lintplus.interpreter {
|
||||
hint = "(.-):(%d+):(%d+): note: (.+)",
|
||||
error = "(.-):(%d+):(%d+): error: (.+)",
|
||||
warning = "(.-):(%d+):(%d+): warning: (.+)",
|
||||
}
|
||||
},
|
||||
}
|
||||
66
.config/lite-xl/plugins/lintplus/liteipc.lua
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
-- liteipc - async IPC for lite
|
||||
|
||||
local liteipc = {}
|
||||
|
||||
local Process = {}
|
||||
Process.__index = Process
|
||||
|
||||
function liteipc.start_process(args, cwd)
|
||||
local proc = setmetatable({
|
||||
popen = process.start(args, {cwd = cwd}),
|
||||
read_from = ""
|
||||
}, Process)
|
||||
return proc
|
||||
end
|
||||
|
||||
function Process.poll(self, callback)
|
||||
local line = ""
|
||||
local read = nil
|
||||
|
||||
while self.read_from == "" and self.popen:returncode() == nil do
|
||||
local stderr = self.popen:read_stderr(1)
|
||||
local stdout = self.popen:read_stdout(1)
|
||||
local out = nil
|
||||
if stderr ~= nil and stderr ~= "" then
|
||||
out = stderr
|
||||
self.read_from = "stderr"
|
||||
elseif stdout ~= nil and stdout ~= "" then
|
||||
out = stdout
|
||||
self.read_from = "stdout"
|
||||
end
|
||||
if out ~= nil then
|
||||
if out ~= "\n" then
|
||||
line = line .. out
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
if self.read_from == "stderr" then
|
||||
read = self.popen:read_stderr(1)
|
||||
else
|
||||
read = self.popen:read_stdout(1)
|
||||
end
|
||||
if read == nil or read == "\n" then
|
||||
if line ~= "" then callback(line) end
|
||||
break
|
||||
else
|
||||
line = line .. read
|
||||
end
|
||||
end
|
||||
|
||||
if not self.popen:running() and read == nil then
|
||||
local exit = "exit"
|
||||
local retcode = self.popen:returncode()
|
||||
if retcode ~= 1 and retcode ~= 0 then
|
||||
exit = "signal"
|
||||
end
|
||||
local errmsg = process.strerror(retcode)
|
||||
return exit, retcode, errmsg
|
||||
end
|
||||
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
return liteipc
|
||||
14
.config/lite-xl/plugins/lintplus/manifest.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"addons": [
|
||||
{
|
||||
"id": "lintplus",
|
||||
"name": "Lint+",
|
||||
"description": "An improved linting plugin.",
|
||||
"version": "0.2",
|
||||
"mod_version": "3",
|
||||
"tags": [
|
||||
"linter"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
44
.config/lite-xl/plugins/lintplus/renderutil.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
-- rendering utilities
|
||||
|
||||
local common = require "core.common"
|
||||
|
||||
local renderutil = {}
|
||||
|
||||
function renderutil.draw_dotted_line(x, y, length, axis, color)
|
||||
if axis == 'x' then
|
||||
for xx = x, x + length, 2 do
|
||||
renderer.draw_rect(xx, y, 1, 1, color)
|
||||
end
|
||||
elseif axis == 'y' then
|
||||
for yy = y, y + length, 2 do
|
||||
renderer.draw_rect(x, yy, 1, 1, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function plot(x, y, color)
|
||||
renderer.draw_rect(x, y, 1, 1, color)
|
||||
end
|
||||
|
||||
function renderutil.draw_quarter_circle(x, y, r, color, flipy)
|
||||
-- inefficient for large circles, but it works.
|
||||
color = { table.unpack(color) }
|
||||
local a = color[4]
|
||||
for dx = 0, r - 1 do
|
||||
for dy = 0, r - 1 do
|
||||
local xx = r - 1 - dx
|
||||
local yy = dy
|
||||
if not flipy then
|
||||
yy = r - 1 - dy
|
||||
end
|
||||
local t = math.abs(math.sqrt(xx*xx + yy*yy) - r + 1)
|
||||
t = common.clamp(1 - t, 0, 1)
|
||||
if t > 0 then
|
||||
color[4] = a * t
|
||||
plot(x + dx, y + dy, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return renderutil
|
||||
BIN
.config/lite-xl/plugins/lintplus/screenshots/1.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
.config/lite-xl/plugins/lintplus/screenshots/2.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
22
.config/lite-xl/plugins/lsp/LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Jefferson González
|
||||
Copyright (c) 2023-present Lite XL team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
271
.config/lite-xl/plugins/lsp/README.md
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
# LSP Plugin for Lite XL editor
|
||||
|
||||
Plugin that provides intellisense for Lite XL by leveraging the [LSP protocol]
|
||||
While still a work in progress it already implements all the most important
|
||||
features to make your life easier while coding with Lite XL. Using it
|
||||
requires __Lite XL v2.1+__ (for __Lite XL v2.0.1__ to __Lite XL v2.0.5__
|
||||
check out the __0.1__ branch). [lint+] is optionally used to render
|
||||
diagnostic messages while you type so make sure to get it. Also, the
|
||||
[snippets] plugin is used to properly process the received autocompletions
|
||||
in the form of snippets, so grab that too.
|
||||
|
||||
To use, clone this project into the __lsp__ directory in your plugins
|
||||
folder. Finally you will need the [Widgets] lib so make sure to also drop
|
||||
it into your lite-xl configs directory. For example:
|
||||
|
||||
```sh
|
||||
cd ~/.config/lite-xl/
|
||||
git clone https://github.com/lite-xl/lite-xl-lsp plugins/lsp
|
||||
git clone https://github.com/lite-xl/lite-xl-widgets libraries/widget
|
||||
git clone https://github.com/liquidev/lintplus plugins/lintplus
|
||||
wget https://raw.githubusercontent.com/vqns/lite-xl-snippets/main/snippets.lua \
|
||||
-O plugins/snippets.lua
|
||||
wget https://raw.githubusercontent.com/vqns/lite-xl-snippets/main/lsp_snippets.lua \
|
||||
-O plugins/lsp_snippets.lua
|
||||
```
|
||||
|
||||
The lite-xl configs directory should have:
|
||||
|
||||
* ~/.config/lite-xl/libraries/widget/
|
||||
* ~/.config/lite-xl/plugins/lsp/
|
||||
* ~/.config/lite-xl/plugins/lintplus/
|
||||
* ~/.config/lite-xl/plugins/snippets.lua
|
||||
* ~/.config/lite-xl/plugins/lsp_snippets.lua
|
||||
|
||||
## Features
|
||||
|
||||
Stuff that is currently implemented:
|
||||
|
||||
* Code auto completion (**ctrl+space**)
|
||||
* Function signatures tooltip (**ctrl+shift+space**)
|
||||
* Current cursor symbol details tooltip on mouse hover or shortcut (**alt+a**)
|
||||
* Goto definition (**alt+d**)
|
||||
* Goto implementation (**alt+shift+d**)
|
||||
* View/jump to current document symbols (**alt+s**)
|
||||
* Find workspace symbols (**alt+shift+s**)
|
||||
* View/jump to symbol references (**alt+f**)
|
||||
* View/jump to document diagnostic messages (**alt+e**)
|
||||
* Document format (**alt+shift+f**)
|
||||
* Optional diagnostics rendering while typing with [lint+]
|
||||
(**alt+shift+e** to toggle)
|
||||
* List all documents with diagnostics (**ctrl+alt+e**)
|
||||
* Snippets processing using the [snippets] plugin
|
||||
|
||||
## Setting a LSP Server
|
||||
|
||||
The easiest method of setting up a lsp server is by using the __[config.lua]__
|
||||
file shipped with the lsp plugin which already contains a list of predefined
|
||||
servers (notice: not all of them have been tested to work). Require this file
|
||||
on your users **init.lua**, call `setup()` on the desired lsp servers or
|
||||
overwrite the configuration options of the defined lsp servers if needed
|
||||
as shown below:
|
||||
|
||||
__Examples:__
|
||||
|
||||
```lua
|
||||
local lspconfig = require "plugins.lsp.config"
|
||||
|
||||
-- Activate clangd without overwriting any settings for c/c++
|
||||
-- autocompletion (requires a compile_commands.json file on
|
||||
-- your project directory usually generated by build tools
|
||||
-- like cmake or meson)
|
||||
-- See: https://clangd.llvm.org/installation.html#project-setup
|
||||
lspconfig.clangd.setup()
|
||||
|
||||
-- Activate gopls
|
||||
lspconfig.gopls.setup()
|
||||
|
||||
-- Activate the lua-language-server, set the server command and
|
||||
-- modify the default settings in order to disable diagnostics.
|
||||
lspconfig.sumneko_lua.setup {
|
||||
command = {
|
||||
"/path/to/lua-language-server/bin/Linux/lua-language-server",
|
||||
"-E",
|
||||
"/path/to/lua-language-server/main.lua"
|
||||
},
|
||||
settings = {
|
||||
Lua = {
|
||||
diagnostics = {
|
||||
enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- Activate intelephense and pass additional initializationOptions
|
||||
-- like the license key and storage path.
|
||||
lspconfig.intelephense.setup {
|
||||
init_options = {
|
||||
licenceKey = "MYLICENSEKEY",
|
||||
storagePath = "/home/myuser/.cache/intelephense"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your preferred LSP server is not listed on the config.lua file feel free
|
||||
to submit a __pull request__ with the addition!
|
||||
|
||||
## Manually Configuring a LSP Server
|
||||
|
||||
Besides the __[config.lua]__ method, you can fully define an lsp server in
|
||||
your user init.lua file. You would need to require the lsp plugin and use the
|
||||
**add_server** function as shown on the following example:
|
||||
|
||||
```lua
|
||||
local lsp = require "plugins.lsp"
|
||||
|
||||
lsp.add_server {
|
||||
-- Name of server
|
||||
name = "intelephense",
|
||||
-- Main language
|
||||
language = "PHP",
|
||||
-- If the server supports multiple languages:
|
||||
-- language = {
|
||||
-- { id = "javascript", pattern = "%.js$" },
|
||||
-- { id = "typescript", pattern = "%.ts$" },
|
||||
-- }
|
||||
-- If no pattern matches, the file extension is used instead.
|
||||
-- File types that are supported by this server
|
||||
file_patterns = { "%.php$" },
|
||||
-- LSP command and optional arguments
|
||||
command = { "intelephense", "--stdio" },
|
||||
-- Optional table of settings to pass into the lsp
|
||||
-- Note that also having a settings.json or settings.lua in
|
||||
-- your workspace directory with a table of settings is supported.
|
||||
settings = {
|
||||
intelephense = {
|
||||
files = {
|
||||
exclude = {
|
||||
"**/.git/**"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
-- Optional table of initializationOptions for the LSP
|
||||
init_options = {
|
||||
storagePath = "/home/myuser/.cache/intelephense"
|
||||
},
|
||||
-- Set by default to 16 should only be modified if having issues with a server
|
||||
requests_per_second = 16,
|
||||
-- Some servers like bash language server support incremental changes
|
||||
-- which are more performant but don't advertise it, set to true to force
|
||||
-- incremental changes even if server doesn't advertise them.
|
||||
incremental_changes = false,
|
||||
-- True to debug the lsp client when developing it
|
||||
verbose = false
|
||||
}
|
||||
```
|
||||
|
||||
## LSP Plugin Settings
|
||||
|
||||
Configuration options that can be used to control the plugin behaviour:
|
||||
|
||||
```lua
|
||||
---Show a symbol hover information when mouse cursor is on top.
|
||||
---@type boolean
|
||||
config.plugins.lsp.mouse_hover = true
|
||||
|
||||
---The amount of time in milliseconds before showing the tooltip.
|
||||
---@type integer
|
||||
config.plugins.lsp.mouse_hover_delay = 300
|
||||
|
||||
---Show diagnostic messages
|
||||
---@type boolean
|
||||
config.plugins.lsp.show_diagnostics = true
|
||||
|
||||
---Stop servers that aren't needed by any of the open files
|
||||
---@type boolean
|
||||
config.plugins.lsp.stop_unneeded_servers = true
|
||||
|
||||
---Set to a file path to log all json
|
||||
---@type string
|
||||
config.plugins.lsp.log_file = ""
|
||||
|
||||
---Setting to true prettyfies json for more readability on the log
|
||||
---but this setting will impact performance so only enable it when
|
||||
---in need of easy to read json output when developing the plugin.
|
||||
---@type boolean
|
||||
config.plugins.lsp.prettify_json = false
|
||||
|
||||
---Send a server stderr output to lite log
|
||||
---@type boolean
|
||||
config.plugins.lsp.log_server_stderr = false
|
||||
|
||||
---Force verbosity off even if a server is configured with verbosity on
|
||||
---@type boolean
|
||||
config.plugins.lsp.force_verbosity_off = false
|
||||
|
||||
---Yield when reading from LSP which may give you better UI responsiveness
|
||||
---when receiving large responses, but will affect LSP performance.
|
||||
---@type boolean
|
||||
config.plugins.lsp.more_yielding = false
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Properly handle multiple opened project directories
|
||||
- [ ] Handle window/showMessage, window/showMessageRequest,
|
||||
$/progress, telemetry/event
|
||||
- [x] Be able to search workspace symbols 'workspace/symbol'
|
||||
- [ ] Completion preselectSupport (needs autocomplete plugin change)
|
||||
- [ ] Add symbol renaming support 'textDocument/rename'
|
||||
- [x] Add Snippets support (this will need a whole standalone [snippets] plugin).
|
||||
- [x] Fix issues when parsing stdout from some lsp servers (really fixed?).
|
||||
- [x] More improvements to autocomplete.lua plugin
|
||||
- [x] Detect view edges and render to the most visible side
|
||||
- [x] Description box, detect view width and expand accordingly
|
||||
- [ ] Support for pre-selected item
|
||||
- [ ] Be able to use a custom sorting field.
|
||||
- [x] Add hover support for function arguments
|
||||
- [x] Add custom tooltip that accents active parameter and signature
|
||||
- [x] Figure out how to get an autocompletion item full documentation with
|
||||
'completionItem/resolve' or any other in order to better populate
|
||||
the new autocomplete item description
|
||||
- [x] (we kill it) Detect if lsp server hangs and restart it (eg: clangd)
|
||||
- [x] Exit LSP server if no open document needs it.
|
||||
- [x] Add hover support for symbols
|
||||
- [x] Generate list of current document symbols for easy document navigation
|
||||
- [x] Goto definition
|
||||
- [x] Display select box when more than one result
|
||||
- [x] Show diagnostics on active document similar to the linter plugin.
|
||||
- [x] Send incremental changes on textDocument/didChange notification
|
||||
since sending the whole document content on big files is slow and bad.
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
Some images to easily visualize the progress :)
|
||||
|
||||
### Completion
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Symbol hover
|
||||

|
||||
|
||||

|
||||
|
||||
### Function signatures
|
||||

|
||||
|
||||
### Document symbols
|
||||

|
||||

|
||||
|
||||
### Goto definition
|
||||

|
||||
|
||||
### Diagnostics rendering using Lint+
|
||||

|
||||
|
||||
|
||||
[LSP protocol]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
||||
[lint+]: https://github.com/liquidev/lintplus
|
||||
[snippets]: https://github.com/vqns/lite-xl-snippets
|
||||
[Widgets]: https://github.com/lite-xl/lite-xl-widgets
|
||||
[config.lua]: config.lua
|
||||
956
.config/lite-xl/plugins/lsp/config.lua
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
--
|
||||
-- A list of servers.
|
||||
--
|
||||
-- Can be used by doing a 'local lspconfig = require "plugins.lsp.config"'
|
||||
-- on your user init.lua, for more details check the README.md
|
||||
--
|
||||
-- Servers taken from:
|
||||
-- https://github.com/prabirshrestha/vim-lsp/wiki/Servers
|
||||
-- https://github.com/mattn/vim-lsp-settings/tree/master/settings
|
||||
--
|
||||
|
||||
local lsp = require "plugins.lsp"
|
||||
local util = require "plugins.lsp.util"
|
||||
local config = require "core.config"
|
||||
local snippets = pcall(require, "plugins.snippets") and config.plugins.lsp.snippets
|
||||
|
||||
---Options that can be passed to a LSP server to overwrite the defaults.
|
||||
---@class lsp.config.options
|
||||
---
|
||||
---Name of server.
|
||||
---@field name string
|
||||
---Main language, eg: C.
|
||||
---Can be a string or a table.
|
||||
---If the table is empty, the file extension will be used instead.
|
||||
---The table should be an array of tables containing `id` and `pattern`.
|
||||
---The `pattern` will be matched with the file path.
|
||||
---Will use the `id` of the first `pattern` that matches.
|
||||
---If no pattern matches, the file extension will be used instead.
|
||||
---@field language string | lsp.server.languagematch[]
|
||||
---File types that are supported by this server.
|
||||
---@field file_patterns string[]
|
||||
---LSP command and optional arguments.
|
||||
---@field command table<integer,string|table>
|
||||
---On Windows, avoid running the LSP server with cmd.exe.
|
||||
---@field windows_skip_cmd? boolean
|
||||
---Enviroment variables to set for the server command.
|
||||
---@field env? table<string, string>
|
||||
---Seconds before closing the server when not needed anymore.
|
||||
---@field quit_timeout? number
|
||||
---Optional table of settings to pass into the LSP.
|
||||
---Note that also having a settings.json or settings.lua in
|
||||
---your workspace directory with a table of settings is supported.
|
||||
---@field settings? table<string,any>
|
||||
---Optional table of initializationOptions for the LSP.
|
||||
---@field init_options? table<string,any>
|
||||
---Optional table of capabilities that will be merged with our default one.
|
||||
---@field custom_capabilities? table<string,any>
|
||||
---Function called when the server has been started.
|
||||
---@field on_start? fun(server: lsp.server)
|
||||
---Set by default to 16 should only be modified if having issues with a server.
|
||||
---@field requests_per_second? integer
|
||||
---Some servers like bash language server support incremental changes
|
||||
---which are more performant but don't advertise it, set to true to force
|
||||
---incremental changes even if server doesn't advertise them.
|
||||
---@field incremental_changes? boolean
|
||||
---Set to true to debug the lsp client when developing it.
|
||||
---@field verbose? boolean
|
||||
|
||||
---@class lsp.config.server
|
||||
---Register the lsp server for usage.
|
||||
---@field setup fun(options?:lsp.config.options)
|
||||
---Get the default lsp server options.
|
||||
---@field get_options fun():lsp.config.options
|
||||
|
||||
---Helper to register a language server.
|
||||
---@param options lsp.config.options
|
||||
---@return lsp.config.server
|
||||
local function add_lsp(options)
|
||||
return {
|
||||
setup = function(user_options)
|
||||
local merged_options = util.deep_merge(options, user_options)
|
||||
lsp.add_server(merged_options)
|
||||
end,
|
||||
get_options = function()
|
||||
return options
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
---List of predefined language servers that can be easily enabled at runtime.
|
||||
---@class lsp.config
|
||||
local lspconfig = {}
|
||||
|
||||
---# bash-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/bash-lsp/bash-language-server
|
||||
--- __Installation__: `npm i -g bash-language-server`
|
||||
--- __Note__: also install `shellcheck` for linting
|
||||
lspconfig.bashls = add_lsp {
|
||||
name = "bash-language-server",
|
||||
language = "shellscript",
|
||||
file_patterns = { "%.sh$" },
|
||||
command = { "bash-language-server", "start" },
|
||||
incremental_changes = true,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# ccls
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/MaskRay/ccls/
|
||||
--- __Installation__: https://github.com/MaskRay/ccls/wiki
|
||||
lspconfig.ccls = add_lsp {
|
||||
name = "ccls",
|
||||
language = {
|
||||
{ id = "c", pattern = "%.[ch]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]pp$" },
|
||||
{ id = "cpp", pattern = "%.[CH]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]%+%+$" },
|
||||
},
|
||||
file_patterns = {
|
||||
"%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$",
|
||||
"%.cc$", "%.C$", "%.cxx$", "%.c++$", "%.hh$",
|
||||
"%.H$", "%.hxx$", "%.h++$", "%.objc$", "%.objcpp$"
|
||||
},
|
||||
command = { "ccls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# clangd
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://clangd.llvm.org/
|
||||
--- __Installation__: install the clang software package on your system
|
||||
--- __Note__: See https://clangd.llvm.org/installation.html#project-setup
|
||||
lspconfig.clangd = add_lsp {
|
||||
name = "clangd",
|
||||
language = {
|
||||
{ id = "c", pattern = "%.[ch]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]pp$" },
|
||||
{ id = "cpp", pattern = "%.[CH]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]%+%+$" },
|
||||
},
|
||||
file_patterns = {
|
||||
"%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$",
|
||||
"%.cc$", "%.C$", "%.cxx$", "%.c++$", "%.hh$",
|
||||
"%.H$", "%.hxx$", "%.h++$", "%.objc$", "%.objcpp$"
|
||||
},
|
||||
command = { "clangd", "-background-index" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Clojure
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://clojure-lsp.github.io/
|
||||
--- __Installation__: https://clojure-lsp.github.io/clojure-lsp/installation/
|
||||
lspconfig.clojure_lsp = add_lsp {
|
||||
name = "clojure-lsp",
|
||||
language = "clojure",
|
||||
file_patterns = { "%.clj$", "%.cljs$", "%.clc$", "%.edn$" },
|
||||
command = { "clojure-lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Crystal
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/elbywan/crystalline
|
||||
--- __Installation__: 'paru -S crystalline-bin'
|
||||
lspconfig.crystalline = add_lsp {
|
||||
name = "crystalline",
|
||||
language = "crystal",
|
||||
file_patterns = { "%.cr$" },
|
||||
command = { "crystalline", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vscode-css-languageserver
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vscode-langservers/vscode-css-languageserver-bin
|
||||
--- __Installation__: `npm install -g vscode-css-languageserver-bin`
|
||||
--- or `pacman -S vscode-css-languageserver`
|
||||
lspconfig.cssls = add_lsp {
|
||||
name = "css-languageserver",
|
||||
language = "css",
|
||||
file_patterns = { "%.css$", "%.less$", "%.sass$" },
|
||||
command = {
|
||||
{
|
||||
'vscode-css-languageserver',
|
||||
'vscode-css-language-server',
|
||||
'css-languageserver'
|
||||
},
|
||||
'--stdio'
|
||||
},
|
||||
fake_snippets = true,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# D
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/Pure-D/serve-d
|
||||
--- __Installation__: https://github.com/Pure-D/serve-d?tab=readme-ov-file#installation
|
||||
lspconfig.serve_d = add_lsp {
|
||||
name = "serve_d",
|
||||
language = "d",
|
||||
file_patterns = { "%.di?$" },
|
||||
command = { "serve-d" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# dartls
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/dart-lang/sdk
|
||||
--- __Installation__: Provided in dart sdk
|
||||
lspconfig.dartls = add_lsp {
|
||||
name = "dart",
|
||||
language = "dart",
|
||||
file_patterns = { "%.dart$" },
|
||||
command = { "dart", "language-server", "--protocol=lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Deno
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://deno.land/manual/advanced/language_server
|
||||
--- __Installation__: Provided in Deno runtime
|
||||
lspconfig.deno = add_lsp {
|
||||
name = "deno",
|
||||
language = {
|
||||
{ id = "javascript", pattern = "%.js$" },
|
||||
{ id = "javascriptreact", pattern = "%.jsx$" },
|
||||
{ id = "typescript", pattern = "%.ts$" },
|
||||
{ id = "typescriptreact", pattern = "%.tsx$" },
|
||||
},
|
||||
file_patterns = { "%.[tj]s$", "%.[tj]sx$" },
|
||||
command = { 'deno', 'lsp' },
|
||||
verbose = false,
|
||||
settings = {
|
||||
deno = {
|
||||
enable = true,
|
||||
unstable = true,
|
||||
config = "./deno.json",
|
||||
importMap = "./import_map.json",
|
||||
lint = true,
|
||||
codeLens = {
|
||||
implementations = true,
|
||||
references = true,
|
||||
test = true,
|
||||
referencesAllFunctions = true
|
||||
},
|
||||
suggest = {
|
||||
names = true,
|
||||
paths = true,
|
||||
completeFunctionCalls = true,
|
||||
imports = {
|
||||
autoDiscover = true,
|
||||
hosts = {
|
||||
["https://deno.land/"] = true,
|
||||
["https://nest.land/"] = true,
|
||||
["https://crux.land/"] = true
|
||||
}
|
||||
},
|
||||
autoImports = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---# Dockerfile
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/rcjsuen/dockerfile-language-server-nodejs
|
||||
--- __Installation__: `npm install -g dockerfile-language-server-nodejs`
|
||||
lspconfig.dockerls = add_lsp {
|
||||
name = "docker-langserver",
|
||||
language = "dockerfile",
|
||||
file_patterns = { "Dockerfile$" },
|
||||
command = { "docker-langserver", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Elixir
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/elixir-lsp/elixir-ls
|
||||
--- __Installation__: 'paru -S elixir-ls'
|
||||
lspconfig.elixirls = add_lsp {
|
||||
name = "elixirls",
|
||||
language = "elixir",
|
||||
file_patterns = { "%.ex$", "%.exs$" },
|
||||
command = { "elixir-ls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Elm
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/elm-tooling/elm-language-server
|
||||
--- __Installation__: `paru -S elm-language-server`
|
||||
lspconfig.elmls = add_lsp {
|
||||
name = "elmls",
|
||||
language = "elm",
|
||||
file_patterns = { "%.elm$" },
|
||||
command = { "elm-language-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Erlang
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/erlang-ls/erlang_ls
|
||||
--- __Installation__: ?
|
||||
lspconfig.erlangls = add_lsp {
|
||||
name = "erlangls",
|
||||
language = "erlang",
|
||||
file_patterns = { "%.erl$", "%.hrl$" },
|
||||
command = { 'Erlang', 'LS', '-t', 'stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# fennel-ls
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://git.sr.ht/~xerool/fennel-ls
|
||||
--- __Installation__:
|
||||
--- ```sh
|
||||
--- git clone https://git.sr.ht/~xerool/fennel-ls
|
||||
--- make -C fennel-ls
|
||||
--- sudo make -C fennel-ls install
|
||||
--- ```
|
||||
lspconfig.fennells = add_lsp {
|
||||
name = "fennel-ls",
|
||||
language = "fennel",
|
||||
file_patterns = { "%.fnl$" },
|
||||
command = { "fennel-ls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Flow - JavaScript
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://flow.org/
|
||||
--- __Installation__: `npm install -g flow-bin`
|
||||
lspconfig.flow = add_lsp {
|
||||
name = "flow",
|
||||
language = "javascript",
|
||||
file_patterns = { "%.js$", "%.jsx$" },
|
||||
command = { "flow", "lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Fortran - fortls
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://fortls.fortran-lang.org/index.html
|
||||
--- __Installation__: `paru -S fortls`
|
||||
lspconfig.fortls = add_lsp {
|
||||
name = "fortls",
|
||||
language = "fortran",
|
||||
file_patterns = { "%.f$", "%.f90$", "%.f95$", "%.F$" },
|
||||
command = { "fortls", "--notify_init" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Gleam
|
||||
--- __Status__: Works (the gleam lsp itself acts kinda weird)
|
||||
--- __Site__: https://gleam.run/
|
||||
--- __Installation__: Included with the gleam compiler binary
|
||||
lspconfig.gleam = add_lsp {
|
||||
name = "gleam",
|
||||
language = "gleam",
|
||||
file_patterns = { "%.gleam$" },
|
||||
command = { "gleam", "lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# gopls
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://pkg.go.dev/golang.org/x/tools/gopls
|
||||
--- __Installation__: `go get -u golang.org/x/tools/gopls`
|
||||
lspconfig.gopls = add_lsp {
|
||||
name = "gopls",
|
||||
language = "go",
|
||||
file_patterns = { "%.go$" },
|
||||
command = { "gopls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# groovy-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/prominic/groovy-language-server
|
||||
--- __Installation__:
|
||||
--- ```sh
|
||||
--- mkdir ~/lsp
|
||||
--- cd ~/lsp
|
||||
--- git clone https://github.com/prominic/groovy-language-server.git
|
||||
--- cd ~/lsp/groovy-language-server
|
||||
--- ./gradlew build
|
||||
--- ```
|
||||
lspconfig.groovyls = add_lsp {
|
||||
name = "groovy-language-server",
|
||||
language = "groovy",
|
||||
file_patterns = { "%.groovy$", "%.gvy$", "%.gy$", "%.gsh$" },
|
||||
-- command = { "java", "-jar", "/path/to/groovy-language-server-all.jar" },
|
||||
command = { "groovy-language-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# haskell-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/haskell/haskell-language-server
|
||||
--- __Installation__: `ghcup install hls`
|
||||
--- or https://github.com/haskell/haskell-language-server#installation
|
||||
lspconfig.hls = add_lsp {
|
||||
name = "haskell-language-server",
|
||||
language = "haskell",
|
||||
file_patterns = { "%.hs$", "%.lhs$" },
|
||||
command = { 'haskell-language-server-wrapper', '--lsp' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vscode-html-languageserver
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vscode-langservers/vscode-html-languageserver-bin
|
||||
--- __Installation__: `npm install --global vscode-html-languageserver-bin`
|
||||
--- or `pacman -S vscode-html-languageserver`
|
||||
lspconfig.html = add_lsp {
|
||||
name = "html-languageserver",
|
||||
language = "html",
|
||||
file_patterns = { "%.html$" },
|
||||
command = {
|
||||
{
|
||||
'vscode-html-languageserver',
|
||||
'vscode-html-language-server',
|
||||
'html-languageserver'
|
||||
},
|
||||
'--stdio'
|
||||
},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# intelephense
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/bmewburn/intelephense-docs
|
||||
--- __Installation__: `npm -g install intelephense`
|
||||
--- __Note__: Set your license and storage by passing the init_options as follows:
|
||||
--- ```lua
|
||||
--- init_options = { licenceKey = "...", storagePath = "/some/path"}
|
||||
--- ```
|
||||
lspconfig.intelephense = add_lsp {
|
||||
name = "intelephense",
|
||||
language = "php",
|
||||
file_patterns = { "%.php$" },
|
||||
command = { "intelephense", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# java
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/eclipse/eclipse.jdt.ls
|
||||
lspconfig.jdtls = add_lsp {
|
||||
name = "jdtls",
|
||||
language = "java",
|
||||
file_patterns = { "%.java$" },
|
||||
command = { "jdtls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Scala
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://scalameta.org/metals/
|
||||
--- __Installation__: `paru -S metals`
|
||||
lspconfig.metals = add_lsp {
|
||||
name = "metals",
|
||||
language = "scala",
|
||||
file_patterns = { "%.scala$" },
|
||||
command = { "metals" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vscode-json-languageserver
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://www.npmjs.com/package/vscode-json-languageserver
|
||||
--- __Installation__: `npm install -g vscode-json-languageserver`
|
||||
--- or `pacman -S vscode-json-languageserver`
|
||||
lspconfig.jsonls = add_lsp {
|
||||
name = "json-languageserver",
|
||||
language = "json",
|
||||
file_patterns = { "%.json$", "%.jsonc$" },
|
||||
command = {
|
||||
{
|
||||
'vscode-json-languageserver',
|
||||
'vscode-json-language-server',
|
||||
'json-languageserver',
|
||||
},
|
||||
'--stdio'
|
||||
},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# kotlin-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/fwcd/kotlin-language-server
|
||||
--- __Installation__: https://github.com/fwcd/kotlin-language-server/releases
|
||||
lspconfig.kotlin_language_server = add_lsp {
|
||||
name = "kotlin-language-server",
|
||||
language = "kotlin",
|
||||
file_patterns = { "%.kt$", "%.kts$", "%.ktm$" },
|
||||
command = { 'kotlin-language-server' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# XML
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/eclipse/lemminx
|
||||
--- __Installation__: 'paru -S lemminx'
|
||||
lspconfig.lemminx = add_lsp {
|
||||
name = "lemminx",
|
||||
language = "xml",
|
||||
file_patterns = { "%.xml$" },
|
||||
command = { "lemminx" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# nil
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/oxalica/nil
|
||||
--- __Installation__: cargo install --git https://github.com/oxalica/nil nil
|
||||
--- __Note__: nix >= 2.4 needs to be installed
|
||||
lspconfig.nillsp = add_lsp {
|
||||
name = "nil",
|
||||
language = "nix",
|
||||
file_patterns = { "%.nix$" },
|
||||
command = { "nil" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# nimlsp
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/PMunch/nimlsp
|
||||
--- __Installation__: `nimble install nimlsp`
|
||||
lspconfig.nimlsp = add_lsp {
|
||||
name = "nimlsp",
|
||||
language = "nim",
|
||||
file_patterns = { "%.nim$" },
|
||||
command = { "nimlsp" },
|
||||
requests_per_second = 25,
|
||||
incremental_changes = false,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# ocaml-lsp
|
||||
--- __Status__: Reported working on https://github.com/jgmdev/lite-xl-lsp/issues/17
|
||||
--- __Site__: https://github.com/ocaml/ocaml-lsp
|
||||
--- __Installation__: https://github.com/ocaml/ocaml-lsp#installation
|
||||
lspconfig.ocaml_lsp = add_lsp {
|
||||
name = "ocaml-lsp",
|
||||
language = "ocaml",
|
||||
file_patterns = { "%.ml$", "%.mli$" },
|
||||
command = { "ocamllsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Odin
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/DanielGavin/ols
|
||||
--- __Installation__: `paru -S odinls`
|
||||
lspconfig.odinls = add_lsp {
|
||||
name = "odinls",
|
||||
language = "odin",
|
||||
file_patterns = { "%.odin$" },
|
||||
command = { "ols" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# omnisharp
|
||||
--- __Status__: Works but, freeze on large projects (https://github.com/ppy/osu.git)
|
||||
--- __Site__: https://github.com/OmniSharp/omnisharp-roslyn
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.omnisharp = add_lsp {
|
||||
name = "omnisharp",
|
||||
language = "csharp",
|
||||
file_patterns = { "%.cs$" },
|
||||
command = { "omnisharp", "-lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# PerlNavigator - Perl
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/bscan/PerlNavigator
|
||||
--- __Installation__: `paru -S perlnavigator`
|
||||
lspconfig.perlnavigator = add_lsp {
|
||||
name = "perlnavigator",
|
||||
language = "perl",
|
||||
file_patterns = { "%.pl$", "%.pm$" },
|
||||
command = { "perlnavigator" },
|
||||
settings = {
|
||||
perlnavigator = {
|
||||
-- The following setting is only needed if you want to set a custom perl path. It already defaults to "perl"
|
||||
perlPath = "perl"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---# python-language-server
|
||||
--- __Status__: Works (deprecated in favor of python-lsp-server)
|
||||
--- __Site__: https://github.com/palantir/python-language-server
|
||||
--- __Installation__: `pip install python-language-server`
|
||||
--- __Note__: Also don't forget to install any additional optional dependencies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.pyls = add_lsp {
|
||||
name = "pyls",
|
||||
language = "python",
|
||||
file_patterns = { "%.py$" },
|
||||
command = { 'pyls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# python-lsp-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/python-lsp/python-lsp-server
|
||||
--- __Installation__: `pip install python-lsp-server`
|
||||
--- __Note__: Also don't forget to install any additional optional dependencies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.pylsp = add_lsp {
|
||||
name = "pylsp",
|
||||
language = "python",
|
||||
file_patterns = { "%.py$" },
|
||||
command = { 'pylsp' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
--# pyright
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/microsoft/pyright
|
||||
--- __Installation__: `pip install pyright` or `npm install -g pyright`
|
||||
lspconfig.pyright = add_lsp {
|
||||
name = "pyright",
|
||||
language = "python",
|
||||
file_patterns = { "%.py$" },
|
||||
command = { "pyright-langserver", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# quick-lint-js
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/quick-lint/quick-lint-js
|
||||
--- __Installation__: Arch Linux: `yay -Syu quick-lint-js`
|
||||
lspconfig.quicklintjs = add_lsp {
|
||||
name = "quick-lint-js",
|
||||
language = {
|
||||
{ id = "javascriptreact", pattern = "%.jsx$" },
|
||||
{ id = "javascript", pattern = "%.js$" },
|
||||
{ id = "typescriptdefinition", pattern = "%.d%.ts$" },
|
||||
{ id = "typescriptsource", pattern = "%.ts$" },
|
||||
{ id = "typescriptreact", pattern = "%.tsx$" },
|
||||
{ id = "typescript", pattern = ".*" },
|
||||
},
|
||||
file_patterns = { "%.[mc]?jsx?$", "%.tsx?$" },
|
||||
command = { "quick-lint-js", "--lsp-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# R
|
||||
-- __Status__: Works
|
||||
-- __Site__:https://github.com/REditorSupport/languageserver#installation
|
||||
-- __Installation__: `paru -S r-languageserver`
|
||||
lspconfig.rlanguageserver = add_lsp {
|
||||
name = "rlanguageserver",
|
||||
language = "r",
|
||||
file_patterns = { "%.r$", "%.R$" },
|
||||
command = {'R', '--slave', '-e', 'languageserver::run()'},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Rust Language Server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/rust-lang/rls
|
||||
--- __Installation__: Install rust on your system
|
||||
lspconfig.rls = add_lsp {
|
||||
name = "rust-language-server",
|
||||
language = "rust",
|
||||
file_patterns = { "%.rs$" },
|
||||
command = { 'rls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Ruby LSP
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/Shopify/ruby-lsp
|
||||
--- __Instalation__: gem install ruby-lsp
|
||||
--- __Note__: Also don't forget to install any additional optional dependecies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.ruby_lsp = add_lsp {
|
||||
name = "ruby-lsp",
|
||||
language = "ruby",
|
||||
file_patterns = { "%.rb$" },
|
||||
command = { 'ruby-lsp' },
|
||||
-- Override command to one below if You want to use it with bundler
|
||||
-- command = { 'bundle', 'exec', 'ruby-lsp'},
|
||||
incremental_changes = true,
|
||||
init_options = {
|
||||
enabledFeatures = {
|
||||
"codeActions",
|
||||
"diagnostics",
|
||||
-- semanticHighlighting should be use only when running with bundle at the moment
|
||||
--"semanticHighlighting",
|
||||
"documentHighlights",
|
||||
"documentLink",
|
||||
"documentSymbols",
|
||||
"foldingRanges",
|
||||
"formatting",
|
||||
"hover",
|
||||
"inlayHint",
|
||||
"onTypeFormatting",
|
||||
"selectionRanges",
|
||||
"completion"
|
||||
},
|
||||
-- enableExperimentalFeatures = true,
|
||||
-- rubyVersionManager = "",
|
||||
},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Rust Analyzer
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://rust-analyzer.github.io/
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.rust_analyzer = add_lsp {
|
||||
name = "rust-analyzer",
|
||||
language = "rust",
|
||||
file_patterns = { "%.rs$" },
|
||||
command = { 'rust-analyzer' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Solargraph
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/castwide/solargraph
|
||||
--- __Installation__: `gem install solargraph`
|
||||
lspconfig.solargraph = add_lsp {
|
||||
name = "solargraph",
|
||||
language = "ruby",
|
||||
file_patterns = { "%.rb$" },
|
||||
command = { 'solargraph', 'stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# sql-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/joe-re/sql-language-server
|
||||
--- __Installation__: `npm i -g sql-language-server`
|
||||
lspconfig.sqlls = add_lsp {
|
||||
name = "sql-language-server",
|
||||
language = "sql",
|
||||
file_patterns = { "%.sql$" },
|
||||
command = { 'sql-language-server', 'up', '--method', 'stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# lua-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/sumneko/lua-language-server
|
||||
--- __Installation__: https://github.com/sumneko/lua-language-server/wiki/Build-and-Run-(Standalone)
|
||||
lspconfig.sumneko_lua = add_lsp {
|
||||
name = "lua-language-server",
|
||||
language = "lua",
|
||||
file_patterns = { "%.lua$" },
|
||||
command = { 'lua-language-server' },
|
||||
verbose = false,
|
||||
settings = {
|
||||
Lua = {
|
||||
completion = {
|
||||
enable = true,
|
||||
callSnippet = snippets and "Replace" or "Disable",
|
||||
keywordSnippet = snippets and "Replace" or "Disable"
|
||||
},
|
||||
develop = {
|
||||
enable = false,
|
||||
debuggerPort = 11412,
|
||||
debuggerWait = false
|
||||
},
|
||||
diagnostics = {
|
||||
enable = true,
|
||||
},
|
||||
hover = {
|
||||
enable = true,
|
||||
viewNumber = true,
|
||||
viewString = true,
|
||||
viewStringMax = 1000
|
||||
},
|
||||
runtime = {
|
||||
version = 'Lua 5.4',
|
||||
path = {
|
||||
"?.lua",
|
||||
"?/init.lua",
|
||||
"?/?.lua",
|
||||
"/usr/share/5.4/?.lua",
|
||||
"/usr/share/lua/5.4/?/init.lua"
|
||||
}
|
||||
},
|
||||
signatureHelp = {
|
||||
enable = true
|
||||
},
|
||||
workspace = {
|
||||
library = {
|
||||
DATADIR,
|
||||
USERDIR
|
||||
},
|
||||
maxPreload = 2000,
|
||||
preloadFileSize = 1000
|
||||
},
|
||||
telemetry = {
|
||||
enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---# svelte-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/sveltejs/language-tools/tree/master/packages/language-server
|
||||
--- __Installation__: `npm install -g svelte-language-server`
|
||||
--- __Note__: Also don't forget to install any additional optional dependencies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.sveltels = add_lsp {
|
||||
name = "sveltels",
|
||||
language = "svelte",
|
||||
file_patterns = { "%.svelte$" },
|
||||
command = { 'svelteserver', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Tailwind CSS
|
||||
--- __Status__: Broken (freezes when writing class names inside html doc, requires new implementation of json.lua)
|
||||
--- __Site__: https://github.com/tailwindlabs/tailwindcss-intellisense
|
||||
--- __Installation__: Arch Linux: `sudo pacman -S tailwindcss-language-server`
|
||||
lspconfig.tailwindcss = add_lsp {
|
||||
name = "tailwindcss",
|
||||
language = "html",
|
||||
file_patterns = { "%.html$"},
|
||||
command = {'tailwindcss-language-server', '--stdio'},
|
||||
fake_snippets = true,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# LaTeX Texlab language server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/latex-lsp/texlab
|
||||
--- __Installation__: git clone https://github.com/latex-lsp/texlab.git , then inside the texlab folder, run: cargo build --release
|
||||
--- __Note__: Rust has to be installed
|
||||
lspconfig.texlab = add_lsp {
|
||||
name = "texlab",
|
||||
language = "latex",
|
||||
file_patterns = { "%.tex$", "%.bib$" , "%.dtx$", "%.sty$", "%.ins$", "%.cls$" },
|
||||
command = { 'texlab' }
|
||||
}
|
||||
|
||||
---# TOML - Taplo
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/tamasfe/taplo
|
||||
--- __Installation__: 'sudo pacman -S taplo-cli'
|
||||
lspconfig.taplo = add_lsp {
|
||||
name = "taplo",
|
||||
language = "toml",
|
||||
file_patterns = { "%.toml$" },
|
||||
command = { "taplo", "lsp", "stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# typescript-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/typescript-language-server/typescript-language-server
|
||||
--- __Installation__: `npm install -g typescript-language-server typescript`
|
||||
lspconfig.tsserver = add_lsp {
|
||||
name = "typescript-language-server",
|
||||
language = {
|
||||
{ id = "javascript", pattern = "%.[cm]?js$" },
|
||||
{ id = "javascriptreact", pattern = "%.jsx$" },
|
||||
{ id = "typescript", pattern = "%.ts$" },
|
||||
{ id = "typescriptreact", pattern = "%.tsx$" },
|
||||
},
|
||||
file_patterns = { "%.jsx?$", "%.[cm]js$", "%.tsx?$" },
|
||||
command = { 'typescript-language-server', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# typst-lsp
|
||||
--- __Status: Works
|
||||
--- __Site__: https://github.com/nvarner/typst-lsp
|
||||
--- __Instalation__: `yay typst-lsp-bin`
|
||||
lspconfig.typst_lsp = add_lsp {
|
||||
name = "typst-lsp",
|
||||
language = "typst",
|
||||
file_patterns = { "%.typ$" },
|
||||
command = { 'typst-lsp' },
|
||||
verbose = false,
|
||||
settings = {
|
||||
exportPdf = "never", -- Choose onType, onSave or never.
|
||||
experimentalFormatterMode = "on" -- Choose on, or off
|
||||
}
|
||||
}
|
||||
|
||||
---# vim-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/iamcco/vim-language-server
|
||||
--- __Installation__: `npm install -g vim-language-server`
|
||||
lspconfig.vimls = add_lsp {
|
||||
name = "vim-language-server",
|
||||
language = "vim",
|
||||
file_patterns = { "%.vim$" },
|
||||
command = { 'vim-language-server', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# V
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vlang/v-analyzer
|
||||
--- __Installation__: https://github.com/vlang/v-analyzer?tab=readme-ov-file#installation
|
||||
lspconfig.v_analyzer = add_lsp {
|
||||
name = "v_analyzer",
|
||||
language = "v",
|
||||
file_patterns = { "%.vv?$", "%.vsh$" },
|
||||
command = { "v-analyzer", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Vala - vala-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vala-lang/vala-language-server
|
||||
--- __Installation__: `paru -S vala-language-server`
|
||||
lspconfig.vala_ls = add_lsp {
|
||||
name = "vala_ls",
|
||||
language = "vala",
|
||||
file_patterns = { "%.vala$" },
|
||||
command = { "vala-language-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vlang-vls
|
||||
--- __Status__: doesn't respond to completion requests (no longer officially maintained in favor of v-analyzer)
|
||||
--- __Site__: https://github.com/vlang/vls
|
||||
--- __Installation__: https://github.com/vlang/vls?tab=readme-ov-file#installation
|
||||
lspconfig.vls = add_lsp {
|
||||
name = "vlang-vls",
|
||||
language = "v",
|
||||
file_patterns = { "%.vv?$", "%.vsh$" },
|
||||
command = { 'vlang-vls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# yaml-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/redhat-developer/yaml-language-server
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.yamlls = add_lsp {
|
||||
name = "yaml-language-server",
|
||||
language = "yaml",
|
||||
file_patterns = { "%.yml$", "%.yaml$" },
|
||||
command = { 'yaml-language-server', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Zig Language Server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/zigtools/zls
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.zls = add_lsp {
|
||||
name = "zls",
|
||||
language = "zig",
|
||||
file_patterns = { "%.zig$" },
|
||||
command = { 'zls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
return lspconfig
|
||||
305
.config/lite-xl/plugins/lsp/diagnostics.lua
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
-- Store diagnostic messages received by an LSP.
|
||||
-- @copyright Jefferson Gonzalez
|
||||
-- @license MIT
|
||||
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local util = require "plugins.lsp.util"
|
||||
local Timer = require "plugins.lsp.timer"
|
||||
|
||||
---@class lsp.diagnostics
|
||||
local diagnostics = {}
|
||||
|
||||
---@class lsp.diagnostics.position
|
||||
---@field line integer
|
||||
---@field character integer
|
||||
|
||||
---@class lsp.diagnostics.range
|
||||
---@field start lsp.diagnostics.position
|
||||
---@field end lsp.diagnostics.position
|
||||
|
||||
---@class lsp.diagnostics.severity
|
||||
---@field ERROR integer
|
||||
---@field WARNING integer
|
||||
---@field INFO integer
|
||||
---@field HINT integer
|
||||
diagnostics.severity = {
|
||||
ERROR = 1,
|
||||
WARNING = 2,
|
||||
INFO = 3,
|
||||
HINT = 4
|
||||
}
|
||||
|
||||
---@alias lsp.diagnostics.severity_code
|
||||
---|>`diagnostics.severity.ERROR`
|
||||
---| `diagnostics.severity.WARNING`
|
||||
---| `diagnostics.severity.INFO`
|
||||
---| `diagnostics.severity.HINT`
|
||||
|
||||
---@class lsp.diagnostics.code_description
|
||||
---@field href string
|
||||
|
||||
---@class lsp.diagnostics.tag
|
||||
---@field UNNECESSARY integer
|
||||
---@field DEPRECATED integer
|
||||
diagnostics.tag = {
|
||||
UNNECESSARY = 1,
|
||||
DEPRECATED = 2
|
||||
}
|
||||
|
||||
---@alias lsp.diagnostics.tag_code
|
||||
---|>`diagnostics.tag.UNNECESSARY`
|
||||
---| `diagnostics.tag.DEPRECATED`
|
||||
|
||||
---@class lsp.diagnostics.location
|
||||
---@field uri string
|
||||
---@field range lsp.diagnostics.range
|
||||
|
||||
---@class lsp.diagnostics.related_information
|
||||
---@field location lsp.diagnostics.location
|
||||
---@field message string
|
||||
|
||||
---A diagnostic message.
|
||||
---@class lsp.diagnostics.message
|
||||
---@field filename string
|
||||
---@field range lsp.diagnostics.position
|
||||
---@field severity lsp.diagnostics.severity_code | integer
|
||||
---@field code integer | string
|
||||
---@field codeDescription lsp.diagnostics.code_description
|
||||
---@field source string
|
||||
---@field message string
|
||||
---@field tags lsp.diagnostics.tag_code[]
|
||||
---@field relatedInformation lsp.diagnostics.related_information
|
||||
|
||||
---A diagnostic item.
|
||||
---@class lsp.diagnostics.item
|
||||
---@field filename string
|
||||
---@field messages lsp.diagnostics.message[]
|
||||
|
||||
---@type table<integer, lsp.diagnostics.item>
|
||||
diagnostics.list = {}
|
||||
|
||||
---@type integer
|
||||
diagnostics.count = 0
|
||||
|
||||
-- Try to load lintplus plugin if available for diagnostics rendering
|
||||
local lintplus_found, lintplus = nil, nil
|
||||
if config.plugins.lintplus ~= false then
|
||||
lintplus_found, lintplus = pcall(require, "plugins.lintplus")
|
||||
end
|
||||
local lintplus_kinds = { "error", "warning", "info", "hint" }
|
||||
|
||||
---List of linplus coroutines to delay messages population
|
||||
---@type table<string,lsp.timer>
|
||||
local lintplus_delays = {}
|
||||
|
||||
---Used to set proper diagnostic type on lintplus
|
||||
---@type table<integer, string>
|
||||
diagnostics.lintplus_kinds = lintplus_kinds
|
||||
|
||||
---@type boolean
|
||||
diagnostics.lintplus_found = lintplus_found
|
||||
|
||||
---@param a lsp.diagnostics.message
|
||||
---@param b lsp.diagnostics.message
|
||||
local function sort_helper(a, b)
|
||||
return a.severity < b.severity
|
||||
end
|
||||
|
||||
---Helper to catch some trange occurances where nil is given as filename
|
||||
---@param filename string|nil
|
||||
---@return string | nil
|
||||
local function get_absolute_path(filename)
|
||||
if not filename then
|
||||
core.error(
|
||||
"[LSP Diagnostics]: nil filename given",
|
||||
tostring(filename)
|
||||
)
|
||||
return nil
|
||||
end
|
||||
return core.project_absolute_path(filename)
|
||||
end
|
||||
|
||||
---Get the position of diagnostics associated to a file.
|
||||
---@param filename string
|
||||
---@return integer | nil
|
||||
function diagnostics.get_index(filename)
|
||||
---@cast filename +nil
|
||||
filename = get_absolute_path(filename)
|
||||
if not filename then return nil end
|
||||
for index, diagnostic in ipairs(diagnostics.list) do
|
||||
if diagnostic.filename == filename then
|
||||
return index
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Get the diagnostics associated to a file.
|
||||
---@param filename string
|
||||
---@param severity? lsp.diagnostics.severity_code | integer
|
||||
---@return lsp.diagnostics.message[] | nil
|
||||
function diagnostics.get(filename, severity)
|
||||
---@cast filename +nil
|
||||
filename = get_absolute_path(filename)
|
||||
if not filename then return nil end
|
||||
for _, diagnostic in ipairs(diagnostics.list) do
|
||||
if diagnostic.filename == filename then
|
||||
if not severity then return diagnostic.messages end
|
||||
|
||||
local results = {}
|
||||
for _, message in ipairs(diagnostic.messages) do
|
||||
if message.severity == severity then table.insert(results, message) end
|
||||
end
|
||||
|
||||
return #results > 0 and results or nil
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Adds a new list of diagnostics associated to a file replacing previous one.
|
||||
---@param filename string
|
||||
---@param messages lsp.diagnostics.message[]
|
||||
---@return boolean
|
||||
function diagnostics.add(filename, messages)
|
||||
local index = diagnostics.get_index(filename)
|
||||
|
||||
---@cast filename +nil
|
||||
filename = get_absolute_path(filename)
|
||||
if not filename then return false end
|
||||
|
||||
table.sort(messages, sort_helper)
|
||||
|
||||
if not index then
|
||||
diagnostics.count = diagnostics.count + 1
|
||||
table.insert(diagnostics.list, {
|
||||
filename = filename, messages = messages
|
||||
})
|
||||
else
|
||||
diagnostics.list[index].messages = messages
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---Removes all diagnostics associated to a file.
|
||||
---@param filename string
|
||||
function diagnostics.clear(filename)
|
||||
local index = diagnostics.get_index(filename)
|
||||
|
||||
if index then
|
||||
table.remove(diagnostics.list, index)
|
||||
diagnostics.count = diagnostics.count - 1
|
||||
end
|
||||
end
|
||||
|
||||
---Get the amount of diagnostics associated to a file.
|
||||
---@param filename string
|
||||
---@param severity? lsp.diagnostics.severity_code | integer
|
||||
function diagnostics.get_messages_count(filename, severity)
|
||||
local index = diagnostics.get_index(filename)
|
||||
|
||||
if not index then return 0 end
|
||||
|
||||
if not severity then return #diagnostics.list[index].messages end
|
||||
|
||||
local count = 0
|
||||
for _, message in ipairs(diagnostics.list[index].messages) do
|
||||
if message.severity == severity then count = count + 1 end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
---@param doc core.doc
|
||||
function diagnostics.lintplus_init_doc(doc)
|
||||
if lintplus_found then
|
||||
lintplus.init_doc(doc.filename, doc)
|
||||
end
|
||||
end
|
||||
|
||||
---Remove registered diagnostics from lintplus for the given file or for
|
||||
---all files if no filename is given.
|
||||
---@param filename? string
|
||||
---@param force boolean
|
||||
function diagnostics.lintplus_clear_messages(filename, force)
|
||||
if lintplus_found then
|
||||
if
|
||||
not force and lintplus_delays[filename]
|
||||
and
|
||||
lintplus_delays[filename]:running()
|
||||
then
|
||||
return
|
||||
end
|
||||
if filename then
|
||||
lintplus.clear_messages(filename)
|
||||
else
|
||||
for fname, _ in pairs(lintplus.messages) do
|
||||
if lintplus_delays[fname] then
|
||||
lintplus_delays[fname]:stop()
|
||||
lintplus_delays[fname] = nil
|
||||
end
|
||||
lintplus.clear_messages(fname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param filename string
|
||||
function diagnostics.lintplus_populate(filename)
|
||||
if lintplus_found then
|
||||
diagnostics.lintplus_clear_messages(filename, true)
|
||||
|
||||
if not filename then
|
||||
for _, diagnostic in ipairs(diagnostics.list) do
|
||||
local fname = core.normalize_to_project_dir(diagnostic.filename)
|
||||
for _, message in pairs(diagnostic.messages) do
|
||||
local line, col = util.toselection(message.range)
|
||||
local text = message.message
|
||||
local kind = lintplus_kinds[message.severity]
|
||||
|
||||
lintplus.add_message(fname, line, col, kind, text)
|
||||
end
|
||||
end
|
||||
else
|
||||
local messages = diagnostics.get(filename)
|
||||
if messages then
|
||||
for _, message in pairs(messages) do
|
||||
local line, col = util.toselection(message.range)
|
||||
local text = message.message
|
||||
local kind = lintplus_kinds[message.severity]
|
||||
|
||||
lintplus.add_message(
|
||||
core.normalize_to_project_dir(filename),
|
||||
line, col, kind, text
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param filename string
|
||||
---@param user_typed boolean
|
||||
function diagnostics.lintplus_populate_delayed(filename)
|
||||
if lintplus_found then
|
||||
if not lintplus_delays[filename] then
|
||||
lintplus_delays[filename] = Timer(
|
||||
config.plugins.lsp.diagnostics_delay or 500,
|
||||
true
|
||||
)
|
||||
lintplus_delays[filename].on_timer = function()
|
||||
diagnostics.lintplus_populate(filename)
|
||||
lintplus_delays[filename] = nil
|
||||
end
|
||||
lintplus_delays[filename]:start()
|
||||
else
|
||||
lintplus_delays[filename]:reset()
|
||||
lintplus_delays[filename]:start()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return diagnostics
|
||||
29
.config/lite-xl/plugins/lsp/fonts/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Fonts used by the LSP Plugin
|
||||
|
||||
This directory holds fonts to enhance the LSP expirience. They are
|
||||
generated by copying only the desired glyphs from other known fonts
|
||||
([Nerd Fonts](https://www.nerdfonts.com/)).
|
||||
|
||||
## Requirements
|
||||
|
||||
* Symbols-2048-em Nerd Font Complete Mono.ttf - symbols.ttf
|
||||
|
||||
**Resources:**
|
||||
* https://www.nerdfonts.com/font-downloads
|
||||
* https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.0/NerdFontsSymbolsOnly.zip
|
||||
|
||||
## How to generate
|
||||
|
||||
Copy all the required base fonts into this directory and run:
|
||||
|
||||
```sh
|
||||
./generate-fonts.py
|
||||
```
|
||||
or
|
||||
```sh
|
||||
fontforge -script generate-fonts.py
|
||||
```
|
||||
|
||||
## Generated Fonts
|
||||
|
||||
* `symbols.ttf` - LSP symbols font used as icons on the autocomplete plugin
|
||||
101
.config/lite-xl/plugins/lsp/fonts/generate-fonts.py
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/fontforge -script
|
||||
#
|
||||
# Generates a small LSP symbol icons font using 'Symbols Nerd Font'
|
||||
#
|
||||
# Usage:
|
||||
# fontforge -script generate-font.py
|
||||
#
|
||||
# References:
|
||||
# https://www.nerdfonts.com/font-downloads
|
||||
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.0/NerdFontsSymbolsOnly.zip
|
||||
#
|
||||
import fontforge
|
||||
|
||||
# Define the path to the source font file
|
||||
# Recommended font from the Symbols Nerd Font package is:
|
||||
# Symbols-2048-em Nerd Font Complete Mono.ttf
|
||||
src_font_path = "Symbols-2048-em Nerd Font Complete Mono.ttf"
|
||||
|
||||
# List of symbols to copy
|
||||
# The symbol mappings were taken from:
|
||||
# * https://github.com/onsails/lspkind.nvim
|
||||
# * https://github.com/TorchedSammy/lite-xl-lspkind
|
||||
symbols = [
|
||||
#
|
||||
# Nerdicons Preset
|
||||
#
|
||||
'', # 0xF77E Text
|
||||
'', # 0xF6A6 Method
|
||||
'', # 0xF794 Function
|
||||
'', # 0xF423 Constructor
|
||||
'ﰠ', # 0xFC20 Field
|
||||
'', # 0xF52A Variable
|
||||
'ﴯ', # 0xFD2F Class
|
||||
'', # 0xF0E8 Interface
|
||||
'', # 0xF487 Module
|
||||
'ﰠ', # 0xFC20 Property
|
||||
'塞', # 0xF96C Unit
|
||||
'', # 0xF89F Value
|
||||
'', # 0xF15D Enum
|
||||
'', # 0xF80A Keyword
|
||||
'', # 0xF44F Snippet
|
||||
'', # 0xF8D7 Color
|
||||
'', # 0xF718 File
|
||||
'', # 0xF706 Reference
|
||||
'', # 0xF74A Folder
|
||||
'', # 0xF15D EnumMember
|
||||
'', # 0xF8FE Constant
|
||||
'פּ', # 0xFB44 Struct
|
||||
'', # 0xF0E7 Event
|
||||
'', # 0xF694 Operator
|
||||
'', # 0xF128 Unknown
|
||||
'', # TypeParameter
|
||||
#
|
||||
# Codicons Preset
|
||||
#
|
||||
'', # Text
|
||||
'', # Method
|
||||
'', # Function
|
||||
'', # Constructor
|
||||
'', # Field
|
||||
'', # Variable
|
||||
'', # Class
|
||||
'', # Interface
|
||||
'', # Module
|
||||
'', # Property
|
||||
'', # Unit
|
||||
'', # Value
|
||||
'', # Enum
|
||||
'', # Keyword
|
||||
'', # Snippet
|
||||
'', # Color
|
||||
'', # File
|
||||
'', # Reference
|
||||
'', # Folder
|
||||
'', # EnumMember
|
||||
'', # Constant
|
||||
'', # Struct
|
||||
'', # Event
|
||||
'', # Operator
|
||||
'', # Unknown
|
||||
'' # TypeParameter
|
||||
]
|
||||
|
||||
# Convert symbols list to an integers list a.k.a. unicode values
|
||||
unicode_values = []
|
||||
for char in symbols:
|
||||
unicode_values.append(ord(char))
|
||||
|
||||
# Load the source font into FontForge
|
||||
src_font = fontforge.open(src_font_path)
|
||||
|
||||
# Remove unwanted glyph
|
||||
src_font.selection.select(*unicode_values)
|
||||
src_font.selection.invert()
|
||||
src_font.clear()
|
||||
|
||||
# Save as new font
|
||||
src_font.fontname = "LSPSymbols"
|
||||
src_font.familyname = "LSP Symbols"
|
||||
src_font.fullname = "LSP Symbols"
|
||||
src_font.generate("symbols.ttf")
|
||||
BIN
.config/lite-xl/plugins/lsp/fonts/symbols.ttf
Normal file
32
.config/lite-xl/plugins/lsp/helpdoc.lua
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---@type core.doc
|
||||
local Doc = require "core.doc"
|
||||
|
||||
---A readonly core.doc.
|
||||
---@class lsp.helpdoc : core.doc
|
||||
local HelpDoc = Doc:extend()
|
||||
|
||||
---Set the help text.
|
||||
---@param text string
|
||||
function HelpDoc:set_text(text)
|
||||
self.lines = {}
|
||||
local i = 1
|
||||
for line in text:gmatch("([^\n]*)\n?") do
|
||||
if line:byte(-1) == 13 then
|
||||
line = line:sub(1, -2)
|
||||
self.crlf = true
|
||||
end
|
||||
table.insert(self.lines, line .. "\n")
|
||||
self.highlighter.lines[i] = false
|
||||
i = i + 1
|
||||
end
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
function HelpDoc:raw_insert(...) end
|
||||
function HelpDoc:raw_remove(...) end
|
||||
function HelpDoc:load(...) end
|
||||
function HelpDoc:reload() end
|
||||
function HelpDoc:save(...) end
|
||||
|
||||
|
||||
return HelpDoc
|
||||
2618
.config/lite-xl/plugins/lsp/init.lua
Normal file
532
.config/lite-xl/plugins/lsp/json.lua
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
--
|
||||
-- json.lua
|
||||
-- Origin: https://github.com/rxi/json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
local error_message = ""
|
||||
|
||||
-- Lets us explicitly add null values to table elements
|
||||
json.null = "{{json::null}}"
|
||||
|
||||
-- Treat numbers longer than 14 digits as a string by adding this to the
|
||||
-- beginning of the string for encoder to recognize. This prevents any data
|
||||
-- loss due to lua 5.2 not supporting big integer numbers and converting big
|
||||
-- integers to floats. The drawback is that the user should manually convert
|
||||
-- these strings to a number. Numbers with less than 15 digits are not affected.
|
||||
json.number_flag = "{{json::num}}"
|
||||
local number_flag_len = #json.number_flag
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
if #res > 0 then
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
else
|
||||
return "{}"
|
||||
end
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
if val == json.null then
|
||||
return "null"
|
||||
elseif
|
||||
#val > number_flag_len
|
||||
and
|
||||
string.sub(val, 1, number_flag_len) == json.number_flag
|
||||
then
|
||||
local num = string.sub(val, number_flag_len+1)
|
||||
return num
|
||||
end
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val, prettify)
|
||||
local out = ( encode(val) )
|
||||
if prettify then
|
||||
return json.prettify(out)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error_message = string.format("%s at line %d col %d", msg, line_count, col_count)
|
||||
end
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
if type(idx) ~= "number" then
|
||||
decode_error(str, #str, "invalid json string")
|
||||
return #str + 1
|
||||
end
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = nil
|
||||
if #s > 14 then
|
||||
n = json.number_flag .. s
|
||||
else
|
||||
n = tonumber(s)
|
||||
end
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
function json.last_error()
|
||||
return error_message
|
||||
end
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
error_message = ""
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
if error_message ~= "" then
|
||||
return false
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function indent(code, level, indent_width)
|
||||
return string.rep(" ", level * indent_width) .. code
|
||||
end
|
||||
|
||||
--- Implemented some json prettifier but not a parser so
|
||||
--- don't expect it to give you parsing errors :D
|
||||
--- @param text string The json string
|
||||
--- @param indent_width? integer The amount of spaces per indentation
|
||||
--- @return string
|
||||
function json.prettify(text, indent_width)
|
||||
if type(text) ~= "string" then
|
||||
return ""
|
||||
end
|
||||
|
||||
local out = ""
|
||||
indent_width = indent_width or 2
|
||||
|
||||
local indent_level = 0
|
||||
local reading_literal = false
|
||||
local previous_was_escape = false
|
||||
local inside_string = false
|
||||
local in_value = false
|
||||
local last_was_bracket = false
|
||||
local string_char = ""
|
||||
local last_char = ""
|
||||
|
||||
for char in text:gmatch(".") do
|
||||
if (char == "{" or char == "[") and not inside_string then
|
||||
if not in_value or last_was_bracket then
|
||||
out = out .. indent(char, indent_level, indent_width) .. "\n"
|
||||
else
|
||||
out = out .. char .. "\n"
|
||||
end
|
||||
last_was_bracket = true
|
||||
in_value = false
|
||||
indent_level = indent_level + 1
|
||||
elseif (char == '"' or char == "'") and not inside_string then
|
||||
inside_string = true
|
||||
string_char = char
|
||||
if not in_value then
|
||||
out = out .. indent(char, indent_level, indent_width)
|
||||
else
|
||||
out = out .. char
|
||||
end
|
||||
elseif inside_string then
|
||||
local pe_set = false
|
||||
if char == "\\" and previous_was_escape then
|
||||
previous_was_escape = false
|
||||
elseif char == "\\" then
|
||||
previous_was_escape = true
|
||||
pe_set = true
|
||||
end
|
||||
out = out .. char
|
||||
if char == string_char and not previous_was_escape then
|
||||
inside_string = false
|
||||
elseif previous_was_escape and not pe_set then
|
||||
previous_was_escape = false
|
||||
end
|
||||
elseif char == ":" then
|
||||
in_value = true
|
||||
last_was_bracket = false
|
||||
out = out .. char .. " "
|
||||
elseif char == "," then
|
||||
in_value = false
|
||||
reading_literal = false
|
||||
out = out .. char .. "\n"
|
||||
elseif char == "}" or char == "]" then
|
||||
indent_level = indent_level - 1
|
||||
if
|
||||
(char == "}" and last_char == "{")
|
||||
or
|
||||
(char == "]" and last_char == "[")
|
||||
then
|
||||
out = out:gsub("%s*\n$", "") .. char
|
||||
else
|
||||
out = out .. "\n" .. indent(char, indent_level, indent_width)
|
||||
end
|
||||
elseif not char:match("%s") and not reading_literal then
|
||||
reading_literal = true
|
||||
if not in_value or last_was_bracket then
|
||||
out = out .. indent(char, indent_level, indent_width)
|
||||
last_was_bracket = false
|
||||
else
|
||||
out = out .. char
|
||||
end
|
||||
elseif not char:match("%s") then
|
||||
out = out .. char
|
||||
end
|
||||
|
||||
if not char:match("%s") then
|
||||
last_char = char
|
||||
end
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
580
.config/lite-xl/plugins/lsp/listbox.lua
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
-- A configurable listbox that can be used as tooltip, selection box and
|
||||
-- selection box with fuzzy search, this may change in the future.
|
||||
--
|
||||
-- @note This code is a readaptation of autocomplete plugin from rxi :)
|
||||
--
|
||||
-- TODO implement select box with fuzzy search
|
||||
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local util = require "plugins.lsp.util"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
---@class lsp.listbox.item
|
||||
---@field text string
|
||||
---@field info string
|
||||
---@field on_draw fun(item:lsp.listbox.item, x:number, y:number, calc_only?:boolean):number
|
||||
|
||||
---@alias lsp.listbox.callback fun(doc: core.doc, item: lsp.listbox.item)
|
||||
|
||||
---@class lsp.listbox.signature_param
|
||||
---@field label string
|
||||
|
||||
---@class lsp.listbox.signature
|
||||
---@field label string
|
||||
---@field activeParameter? integer
|
||||
---@field activeSignature? integer
|
||||
---@field parameters lsp.listbox.signature_param[]
|
||||
|
||||
---@class lsp.listbox.signature_list
|
||||
---@field activeParameter? integer
|
||||
---@field activeSignature? integer
|
||||
---@field signatures lsp.listbox.signature[]
|
||||
|
||||
---@class lsp.listbox.position
|
||||
---@field line integer
|
||||
---@field col integer
|
||||
|
||||
---@class lsp.listbox
|
||||
local listbox = {}
|
||||
|
||||
---@class lsp.listbox.settings
|
||||
---@field items lsp.listbox.item[]
|
||||
---@field shown_items lsp.listbox.item[]
|
||||
---@field selected_item_idx integer
|
||||
---@field show_items_count boolean
|
||||
---@field max_height integer
|
||||
---@field active_view core.docview | nil
|
||||
---@field line integer | nil
|
||||
---@field col integer | nil
|
||||
---@field last_line integer | nil
|
||||
---@field last_col integer | nil
|
||||
---@field callback lsp.listbox.callback | nil
|
||||
---@field is_list boolean
|
||||
---@field has_fuzzy_search boolean
|
||||
---@field above_text boolean
|
||||
local settings = {
|
||||
items = {},
|
||||
shown_items = {},
|
||||
selected_item_idx = 1,
|
||||
show_items_count = false,
|
||||
max_height = 6,
|
||||
active_view = nil,
|
||||
line = nil,
|
||||
col = nil,
|
||||
last_line = nil,
|
||||
last_col = nil,
|
||||
callback = nil,
|
||||
is_list = false,
|
||||
has_fuzzy_search = false,
|
||||
above_text = false,
|
||||
}
|
||||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@return core.docview | nil
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
---@param active_view core.docview
|
||||
---@return number x
|
||||
---@return number y
|
||||
---@return number width
|
||||
---@return number height
|
||||
local function get_suggestions_rect(active_view)
|
||||
if #settings.shown_items == 0 then
|
||||
listbox.hide()
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local line, col
|
||||
if settings.line then
|
||||
line, col = settings.line, settings.col
|
||||
else
|
||||
line, col = active_view.doc:get_selection()
|
||||
end
|
||||
|
||||
-- Validate line against current view because there can be cases
|
||||
-- when user rapidly switches between tabs causing the deferred draw
|
||||
-- to be called late and the current document view already changed.
|
||||
if line > #active_view.doc.lines then
|
||||
listbox.hide()
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local x, y = active_view:get_line_screen_position(line)
|
||||
|
||||
-- This function causes tokenizer to fail if given line is greater than
|
||||
-- the amount of lines the document holds, so validation above is needed.
|
||||
x = x + active_view:get_col_x_offset(line, col)
|
||||
|
||||
local padding_x = style.padding.x
|
||||
local padding_y = style.padding.y
|
||||
|
||||
if settings.above_text and line > 1 then
|
||||
y = y - active_view:get_line_height() - style.padding.y
|
||||
else
|
||||
y = y + active_view:get_line_height() + style.padding.y
|
||||
end
|
||||
|
||||
local font = settings.is_list and active_view:get_font() or style.font
|
||||
local text_height = font:get_height()
|
||||
|
||||
local max_width = 0
|
||||
for _, item in ipairs(settings.shown_items) do
|
||||
local w = 0
|
||||
if item.on_draw then
|
||||
w = item.on_draw(item, 0, 0, true)
|
||||
else
|
||||
w = font:get_width(item.text)
|
||||
if item.info then
|
||||
w = w + style.font:get_width(item.info) + style.padding.x
|
||||
end
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
local max_items = #settings.shown_items
|
||||
if settings.is_list and max_items > settings.max_height then
|
||||
max_items = settings.max_height
|
||||
end
|
||||
|
||||
-- additional line to display total items
|
||||
if settings.show_items_count then
|
||||
max_items = max_items + 1
|
||||
end
|
||||
|
||||
if max_width < 150 then
|
||||
max_width = 150
|
||||
end
|
||||
|
||||
local height = max_items * (text_height + (padding_y/4)) + (padding_y*2)
|
||||
local width = max_width + padding_x * 2
|
||||
|
||||
x = x - padding_x
|
||||
y = y - padding_y
|
||||
|
||||
local win_w = system.get_window_size()
|
||||
if (width/win_w*100) >= 85 and (width+style.padding.x*4) < win_w then
|
||||
x = win_w - width - style.padding.x*2
|
||||
elseif width > (win_w - x) then
|
||||
x = x - (width - (win_w - x))
|
||||
if x < 0 then
|
||||
x = 0
|
||||
end
|
||||
end
|
||||
|
||||
return x, y, width, height
|
||||
end
|
||||
|
||||
---@param av core.docview
|
||||
local function draw_listbox(av)
|
||||
if #settings.shown_items <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
|
||||
-- draw border
|
||||
if not settings.is_list then
|
||||
local border_width = 1
|
||||
renderer.draw_rect(
|
||||
rx - border_width,
|
||||
ry - border_width,
|
||||
rw + (border_width * 2),
|
||||
rh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
end
|
||||
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
||||
local padding_x = style.padding.x
|
||||
local padding_y = style.padding.y
|
||||
|
||||
-- draw text
|
||||
local font = settings.is_list and av:get_font() or style.font
|
||||
local line_height = font:get_height() + (padding_y / 4)
|
||||
local y = ry + padding_y
|
||||
|
||||
local max_height = settings.max_height
|
||||
|
||||
local show_count = (
|
||||
#settings.shown_items <= max_height or not settings.is_list
|
||||
) and
|
||||
#settings.shown_items or max_height
|
||||
|
||||
local start_index = settings.selected_item_idx > max_height and
|
||||
(settings.selected_item_idx-(max_height-1)) or 1
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not settings.shown_items[i] then
|
||||
break
|
||||
end
|
||||
|
||||
local item = settings.shown_items[i]
|
||||
|
||||
if item.on_draw then
|
||||
item.on_draw(item, rx + padding_x, y)
|
||||
else
|
||||
local color = (i == settings.selected_item_idx and settings.is_list) and
|
||||
style.accent or style.text
|
||||
|
||||
common.draw_text(
|
||||
font, color, item.text, "left",
|
||||
rx + padding_x, y, rw, line_height
|
||||
)
|
||||
|
||||
if item.info then
|
||||
color = (i == settings.selected_item_idx and settings.is_list) and
|
||||
style.text or style.dim
|
||||
|
||||
common.draw_text(
|
||||
style.font, color, item.info, "right",
|
||||
rx, y, rw - padding_x, line_height
|
||||
)
|
||||
end
|
||||
end
|
||||
y = y + line_height
|
||||
end
|
||||
|
||||
if settings.show_items_count then
|
||||
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
||||
renderer.draw_rect(rx, y+2, rw, line_height, style.background)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
"Items",
|
||||
"left",
|
||||
rx + padding_x, y, rw, line_height
|
||||
)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
tostring(settings.selected_item_idx) .. "/" .. tostring(#settings.shown_items),
|
||||
"right",
|
||||
rx, y, rw - padding_x, line_height
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---Set the document position where the listbox will be draw.
|
||||
---@param position? lsp.listbox.position
|
||||
local function set_position(position)
|
||||
if type(position) == "table" then
|
||||
settings.line = position.line
|
||||
settings.col = position.col
|
||||
else
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@param elements lsp.listbox.item[]
|
||||
function listbox.add(elements)
|
||||
if type(elements) == "table" and #elements > 0 then
|
||||
local items = {}
|
||||
for _, element in pairs(elements) do
|
||||
table.insert(items, setmetatable(element, mt))
|
||||
end
|
||||
settings.items = items
|
||||
end
|
||||
end
|
||||
|
||||
function listbox.clear()
|
||||
settings.items = {}
|
||||
settings.selected_item_idx = 1
|
||||
settings.shown_items = {}
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
end
|
||||
|
||||
---@param element lsp.listbox.item
|
||||
function listbox.append(element)
|
||||
table.insert(settings.items, setmetatable(element, mt))
|
||||
end
|
||||
|
||||
function listbox.hide()
|
||||
settings.active_view = nil
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
settings.selected_item_idx = 1
|
||||
settings.shown_items = {}
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
---@param is_list? boolean
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show(is_list, position)
|
||||
set_position(position)
|
||||
|
||||
local active_view = get_active_view()
|
||||
if active_view then
|
||||
settings.active_view = active_view
|
||||
settings.last_line, settings.last_col = active_view.doc:get_selection()
|
||||
if settings.items and #settings.items > 0 then
|
||||
settings.is_list = is_list or false
|
||||
settings.shown_items = settings.items
|
||||
end
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_text(text, position)
|
||||
if text and type("text") == "string" then
|
||||
local win_w = system.get_window_size() - style.padding.x * 6
|
||||
text = util.wrap_text(text, style.font, win_w)
|
||||
|
||||
local items = {}
|
||||
for result in string.gmatch(text.."\n", "(.-)\n") do
|
||||
table.insert(items, {text = result})
|
||||
end
|
||||
listbox.add(items)
|
||||
end
|
||||
|
||||
listbox.show(false, position)
|
||||
end
|
||||
|
||||
---@param items lsp.listbox.item[]
|
||||
---@param callback lsp.listbox.callback
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_list(items, callback, position)
|
||||
listbox.add(items)
|
||||
|
||||
if callback then
|
||||
settings.callback = callback
|
||||
end
|
||||
|
||||
listbox.show(true, position)
|
||||
end
|
||||
|
||||
---@param signatures lsp.listbox.signature_list
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_signatures(signatures, position)
|
||||
local active_parameter = nil
|
||||
local active_signature = nil
|
||||
|
||||
if signatures.activeParameter then
|
||||
active_parameter = signatures.activeParameter + 1
|
||||
end
|
||||
|
||||
if signatures.activeSignature then
|
||||
active_signature = signatures.activeSignature + 1
|
||||
end
|
||||
|
||||
local signatures_count = #signatures.signatures
|
||||
|
||||
local items = {}
|
||||
for index, signature in ipairs(signatures.signatures) do
|
||||
table.insert(items, {
|
||||
text = signature.label,
|
||||
signature = signature,
|
||||
on_draw = function(item, x, y, calc_only)
|
||||
local width = 0
|
||||
local height = style.font:get_height()
|
||||
|
||||
if item.signature.parameters then
|
||||
if signatures_count > 1 then
|
||||
if index == active_signature then
|
||||
width = style.font:get_width("> ")
|
||||
else
|
||||
width = style.font:get_width("> ")
|
||||
x = x + style.font:get_width("> ")
|
||||
end
|
||||
end
|
||||
|
||||
width = width
|
||||
+ style.font:get_width("(")
|
||||
+ style.font:get_width(")")
|
||||
|
||||
if not calc_only then
|
||||
if signatures_count > 1 and index == active_signature then
|
||||
x = renderer.draw_text(style.font, "> ", x, y, style.caret)
|
||||
end
|
||||
x = renderer.draw_text(style.font, "(", x, y, style.text)
|
||||
end
|
||||
|
||||
local params_count = #item.signature.parameters
|
||||
for pindex, param in ipairs(item.signature.parameters) do
|
||||
local label = ""
|
||||
if type(param.label) == "table" then
|
||||
label = signature.label:sub(param.label[1]+1, param.label[2])
|
||||
else
|
||||
label = param.label
|
||||
end
|
||||
if label and pindex ~= params_count then
|
||||
label = label .. ", "
|
||||
end
|
||||
width = width + style.font:get_width(label)
|
||||
if not calc_only then
|
||||
local color = style.text
|
||||
if
|
||||
(
|
||||
signature.activeParameter
|
||||
and
|
||||
(signature.activeParameter + 1) == pindex
|
||||
)
|
||||
or
|
||||
(index == active_signature and active_parameter == pindex)
|
||||
then
|
||||
color = style.accent
|
||||
end
|
||||
x = renderer.draw_text(
|
||||
style.font,
|
||||
label,
|
||||
x, y,
|
||||
color
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if not calc_only then
|
||||
renderer.draw_text(style.font, ")", x, y, style.text)
|
||||
end
|
||||
else
|
||||
width = style.font:get_width(item.signature.label)
|
||||
if not calc_only then
|
||||
renderer.draw_text(
|
||||
style.font,
|
||||
item.signature.label,
|
||||
x, y,
|
||||
style.text
|
||||
)
|
||||
end
|
||||
end
|
||||
return width, width > 0 and height or 0
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
listbox.add(items)
|
||||
|
||||
listbox.show(false, position)
|
||||
end
|
||||
|
||||
function listbox.toggle_above(enable)
|
||||
if enable then
|
||||
settings.above_text = true
|
||||
else
|
||||
settings.above_text = false
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Patch event logic into RootView
|
||||
--------------------------------------------------------------------------------
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
RootView.update = function(...)
|
||||
root_view_update(...)
|
||||
|
||||
if not settings.active_view then return end
|
||||
|
||||
local active_view = get_active_view()
|
||||
if active_view then
|
||||
-- reset suggestions if caret was moved or not same active view
|
||||
local line, col = active_view.doc:get_selection()
|
||||
if
|
||||
settings.active_view ~= active_view
|
||||
or
|
||||
line ~= settings.last_line or col ~= settings.last_col
|
||||
then
|
||||
listbox.hide()
|
||||
end
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end
|
||||
|
||||
RootView.draw = function(...)
|
||||
if settings.active_view then
|
||||
local active_view = get_active_view()
|
||||
if
|
||||
active_view and settings.active_view == active_view
|
||||
and
|
||||
#settings.shown_items > 0
|
||||
then
|
||||
-- draw suggestions box after everything else
|
||||
core.root_view:defer_draw(draw_listbox, active_view)
|
||||
end
|
||||
end
|
||||
root_view_draw(...)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Commands
|
||||
--------------------------------------------------------------------------------
|
||||
local function predicate()
|
||||
local av = get_active_view()
|
||||
return av and settings.active_view and #settings.shown_items > 0, av
|
||||
end
|
||||
|
||||
command.add(predicate, {
|
||||
["listbox:select"] = function(av)
|
||||
---@cast av core.docview
|
||||
if settings.is_list then
|
||||
local doc = av.doc
|
||||
local item = settings.shown_items[settings.selected_item_idx]
|
||||
|
||||
if settings.callback then
|
||||
settings.callback(doc, item)
|
||||
end
|
||||
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:previous"] = function()
|
||||
if settings.is_list then
|
||||
settings.selected_item_idx = math.max(settings.selected_item_idx - 1, 1)
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:next"] = function()
|
||||
if settings.is_list then
|
||||
settings.selected_item_idx = math.min(
|
||||
settings.selected_item_idx + 1, #settings.shown_items
|
||||
)
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:cancel"] = function()
|
||||
listbox.hide()
|
||||
end,
|
||||
})
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Keymaps
|
||||
--------------------------------------------------------------------------------
|
||||
keymap.add {
|
||||
["tab"] = "listbox:select",
|
||||
["up"] = "listbox:previous",
|
||||
["down"] = "listbox:next",
|
||||
["escape"] = "listbox:cancel",
|
||||
}
|
||||
|
||||
|
||||
return listbox
|
||||
19
.config/lite-xl/plugins/lsp/manifest.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"addons": [
|
||||
{
|
||||
"id": "lsp",
|
||||
"name": "Language Server Protocol",
|
||||
"description": "Provides intellisense by leveraging the LSP protocol.",
|
||||
"version": "0.8",
|
||||
"mod_version": "3",
|
||||
"dependencies": {
|
||||
"widget": { "version": ">=0.1" },
|
||||
"lintplus": { "version": ">=0.2", "optional": true },
|
||||
"lsp_snippets": { "optional": true }
|
||||
},
|
||||
"tags": [
|
||||
"language server protocol"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
.config/lite-xl/plugins/lsp/screenshots/completion01.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/completion02.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/completion03.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/completion04.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/diagnostics01.png
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/docsym01.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/docsym02.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/gotodef01.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/hover01.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/hover02.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/signatures01.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
1662
.config/lite-xl/plugins/lsp/server.lua
Normal file
121
.config/lite-xl/plugins/lsp/symbolresults.lua
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
--
|
||||
-- SymbolResults Widget/View.
|
||||
-- @copyright Jefferson Gonzalez
|
||||
-- @license MIT
|
||||
--
|
||||
|
||||
local style = require "core.style"
|
||||
local Widget = require "libraries.widget"
|
||||
local Label = require "libraries.widget.label"
|
||||
local Line = require "libraries.widget.line"
|
||||
local ListBox = require "libraries.widget.listbox"
|
||||
local Server = require "plugins.lsp.server"
|
||||
|
||||
local Lsp = {}
|
||||
|
||||
---@class lsp.symbolresults : widget
|
||||
---@field public searching boolean
|
||||
---@field public symbol string
|
||||
---@field private title widget.label
|
||||
---@field private line widget.line
|
||||
---@field private list_container widget
|
||||
---@field private list widget.listbox
|
||||
local SymbolResults = Widget:extend()
|
||||
|
||||
function SymbolResults:new(symbol)
|
||||
SymbolResults.super.new(self)
|
||||
|
||||
Lsp = require "plugins.lsp"
|
||||
|
||||
self.name = "Symbols Search"
|
||||
self.defer_draw = false
|
||||
|
||||
self.searching = true
|
||||
self.symbol = symbol or ""
|
||||
self.title = Label(self, "Searching symbols for: " .. symbol)
|
||||
self.line = Line(self, 2, style.padding.x)
|
||||
|
||||
self.list_container = Widget(self)
|
||||
self.list_container.border.width = 0
|
||||
self.list_container:set_size(200, 200)
|
||||
|
||||
self.list = ListBox(self.list_container)
|
||||
self.list.border.width = 0
|
||||
|
||||
self.list:enable_expand(true)
|
||||
self.list:add_column("Num.")
|
||||
self.list:add_column("Symbol")
|
||||
self.list:add_column("Kind")
|
||||
self.list:add_column("Location")
|
||||
|
||||
local list_on_row_click = self.list.on_row_click
|
||||
self.list.on_row_click = function(this, idx, data)
|
||||
list_on_row_click(this, idx, data)
|
||||
self:on_selected(idx, data)
|
||||
end
|
||||
|
||||
self.num = 1
|
||||
|
||||
self.border.width = 0
|
||||
self:set_size(200, 200)
|
||||
self:show()
|
||||
end
|
||||
|
||||
function SymbolResults:add_result(result)
|
||||
local preview, position = Lsp.get_location_preview(result.location)
|
||||
local container_name = result.containerName and
|
||||
result.containerName .. "\n" or ""
|
||||
|
||||
local row = {
|
||||
tostring(self.num),
|
||||
ListBox.COLEND,
|
||||
style.syntax.keyword, container_name .. result.name,
|
||||
ListBox.COLEND,
|
||||
style.syntax.literal, Server.get_symbol_kind(result.kind),
|
||||
ListBox.COLEND,
|
||||
style.text, position, ListBox.NEWLINE, style.accent, preview
|
||||
}
|
||||
|
||||
self.num = self.num + 1
|
||||
|
||||
self.list:add_row(row, result)
|
||||
end
|
||||
|
||||
function SymbolResults:stop_searching()
|
||||
self.searching = false
|
||||
end
|
||||
|
||||
function SymbolResults:on_selected(idx, data)
|
||||
Lsp.goto_location(data.location)
|
||||
end
|
||||
|
||||
function SymbolResults:update()
|
||||
if not SymbolResults.super.update(self) then return end
|
||||
-- update the positions and sizes
|
||||
self.background_color = style.background
|
||||
self.title:set_position(style.padding.x, style.padding.y)
|
||||
if not self.searching or #self.list.rows > 0 then
|
||||
local label = "Finished: "
|
||||
if self.searching then
|
||||
label = "Searching: "
|
||||
end
|
||||
self.title:set_label(
|
||||
label
|
||||
.. #self.list.rows
|
||||
.. " results found for "
|
||||
.. '"'
|
||||
.. self.symbol
|
||||
.. '"'
|
||||
)
|
||||
end
|
||||
self.line:set_position(0, self.title:get_bottom() + 10)
|
||||
self.list_container:set_position(style.padding.x, self.line:get_bottom() + 10)
|
||||
self.list_container:set_size(
|
||||
self.size.x - (style.padding.x * 2),
|
||||
self.size.y - self.line:get_bottom()
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
return SymbolResults
|
||||
|
||||
101
.config/lite-xl/plugins/lsp/timer.lua
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
local core = require "core"
|
||||
local Object = require "core.object"
|
||||
|
||||
---Timer class
|
||||
---@class lsp.timer : core.object
|
||||
---@field public interval integer
|
||||
---@field public single_shot boolean
|
||||
---@field private started boolean
|
||||
---@field private last_run integer
|
||||
local Timer = Object:extend()
|
||||
|
||||
---Constructor
|
||||
---@param interval integer The interval in milliseconds
|
||||
---@param single_shot boolean Indicates if timer should only run once
|
||||
function Timer:new(interval, single_shot)
|
||||
Timer.super.new(self)
|
||||
|
||||
self.single_shot = single_shot or false
|
||||
self.started = false
|
||||
self.cancel_thread_marker = { cancel = true }
|
||||
self.last_run = 0
|
||||
|
||||
self:set_interval(interval or 1000)
|
||||
end
|
||||
|
||||
---Starts a non running timer.
|
||||
function Timer:start()
|
||||
if self.started then return end
|
||||
|
||||
self.started = true
|
||||
self.cancel_thread_marker = { cancel = false }
|
||||
local this = self
|
||||
|
||||
-- Save marker so that we keep this one and not an "updated" one
|
||||
local marker = self.cancel_thread_marker
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
if marker.cancel == true then return end
|
||||
this:reset()
|
||||
local now = system.get_time()
|
||||
local remaining = (this.last_run + this.interval) - now
|
||||
if remaining > 0 then
|
||||
repeat
|
||||
if not this.started or marker.cancel then return end
|
||||
coroutine.yield(remaining)
|
||||
now = system.get_time()
|
||||
remaining = (this.last_run + this.interval) - now
|
||||
until remaining <= 0
|
||||
end
|
||||
if not this.started or marker.cancel then return end
|
||||
this:on_timer()
|
||||
if this.single_shot then break end
|
||||
end
|
||||
this.started = false
|
||||
end)
|
||||
end
|
||||
|
||||
---Stops a running timer.
|
||||
function Timer:stop()
|
||||
self.started = false
|
||||
end
|
||||
|
||||
---Resets the timer countdown for execution.
|
||||
function Timer:reset()
|
||||
self.last_run = system.get_time()
|
||||
end
|
||||
|
||||
---Restarts the timer countdown for execution.
|
||||
function Timer:restart()
|
||||
self:reset()
|
||||
if not self.started then
|
||||
self:start()
|
||||
end
|
||||
end
|
||||
|
||||
---Check if the timer is running.
|
||||
---@return boolean
|
||||
function Timer:running()
|
||||
return self.started
|
||||
end
|
||||
|
||||
---Appropriately set the timer interval by converting milliseconds to seconds.
|
||||
---@param interval integer The interval in milliseconds
|
||||
function Timer:set_interval(interval)
|
||||
local new_interval = interval / 1000
|
||||
-- As this new interval might be shorter than the currently running one, and
|
||||
-- because we might already be sleeping waiting for the old interval, we
|
||||
-- mark the already running coroutine as cancelled, and create a new one.
|
||||
if self.started and self.interval > new_interval then
|
||||
self.cancel_thread_marker.cancel = true
|
||||
self:stop()
|
||||
self:start()
|
||||
end
|
||||
self.interval = new_interval
|
||||
end
|
||||
|
||||
---To be overwritten by the instantiated timer objects
|
||||
function Timer:on_timer() end
|
||||
|
||||
|
||||
return Timer
|
||||
534
.config/lite-xl/plugins/lsp/util.lua
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
-- Some functions adapted from: https://github.com/orbitalquark/textadept-lsp
|
||||
-- and others added as needed.
|
||||
--
|
||||
-- @copyright Jefferson Gonzalez
|
||||
-- @license MIT
|
||||
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local json = require "plugins.lsp.json"
|
||||
|
||||
local util = {}
|
||||
|
||||
---Check if the given file is currently opened on the editor.
|
||||
---@param abs_filename string
|
||||
function util.doc_is_open(abs_filename)
|
||||
-- make path separator consistent
|
||||
abs_filename = abs_filename:gsub("\\", "/")
|
||||
for _, doc in ipairs(core.docs) do
|
||||
---@cast doc core.doc
|
||||
if doc.abs_filename then
|
||||
local doc_path = doc.abs_filename:gsub("\\", "/")
|
||||
if doc_path == abs_filename then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Converts a utf-8 column position into the equivalent utf-16 position.
|
||||
---@param doc core.doc
|
||||
---@param line integer
|
||||
---@param column integer
|
||||
---@return integer col_position
|
||||
function util.doc_utf8_to_utf16(doc, line, column)
|
||||
local ltext = doc.lines[line]
|
||||
local ltext_len = ltext and #ltext or 0
|
||||
local ltext_ulen = ltext and utf8extra.len(ltext) or 0
|
||||
column = common.clamp(column, 1, ltext_len > 0 and ltext_len or 1)
|
||||
-- no need for conversion so return column as is
|
||||
if ltext_len == ltext_ulen then return column end
|
||||
if column > 1 then
|
||||
local col = 1
|
||||
for pos, code in utf8extra.next, ltext do
|
||||
if pos >= column then
|
||||
return col
|
||||
end
|
||||
-- Codepoints that high are encoded using surrogate pairs
|
||||
if code < 0x010000 then
|
||||
col = col + 1
|
||||
else
|
||||
col = col + 2
|
||||
end
|
||||
end
|
||||
return col
|
||||
end
|
||||
return column
|
||||
end
|
||||
|
||||
---Converts a utf-16 column position into the equivalent utf-8 position.
|
||||
---@param doc core.doc
|
||||
---@param line integer
|
||||
---@param column integer
|
||||
---@return integer col_position
|
||||
function util.doc_utf16_to_utf8(doc, line, column)
|
||||
local ltext = doc.lines[line]
|
||||
local ltext_len = ltext and #ltext or 0
|
||||
local ltext_ulen = ltext and utf8extra.len(ltext) or 0
|
||||
column = common.clamp(column, 1, ltext_len > 0 and ltext_len or 1)
|
||||
-- no need for conversion so return column as is
|
||||
if ltext_len == ltext_ulen then return column end
|
||||
if column > 1 then
|
||||
local col = 1
|
||||
local utf8_pos = 1
|
||||
for pos, code in utf8extra.next, ltext do
|
||||
if col >= column then
|
||||
return pos
|
||||
end
|
||||
utf8_pos = pos
|
||||
-- Codepoints that high are encoded using surrogate pairs
|
||||
if code < 0x010000 then
|
||||
col = col + 1
|
||||
else
|
||||
col = col + 2
|
||||
end
|
||||
end
|
||||
return utf8_pos
|
||||
end
|
||||
return column
|
||||
end
|
||||
|
||||
---Split a string by the given delimeter
|
||||
---@param s string The string to split
|
||||
---@param delimeter string Delimeter without lua patterns
|
||||
---@param delimeter_pattern? string Optional delimeter with lua patterns
|
||||
---@return table
|
||||
---@return boolean ends_with_delimiter
|
||||
function util.split(s, delimeter, delimeter_pattern)
|
||||
if not delimeter_pattern then
|
||||
delimeter_pattern = delimeter
|
||||
end
|
||||
|
||||
local last_idx = 1
|
||||
local result = {}
|
||||
for match_idx, afer_match_idx in s:gmatch("()"..delimeter_pattern.."()") do
|
||||
table.insert(result, string.sub(s, last_idx, match_idx - 1))
|
||||
last_idx = afer_match_idx
|
||||
end
|
||||
if last_idx > #s then
|
||||
return result, true
|
||||
else
|
||||
table.insert(result, string.sub(s, last_idx))
|
||||
return result, false
|
||||
end
|
||||
end
|
||||
|
||||
---Get the extension component of a filename.
|
||||
---@param filename string
|
||||
---@return string
|
||||
function util.file_extension(filename)
|
||||
local parts = util.split(filename, "%.")
|
||||
if #parts > 1 then
|
||||
return parts[#parts]:gsub("%%", "")
|
||||
end
|
||||
|
||||
return filename
|
||||
end
|
||||
|
||||
---Check if a file exists.
|
||||
---@param file_path string
|
||||
---@return boolean
|
||||
function util.file_exists(file_path)
|
||||
local file = io.open(file_path, "r")
|
||||
if file ~= nil then
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Converts the given LSP DocumentUri into a valid filename path.
|
||||
---@param uri string LSP DocumentUri to convert into a filename.
|
||||
---@return string
|
||||
function util.tofilename(uri)
|
||||
local filename = ""
|
||||
if PLATFORM == "Windows" then
|
||||
filename = uri:gsub('^file:///', '')
|
||||
else
|
||||
filename = uri:gsub('^file://', '')
|
||||
end
|
||||
|
||||
filename = filename:gsub(
|
||||
'%%(%x%x)',
|
||||
function(hex) return string.char(tonumber(hex, 16)) end
|
||||
)
|
||||
|
||||
if PLATFORM == "Windows" then filename = filename:gsub('/', '\\') end
|
||||
|
||||
return filename
|
||||
end
|
||||
|
||||
---Convert a file path to a LSP valid uri.
|
||||
---@param file_path string
|
||||
---@return string
|
||||
function util.touri(file_path)
|
||||
if PLATFORM ~= "Windows" then
|
||||
file_path = 'file://' .. file_path
|
||||
else
|
||||
file_path = 'file:///' .. file_path:gsub('\\', '/')
|
||||
end
|
||||
|
||||
return file_path
|
||||
end
|
||||
|
||||
---Converts a document range returned by lsp to a valid document selection.
|
||||
---@param range table LSP Range.
|
||||
---@param doc? core.doc
|
||||
---@return integer line1
|
||||
---@return integer col1
|
||||
---@return integer line2
|
||||
---@return integer col2
|
||||
function util.toselection(range, doc)
|
||||
local line1 = range.start.line + 1
|
||||
local col1 = range.start.character + 1
|
||||
local line2 = range['end'].line + 1
|
||||
local col2 = range['end'].character + 1
|
||||
|
||||
if doc then
|
||||
col1 = util.doc_utf16_to_utf8(doc, line1, col1)
|
||||
col2 = util.doc_utf16_to_utf8(doc, line2, col2)
|
||||
end
|
||||
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
---Opens the given location on a external application.
|
||||
---@param location string
|
||||
function util.open_external(location)
|
||||
local filelauncher = ""
|
||||
if PLATFORM == "Windows" then
|
||||
filelauncher = "start"
|
||||
elseif PLATFORM == "Mac OS X" then
|
||||
filelauncher = "open"
|
||||
else
|
||||
filelauncher = "xdg-open"
|
||||
end
|
||||
|
||||
-- non-Windows platforms need the text quoted (%q)
|
||||
if PLATFORM ~= "Windows" then
|
||||
location = string.format("%q", location)
|
||||
end
|
||||
|
||||
system.exec(filelauncher .. " " .. location)
|
||||
end
|
||||
|
||||
---Prettify json output and logs it if config.lsp.log_file is set.
|
||||
---@param code string
|
||||
---@return string
|
||||
function util.jsonprettify(code)
|
||||
if config.plugins.lsp.prettify_json then
|
||||
code = json.prettify(code)
|
||||
end
|
||||
|
||||
if config.plugins.lsp.log_file and #config.plugins.lsp.log_file > 0 then
|
||||
local log = io.open(config.plugins.lsp.log_file, "a+")
|
||||
log:write("Output: \n" .. tostring(code) .. "\n\n")
|
||||
log:close()
|
||||
end
|
||||
|
||||
return code
|
||||
end
|
||||
|
||||
---Gets the last component of a path. For example:
|
||||
---/my/path/to/somwhere would return somewhere.
|
||||
---@param path string
|
||||
---@return string
|
||||
function util.getpathname(path)
|
||||
local components = {}
|
||||
if PLATFORM == "Windows" then
|
||||
components = util.split(path, "\\")
|
||||
else
|
||||
components = util.split(path, "/")
|
||||
end
|
||||
|
||||
if #components > 0 then
|
||||
return components[#components]
|
||||
end
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
---Check if a value is on a table.
|
||||
---@param value any
|
||||
---@param table_array table
|
||||
---@return boolean
|
||||
function util.intable(value, table_array)
|
||||
for _, element in pairs(table_array) do
|
||||
if element == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---Check if a command exists on the system by inspecting the PATH envar.
|
||||
---@param command string
|
||||
---@return boolean
|
||||
function util.command_exists(command)
|
||||
local command_win = nil
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
if not command:find("%.exe$") then
|
||||
command_win = command .. ".exe"
|
||||
end
|
||||
end
|
||||
|
||||
if
|
||||
util.file_exists(command)
|
||||
or
|
||||
(command_win and util.file_exists(command_win))
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
local env_path = os.getenv("PATH")
|
||||
local path_list = {}
|
||||
|
||||
if PLATFORM ~= "Windows" then
|
||||
path_list = util.split(env_path, ":")
|
||||
else
|
||||
path_list = util.split(env_path, ";")
|
||||
end
|
||||
|
||||
-- Automatic support for brew, macports, etc...
|
||||
if PLATFORM == "Mac OS X" then
|
||||
if
|
||||
system.get_file_info("/usr/local/bin")
|
||||
and
|
||||
not string.find(env_path, "/usr/local/bin", 1, true)
|
||||
then
|
||||
table.insert(path_list, 1, "/usr/local/bin")
|
||||
end
|
||||
end
|
||||
|
||||
for _, path in pairs(path_list) do
|
||||
local path_fix = path:gsub("[/\\]$", "") .. PATHSEP
|
||||
if util.file_exists(path_fix .. command) then
|
||||
return true
|
||||
elseif command_win and util.file_exists(path_fix .. command_win) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---From a list of executable names get the one that is installed
|
||||
---on the system or first one if none of them exists.
|
||||
---@param executables table<integer,string>
|
||||
---@return string executable_name
|
||||
function util.get_best_executable(executables)
|
||||
for _, executable in ipairs(executables) do
|
||||
if util.command_exists(executable) then
|
||||
return executable
|
||||
end
|
||||
end
|
||||
return executables[1]
|
||||
end
|
||||
|
||||
---Remove by key from a table and returns a new
|
||||
---table with element removed.
|
||||
---@param table_object table
|
||||
---@param key_name string|integer
|
||||
---@return table
|
||||
function util.table_remove_key(table_object, key_name)
|
||||
local new_table = {}
|
||||
for key, data in pairs(table_object) do
|
||||
if key ~= key_name then
|
||||
new_table[key] = data
|
||||
end
|
||||
end
|
||||
|
||||
return new_table
|
||||
end
|
||||
|
||||
---Get a table specific field or nil if not found.
|
||||
---@param t table The table we are going to search for the field.
|
||||
---@param fieldset string A field spec in the format
|
||||
---"parent[.child][.subchild]" eg: "myProp.subProp.subSubProp"
|
||||
---@return any|nil The value of the given field or nil if not found.
|
||||
function util.table_get_field(t, fieldset)
|
||||
local fields = util.split(fieldset, ".", "%.")
|
||||
local field = fields[1]
|
||||
local value = nil
|
||||
|
||||
if field and #fields > 1 and t[field] then
|
||||
local sub_fields = table.concat(fields, ".", 2)
|
||||
value = util.table_get_field(t[field], sub_fields)
|
||||
elseif field and #fields > 0 and t[field] then
|
||||
value = t[field]
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
---Merge the content of the tables into a new one.
|
||||
---Arguments from the later tables take precedence.
|
||||
---Doesn't touch the original tables.
|
||||
---`nil` arguments are ignored.
|
||||
---@param ... table?
|
||||
---@return table
|
||||
function util.deep_merge(...)
|
||||
local t = {}
|
||||
local args = table.pack(...)
|
||||
for i=1,args.n do
|
||||
local other = args[i]
|
||||
if other then
|
||||
assert(type(other) == "table", string.format("Argument %d must be a table", i))
|
||||
for k, v in pairs(other) do
|
||||
if type(v) == "table" then
|
||||
if type(t[k]) == "table" then
|
||||
t[k] = util.deep_merge(t[k], v)
|
||||
else
|
||||
t[k] = util.deep_merge({}, v)
|
||||
end
|
||||
else
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
---Check if a table is really empty.
|
||||
---@param t table
|
||||
---@return boolean
|
||||
function util.table_empty(t)
|
||||
return next(t) == nil
|
||||
end
|
||||
|
||||
---Convert markdown to plain text.
|
||||
---@param text string
|
||||
---@return string
|
||||
function util.strip_markdown(text)
|
||||
local clean_text = ""
|
||||
local prev_line = ""
|
||||
for match in (text.."\n"):gmatch("(.-)".."\n") do
|
||||
match = match .. "\n"
|
||||
|
||||
-- strip markdown
|
||||
local new_line = match
|
||||
-- Block quotes
|
||||
:gsub("^>+(%s*)", "%1")
|
||||
-- headings
|
||||
:gsub("^(%s*)######%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)#####%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)####%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)####%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)###%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)##%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)#%s(.-)\n", "%1%2\n")
|
||||
-- heading custom id
|
||||
:gsub("{#.-}", "")
|
||||
-- emoji
|
||||
:gsub(":[%w%-_]+:", "")
|
||||
-- bold and italic
|
||||
:gsub("%*%*%*(.-)%*%*%*", "%1")
|
||||
:gsub("___(.-)___", "%1")
|
||||
:gsub("%*%*_(.-)_%*%*", "%1")
|
||||
:gsub("__%*(.-)%*__", "%1")
|
||||
:gsub("___(.-)___", "%1")
|
||||
-- bold
|
||||
:gsub("%*%*(.-)%*%*", "%1")
|
||||
:gsub("__(.-)__", "%1")
|
||||
-- strikethrough
|
||||
:gsub("%-%-(.-)%-%-", "%1")
|
||||
-- italic
|
||||
:gsub("%*(.-)%*", "%1")
|
||||
:gsub("%s_(.-)_%s", "%1")
|
||||
:gsub("\\_(.-)\\_", "_%1_")
|
||||
:gsub("^_(.-)_", "%1")
|
||||
-- code
|
||||
:gsub("^%s*```(%w+)%s*\n", "")
|
||||
:gsub("^%s*```%s*\n", "")
|
||||
:gsub("``(.-)``", "%1")
|
||||
:gsub("`(.-)`", "%1")
|
||||
-- lines
|
||||
:gsub("^%-%-%-%-*%s*\n", "")
|
||||
:gsub("^%*%*%*%**%s*\n", "")
|
||||
-- reference links
|
||||
:gsub("^%[[^%^](.-)%]:.-\n", "")
|
||||
-- footnotes
|
||||
:gsub("^%[%^(.-)%]:%s+", "[%1]: ")
|
||||
:gsub("%[%^(.-)%]", "[%1]")
|
||||
-- Images
|
||||
:gsub("!%[(.-)%]%((.-)%)", "")
|
||||
-- links
|
||||
:gsub("%s<(.-)>%s", "%1")
|
||||
:gsub("%[(.-)%]%s*%[(.-)%]", "%1")
|
||||
:gsub("%[(.-)%]%((.-)%)", "%1: %2")
|
||||
-- remove escaped punctuations
|
||||
:gsub("\\(%p)", "%1")
|
||||
|
||||
-- if paragraph put in same line
|
||||
local is_paragraph = false
|
||||
|
||||
local prev_spaces = prev_line:match("^%g+")
|
||||
local prev_endings = prev_line:match("[ \t\r\n]+$")
|
||||
local new_spaces = new_line:match("^%g+")
|
||||
|
||||
if prev_spaces and new_spaces then
|
||||
local new_lines = prev_endings ~= nil
|
||||
and prev_endings:gsub("[ \t\r]+", "") or ""
|
||||
|
||||
if #new_lines == 1 then
|
||||
is_paragraph = true
|
||||
clean_text = clean_text:gsub("[%s\n]+$", "")
|
||||
.. " " .. new_line:gsub("^%s+", "")
|
||||
end
|
||||
end
|
||||
|
||||
if not is_paragraph then
|
||||
clean_text = clean_text .. new_line
|
||||
end
|
||||
|
||||
prev_line = new_line
|
||||
end
|
||||
return clean_text
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param font renderer.font
|
||||
---@param max_width number
|
||||
function util.wrap_text(text, font, max_width)
|
||||
local lines = util.split(text, "\n")
|
||||
local wrapped_text = ""
|
||||
local longest_line = 0;
|
||||
for _, line in ipairs(lines) do
|
||||
local line_len = line:ulen() or 0
|
||||
if line_len > longest_line then
|
||||
longest_line = line_len
|
||||
local line_width = font:get_width(line)
|
||||
if line_width > max_width then
|
||||
local words = util.split(line, " ")
|
||||
local new_line = words[1] and words[1] or ""
|
||||
wrapped_text = wrapped_text .. new_line
|
||||
for w=2, #words do
|
||||
if font:get_width(new_line .. " " .. words[w]) <= max_width then
|
||||
new_line = new_line .. " " .. words[w]
|
||||
wrapped_text = wrapped_text .. " " .. words[w]
|
||||
else
|
||||
wrapped_text = wrapped_text .. "\n" .. words[w]
|
||||
new_line = words[w]
|
||||
end
|
||||
end
|
||||
wrapped_text = wrapped_text .. "\n"
|
||||
else
|
||||
wrapped_text = wrapped_text .. line .. "\n"
|
||||
end
|
||||
else
|
||||
wrapped_text = wrapped_text .. line .. "\n"
|
||||
end
|
||||
end
|
||||
|
||||
wrapped_text = wrapped_text:gsub("\n\n\n\n?", "\n\n"):gsub("%s*$", "")
|
||||
|
||||
return wrapped_text
|
||||
end
|
||||
|
||||
|
||||
return util
|
||||
12
.config/lite-xl/plugins/lsp_json/init.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- mod-version:3
|
||||
|
||||
local lspconfig = require "plugins.lsp.config"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
||||
local installed_path = USERDIR .. PATHSEP .. "plugins" .. PATHSEP .. "lsp_json" .. PATHSEP .. "vscode-json-languageserver" .. PATHSEP .. "dist" .. PATHSEP .. "index.js"
|
||||
local node = require "libraries.nodejs"
|
||||
|
||||
lspconfig.jsonls.setup(common.merge({
|
||||
command = { node.path_bin, installed_path, "--stdio" },
|
||||
}, config.plugins.lsp_json or {}))
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2015 - present Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
189
.config/lite-xl/plugins/lsp_json/vscode-json-languageserver/dist/LICENSES
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
@vscode/l10n
|
||||
MIT
|
||||
|
||||
jsonc-parser
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Microsoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
request-light
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Microsoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
vscode-json-languageservice
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Microsoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Glob matching is based on code from https://github.com/fitzgen/glob-to-regexp
|
||||
|
||||
Copyright (c) 2013, Nick Fitzgerald
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
vscode-jsonrpc
|
||||
MIT
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
vscode-languageserver
|
||||
MIT
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
vscode-languageserver-protocol
|
||||
MIT
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
vscode-languageserver-textdocument
|
||||
MIT
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
vscode-languageserver-types
|
||||
MIT
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
vscode-uri
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Microsoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
21719
.config/lite-xl/plugins/lsp_json/vscode-json-languageserver/dist/index.js
vendored
Normal file
793
.config/lite-xl/plugins/lsp_snippets.lua
Normal file
|
|
@ -0,0 +1,793 @@
|
|||
-- mod-version:3
|
||||
|
||||
-- LSP style snippet parser
|
||||
-- shamelessly 'inspired by' (stolen from) LuaSnip
|
||||
-- https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/util/parser/neovim_parser.lua
|
||||
|
||||
local core = require 'core'
|
||||
local common = require 'core.common'
|
||||
local Doc = require 'core.doc'
|
||||
local system = require 'system'
|
||||
local regex = require 'regex'
|
||||
local snippets = require 'plugins.snippets'
|
||||
|
||||
local json do
|
||||
local ok, j
|
||||
for _, p in ipairs {
|
||||
'plugins.json', 'plugins.lsp.json', 'plugins.lintplus.json',
|
||||
'libraries.json'
|
||||
} do
|
||||
ok, j = pcall(require, p)
|
||||
if ok then json = j; break end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local B = snippets.builder
|
||||
|
||||
local LAST_CONVERTED_ID = { }
|
||||
local THREAD_KEY = { }
|
||||
|
||||
|
||||
-- node factories
|
||||
|
||||
local function doc_syntax(doc, k)
|
||||
return doc.syntax and doc.syntax[k]
|
||||
end
|
||||
|
||||
local variables = {
|
||||
-- LSP
|
||||
TM_SELECTED_TEXT = function(ctx) return ctx.selection end,
|
||||
TM_CURRENT_LINE = function(ctx) return ctx.doc.lines[ctx.line] end,
|
||||
TM_CURRENT_WORD = function(ctx) return ctx.partial end,
|
||||
TM_LINE_INDEX = function(ctx) return ctx.line - 1 end,
|
||||
TM_LINE_NUMBER = function(ctx) return ctx.line end,
|
||||
TM_FILENAME = function(ctx) return ctx.doc.filename:match('[^/%\\]*$') or '' end,
|
||||
TM_FILENAME_BASE = function(ctx) return ctx.doc.filename:match('([^/%\\]*)%.%w*$') or ctx.doc.filename end,
|
||||
TM_DIRECTORY = function(ctx) return ctx.doc.filename:match('([^/%\\]*)[/%\\].*$') or '' end,
|
||||
TM_FILEPATH = function(ctx) return common.dirname(ctx.doc.abs_filename) or '' end,
|
||||
-- VSCode
|
||||
RELATIVE_FILEPATH = function(ctx) return core.normalize_to_project_dir(ctx.doc.filename) end,
|
||||
CLIPBOARD = function() return system.get_clipboard() end,
|
||||
-- https://github.com/lite-xl/lite-xl/pull/1455
|
||||
WORKSPACE_NAME = function(ctx) return end,
|
||||
WORKSPACE_FOLDER = function(ctx) return end,
|
||||
CURSOR_INDEX = function(ctx) return ctx.col - 1 end,
|
||||
CURSOR_NUMBER = function(ctx) return ctx.col end,
|
||||
CURRENT_YEAR = function() return os.date('%G') end,
|
||||
CURRENT_YEAR_SHORT = function() return os.date('%g') end,
|
||||
CURRENT_MONTH = function() return os.date('%m') end,
|
||||
CURRENT_MONTH_NAME = function() return os.date('%B') end,
|
||||
CURRENT_MONTH_NAME_SHORT = function() return os.date('%b') end,
|
||||
CURRENT_DATE = function() return os.date('%d') end,
|
||||
CURRENT_DAY_NAME = function() return os.date('%A') end,
|
||||
CURRENT_DAY_NAME_SHORT = function() return os.date('%a') end,
|
||||
CURRENT_HOUR = function() return os.date('%H') end,
|
||||
CURRENT_MINUTE = function() return os.date('%M') end,
|
||||
CURRENT_SECOND = function() return os.date('%S') end,
|
||||
CURRENT_SECONDS_UNIX = function() return os.time() end,
|
||||
RANDOM = function() return string.format('%06d', math.random(999999)) end,
|
||||
RANDOM_HEX = function() return string.format('%06x', math.random(0xFFFFFF)) end,
|
||||
BLOCK_COMMENT_START = function(ctx) return (doc_syntax(ctx.doc, 'block_comment') or { })[1] end,
|
||||
BLOCK_COMMENT_END = function(ctx) return (doc_syntax(ctx.doc, 'block_comment') or { })[2] end,
|
||||
LINE_COMMENT = function(ctx) return doc_syntax(ctx.doc, 'comment') end
|
||||
-- https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables
|
||||
-- UUID
|
||||
}
|
||||
|
||||
local formatters; formatters = {
|
||||
downcase = string.lower,
|
||||
upcase = string.upper,
|
||||
capitalize = function(str)
|
||||
return str:sub(1, 1):upper() .. str:sub(2)
|
||||
end,
|
||||
pascalcase = function(str)
|
||||
local t = { }
|
||||
for s in str:gmatch('%w+') do
|
||||
table.insert(t, formatters.capitalize(s))
|
||||
end
|
||||
return table.concat(t)
|
||||
end,
|
||||
camelcase = function(str)
|
||||
str = formatters.pascalcase(str)
|
||||
return str:sub(1, 1):lower() .. str:sub(2)
|
||||
end
|
||||
}
|
||||
|
||||
local function to_text(v, _s)
|
||||
return v.esc
|
||||
end
|
||||
|
||||
local function format_fn(v, _s)
|
||||
local id = tonumber(v[2])
|
||||
|
||||
-- $1 | ${1}
|
||||
if #v < 4 then
|
||||
return function(captures)
|
||||
return captures[id] or ''
|
||||
end
|
||||
end
|
||||
|
||||
-- ${1:...}
|
||||
local t = v[3][2][1] -- token after the ':' | (else when no token)
|
||||
local i = v[3][2][2] -- formatter | if | (else when no if)
|
||||
local e = v[3][2][4] -- (else when if)
|
||||
|
||||
if t == '/' then
|
||||
local f = formatters[i]
|
||||
return function(captures)
|
||||
local c = captures[id]
|
||||
return c and f(c) or ''
|
||||
end
|
||||
elseif t == '+' then
|
||||
return function(captures)
|
||||
return captures[id] and i or ''
|
||||
end
|
||||
elseif t == '?' then
|
||||
return function(captures)
|
||||
return captures[id] and i or e
|
||||
end
|
||||
elseif t == '-' then
|
||||
return function(captures)
|
||||
return captures[id] or i
|
||||
end
|
||||
else
|
||||
return function(captures)
|
||||
return captures[id] or t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function transform_fn(v, _s)
|
||||
local reg = regex.compile(v[2], v[#v])
|
||||
local fmt = v[4]
|
||||
|
||||
if type(fmt) ~= 'table' then
|
||||
return function(str)
|
||||
return reg:gsub(str, '')
|
||||
end
|
||||
end
|
||||
|
||||
local t = { }
|
||||
for _, f in ipairs(fmt) do
|
||||
if type(f) == 'string' then
|
||||
table.insert(t, f)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if #t == #fmt then
|
||||
t = table.concat(t)
|
||||
return function(str)
|
||||
return reg:gsub(str, t)
|
||||
end
|
||||
end
|
||||
|
||||
return function(str)
|
||||
local captures = { reg:match(str) }
|
||||
for k, v in ipairs(captures) do
|
||||
if type(v) ~= 'string' then
|
||||
captures[k] = nil
|
||||
end
|
||||
end
|
||||
local t = { }
|
||||
for _, f in ipairs(fmt) do
|
||||
if type(f) == 'string' then
|
||||
table.insert(t, f)
|
||||
else
|
||||
table.insert(t, f(captures))
|
||||
end
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
end
|
||||
|
||||
local function text_node(v, _s)
|
||||
return B.static(v.esc)
|
||||
end
|
||||
|
||||
local function variable_node(v, _s)
|
||||
local name = v[2]
|
||||
local var = variables[name]
|
||||
|
||||
local id
|
||||
if not var then
|
||||
if not _s._converted_variables then
|
||||
id = os.time()
|
||||
_s._converted_variables = { [name] = id, [LAST_CONVERTED_ID] = id }
|
||||
else
|
||||
id = _s._converted_variables[name]
|
||||
if not id then
|
||||
id = _s._converted_variables[LAST_CONVERTED_ID] + 1
|
||||
_s._converted_variables[name] = id
|
||||
_s._converted_variables[LAST_CONVERTED_ID] = id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #v ~= 4 then
|
||||
return var and B.static(var) or B.user(id, name)
|
||||
end
|
||||
|
||||
if type(v[3]) == 'table' then
|
||||
-- vscode accepts empty default -> var name
|
||||
return var and B.static(var) or B.user(id, v[3][2] or name)
|
||||
end
|
||||
|
||||
if not var then
|
||||
return B.user(id, nil, v[3])
|
||||
end
|
||||
|
||||
return type(var) ~= 'function' and B.static(var) or B.static(function(ctx)
|
||||
return v[3](var(ctx))
|
||||
end)
|
||||
end
|
||||
|
||||
local function tabstop_node(v, _s)
|
||||
local t = v[3] and v[3] ~= '}' and v[3] or nil
|
||||
return B.user(tonumber(v[2]), nil, t)
|
||||
end
|
||||
|
||||
local function choice_node(v, _s)
|
||||
local id = tonumber(v[2])
|
||||
local c = { [v[4]] = true }
|
||||
if #v == 6 then
|
||||
for _, _c in ipairs(v[5]) do
|
||||
c[_c[2]] = true
|
||||
end
|
||||
end
|
||||
_s:choice(id, c)
|
||||
return B.user(id)
|
||||
end
|
||||
|
||||
local function placeholder_node(v, _s)
|
||||
local id = tonumber(v[2])
|
||||
_s:default(id, v[4])
|
||||
return B.user(id)
|
||||
end
|
||||
|
||||
local function build_snippet(v, _s)
|
||||
for _, n in ipairs(v) do _s:add(n) end
|
||||
return _s:ok()
|
||||
end
|
||||
|
||||
|
||||
-- parser metatable
|
||||
|
||||
local P do
|
||||
local mt = {
|
||||
__call = function(mt, parser, converter)
|
||||
return setmetatable({ parser = parser, converter = converter }, mt)
|
||||
end,
|
||||
-- allows 'lazy arguments'
|
||||
-- i.e can use a yet to be defined rule in a previous rule
|
||||
__index = function(t, k)
|
||||
return function(...) return t[k](...) end
|
||||
end
|
||||
}
|
||||
|
||||
P = setmetatable({
|
||||
__call = function(t, str, at, _s)
|
||||
local r = t.parser(str, at, _s)
|
||||
if r.ok and t.converter then
|
||||
r.value = t.converter(r.value, _s)
|
||||
end
|
||||
return r
|
||||
end
|
||||
}, mt)
|
||||
end
|
||||
|
||||
|
||||
-- utils
|
||||
|
||||
local function toset(t)
|
||||
local r = { }
|
||||
for _, v in pairs(t or { }) do
|
||||
r[v] = true
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
local function fail(at)
|
||||
return { at = at }
|
||||
end
|
||||
|
||||
local function ok(at, v)
|
||||
return { ok = true, at = at, value = v }
|
||||
end
|
||||
|
||||
|
||||
-- base + combinators
|
||||
|
||||
local function token(t)
|
||||
return function(str, at)
|
||||
local to = at + #t
|
||||
return t == str:sub(at, to - 1) and ok(to, t) or fail(at)
|
||||
end
|
||||
end
|
||||
|
||||
local function consume(stops, escapes)
|
||||
stops, escapes = toset(stops), toset(escapes)
|
||||
return function(str, at)
|
||||
local to = at
|
||||
local raw, esc = { }, { }
|
||||
local c = str:sub(to, to)
|
||||
while to <= #str and not stops[c] do
|
||||
if c == '\\' then
|
||||
table.insert(raw, c)
|
||||
to = to + 1
|
||||
c = str:sub(to, to)
|
||||
if not stops[c] and not escapes[c] then
|
||||
table.insert(esc, '\\')
|
||||
end
|
||||
end
|
||||
table.insert(raw, c)
|
||||
table.insert(esc, c)
|
||||
to = to + 1
|
||||
c = str:sub(to, to)
|
||||
end
|
||||
return to ~= at
|
||||
and ok(to, { raw = table.concat(raw), esc = table.concat(esc) })
|
||||
or fail(at)
|
||||
end
|
||||
end
|
||||
|
||||
local function pattern(p)
|
||||
return function(str, at)
|
||||
local r = str:match('^' .. p, at)
|
||||
return r and ok(at + #r, r) or fail(at)
|
||||
end
|
||||
end
|
||||
|
||||
local function maybe(p)
|
||||
return function(str, at, ...)
|
||||
local r = p(str, at, ...)
|
||||
return ok(r.at, r.value)
|
||||
end
|
||||
end
|
||||
|
||||
local function rep(p)
|
||||
return function(str, at, ...)
|
||||
local v, to, r = { }, at, ok(at)
|
||||
while to <= #str and r.ok do
|
||||
table.insert(v, r.value)
|
||||
to = r.at
|
||||
r = p(str, to, ...)
|
||||
end
|
||||
return #v > 0 and ok(to, v) or fail(at)
|
||||
end
|
||||
end
|
||||
|
||||
local function any(...)
|
||||
local t = { ... }
|
||||
return function(str, at, ...)
|
||||
for _, p in ipairs(t) do
|
||||
local r = p(str, at, ...)
|
||||
if r.ok then return r end
|
||||
end
|
||||
return fail(at)
|
||||
end
|
||||
end
|
||||
|
||||
local function seq(...)
|
||||
local t = { ... }
|
||||
return function(str, at, ...)
|
||||
local v, to = { }, at
|
||||
for _, p in ipairs(t) do
|
||||
local r = p(str, to, ...)
|
||||
if r.ok then
|
||||
table.insert(v, r.value)
|
||||
to = r.at
|
||||
else
|
||||
return fail(at)
|
||||
end
|
||||
end
|
||||
return ok(to, v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- grammar rules
|
||||
|
||||
-- token cache
|
||||
local t = setmetatable({ },
|
||||
{
|
||||
__index = function(t, k)
|
||||
local fn = token(k)
|
||||
rawset(t, k, fn)
|
||||
return fn
|
||||
end
|
||||
}
|
||||
)
|
||||
|
||||
P.int = pattern('%d+')
|
||||
|
||||
P.var = pattern('[%a_][%w_]*')
|
||||
|
||||
-- '}' needs to be escaped in normal text (i.e #0)
|
||||
local __text0 = consume({ '$' }, { '\\', '}' })
|
||||
local __text1 = consume({ '}' }, { '\\' })
|
||||
local __text2 = consume({ ':' }, { '\\' })
|
||||
local __text3 = consume({ '/' }, { '\\' })
|
||||
local __text4 = consume({ '$', '}' }, { '\\' })
|
||||
local __text5 = consume({ ',', '|' }, { '\\' })
|
||||
local __text6 = consume({ "$", "/" }, { "\\" })
|
||||
|
||||
P._if1 = P(__text1, to_text)
|
||||
P._if2 = P(__text2, to_text)
|
||||
P._else = P(__text1, to_text)
|
||||
|
||||
P.options = pattern('%l*')
|
||||
|
||||
P.regex = P(__text3, to_text)
|
||||
|
||||
P.format = P(any(
|
||||
seq(t['$'], P.int),
|
||||
seq(t['${'], P.int, maybe(seq(t[':'], any(
|
||||
seq(t['/'], any(t['upcase'], t['downcase'], t['capitalize'], t['pascalcase'], t['camelcase'])),
|
||||
seq(t['+'], P._if1),
|
||||
seq(t['?'], P._if2, t[':'], P._else),
|
||||
seq(t['-'], P._else),
|
||||
P._else
|
||||
))), t['}'])
|
||||
), format_fn)
|
||||
|
||||
P.transform_text = P(__text6, to_text)
|
||||
P.transform = P(
|
||||
seq(t['/'], P.regex, t['/'], rep(any(P.format, P.transform_text)), t['/'], P.options),
|
||||
transform_fn
|
||||
)
|
||||
|
||||
P.variable_text = P(__text4, text_node)
|
||||
P.variable = P(any(
|
||||
seq(t['$'], P.var),
|
||||
seq(t['${'], P.var, maybe(any(
|
||||
-- grammar says a single mandatory 'any' for default, vscode seems to accept any*
|
||||
seq(t[':'], maybe(rep(any(P.dollars, P.variable_text)))),
|
||||
P.transform
|
||||
)), t['}'])
|
||||
), variable_node)
|
||||
|
||||
P.choice_text = P(__text5, to_text)
|
||||
P.choice = P(
|
||||
seq(t['${'], P.int, t['|'], P.choice_text, maybe(rep(seq(t[','], P.choice_text))), t['|}']),
|
||||
choice_node
|
||||
)
|
||||
|
||||
P.placeholder_text = P(__text4, text_node)
|
||||
P.placeholder = P(
|
||||
seq(t['${'], P.int, t[':'], maybe(rep(any(P.dollars, P.placeholder_text))), t['}']),
|
||||
placeholder_node
|
||||
)
|
||||
|
||||
P.tabstop = P(any(
|
||||
seq(t['$'], P.int),
|
||||
-- transform isnt specified in the grammar but seems to be supported by vscode
|
||||
seq(t['${'], P.int, maybe(P.transform), t['}'])
|
||||
), tabstop_node)
|
||||
|
||||
|
||||
P.dollars = any(P.tabstop, P.placeholder, P.choice, P.variable)
|
||||
|
||||
P.text = P(__text0, text_node)
|
||||
P.any = any(P.dollars, P.text)
|
||||
|
||||
P.snippet = P(rep(P.any), build_snippet)
|
||||
|
||||
|
||||
-- JSON files
|
||||
|
||||
-- defined at the end of the file
|
||||
local extensions
|
||||
|
||||
local fstate = { NOT_DONE = 'not done', QUEUED = 'queued', DONE = 'done' }
|
||||
local queue = { }
|
||||
local files = { }
|
||||
local files2exts = { }
|
||||
local exts2files = { }
|
||||
|
||||
local function parse_file(file)
|
||||
if files[file] == fstate.DONE then return end
|
||||
files[file] = fstate.DONE
|
||||
|
||||
local _f = io.open(file)
|
||||
if not _f then
|
||||
core.error('[LSP snippets] Could not open \'%s\'', file)
|
||||
return
|
||||
end
|
||||
local ok, r = pcall(json.decode, _f:read('a'))
|
||||
_f:close()
|
||||
if not ok then
|
||||
core.error('[LSP snippets] %s: %s', file, r:match('%d+:%s+(.*)'))
|
||||
return false
|
||||
end
|
||||
|
||||
local exts = file:match('%.json$') and files2exts[file]
|
||||
for i, s in pairs(r) do
|
||||
-- apparently body can be a single string
|
||||
local template = type(s.body) == 'table'
|
||||
and table.concat(s.body, '\n')
|
||||
or s.body
|
||||
if not template or template == '' then
|
||||
core.warn('[LSP snippets] missing \'body\' for %s (%s)', i, file)
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- https://code.visualstudio.com/docs/editor/userdefinedsnippets#_language-snippet-scope
|
||||
local scope
|
||||
if not exts and s.scope then
|
||||
local tmp = { }
|
||||
for _, l in ipairs(s.scope) do
|
||||
for _, e in ipairs(extensions[l:lower()]) do
|
||||
tmp[e] = true
|
||||
end
|
||||
end
|
||||
scope = { }
|
||||
for l in pairs(tmp) do
|
||||
table.insert(scope, l)
|
||||
end
|
||||
end
|
||||
|
||||
-- prefix may be an array
|
||||
local triggers = type(s.prefix) ~= 'table' and { s.prefix } or s.prefix
|
||||
if #triggers == 0 then
|
||||
core.warn('[LSP snippets] missing \'prefix\' for %s (%s)', i, file)
|
||||
goto continue
|
||||
end
|
||||
|
||||
for _, t in ipairs(triggers) do
|
||||
snippets.add {
|
||||
trigger = t,
|
||||
format = 'lsp',
|
||||
files = exts or scope,
|
||||
info = i,
|
||||
desc = s.description,
|
||||
template = template
|
||||
}
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function pop()
|
||||
while #queue > 0 do
|
||||
repeat until parse_file(table.remove(queue)) ~= nil
|
||||
if #queue > 0 then coroutine.yield() end
|
||||
end
|
||||
end
|
||||
|
||||
local function enqueue(filename)
|
||||
if not core.threads[THREAD_KEY] then
|
||||
core.add_thread(pop, THREAD_KEY)
|
||||
end
|
||||
files[filename] = fstate.QUEUED
|
||||
table.insert(queue, filename)
|
||||
end
|
||||
|
||||
local function add_file(filename, exts)
|
||||
if files[filename] then return end
|
||||
|
||||
if filename:match('%.code%-snippets$') then
|
||||
enqueue(filename)
|
||||
return
|
||||
end
|
||||
|
||||
if not filename:match('%.json$') then return end
|
||||
|
||||
if not exts then
|
||||
local lang_name = filename:match('([^/%\\]*)%.%w*$'):lower()
|
||||
exts = extensions[lang_name]
|
||||
if not exts then return end
|
||||
end
|
||||
|
||||
files[filename] = fstate.NOT_DONE
|
||||
exts = type(exts) == 'string' and { exts } or exts
|
||||
for _, e in ipairs(exts) do
|
||||
files2exts[filename] = files2exts[filename] or { }
|
||||
table.insert(files2exts[filename], '%.' .. e .. '$')
|
||||
exts2files[e] = exts2files[e] or { }
|
||||
table.insert(exts2files[e], filename)
|
||||
end
|
||||
end
|
||||
|
||||
local function for_filename(name)
|
||||
if not name then return end
|
||||
local ext = name:match('%.(.*)$')
|
||||
if not ext then return end
|
||||
local _files = exts2files[ext]
|
||||
if not _files then return end
|
||||
for _, f in ipairs(_files) do
|
||||
if files[f] == fstate.NOT_DONE then
|
||||
enqueue(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local doc_new = Doc.new
|
||||
function Doc:new(filename, ...)
|
||||
doc_new(self, filename, ...)
|
||||
for_filename(filename)
|
||||
end
|
||||
|
||||
local doc_set_filename = Doc.set_filename
|
||||
function Doc:set_filename(filename, ...)
|
||||
doc_set_filename(self, filename, ...)
|
||||
for_filename(filename)
|
||||
end
|
||||
|
||||
|
||||
-- API
|
||||
|
||||
local M = { }
|
||||
|
||||
function M.parse(template)
|
||||
local _s = B.new()
|
||||
local r = P.snippet(template, 1, _s)
|
||||
if not r.ok then
|
||||
return B.new():s(template):ok()
|
||||
elseif r.at == #template + 1 then
|
||||
return r.value
|
||||
else
|
||||
return _s:s(template:sub(r.at + 1)):ok()
|
||||
end
|
||||
end
|
||||
|
||||
snippets.parsers.lsp = M.parse
|
||||
|
||||
local warned = false
|
||||
function M.add_paths(paths)
|
||||
if not json then
|
||||
if not warned then
|
||||
core.error(
|
||||
'[LSP snippets] Could not add snippet file(s):' ..
|
||||
'JSON plugin not found'
|
||||
)
|
||||
warned = true
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
paths = type(paths) ~= 'table' and { paths } or paths
|
||||
|
||||
for _, p in ipairs(paths) do
|
||||
-- non absolute paths are treated as relative from USERDIR
|
||||
p = not common.is_absolute_path(p) and (USERDIR .. PATHSEP .. p) or p
|
||||
local finfo = system.get_file_info(p)
|
||||
|
||||
-- if path of a directory, add every file it contains and directories
|
||||
-- whose name is that of a lang
|
||||
if finfo and finfo.type == 'dir' then
|
||||
for _, f in ipairs(system.list_dir(p)) do
|
||||
f = p .. PATHSEP .. f
|
||||
finfo = system.get_file_info(f)
|
||||
if not finfo or finfo.type == 'file' then
|
||||
add_file(f)
|
||||
else
|
||||
-- only if the directory's name matches a language
|
||||
local lang_name = f:match('[^/%\\]*$'):lower()
|
||||
local exts = extensions[lang_name]
|
||||
for _, f2 in ipairs(system.list_dir(f)) do
|
||||
f2 = f .. PATHSEP .. f2
|
||||
finfo = system.get_file_info(f2)
|
||||
if not finfo or finfo.type == 'file' then
|
||||
add_file(f2, exts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- if path of a file, add the file
|
||||
else
|
||||
add_file(p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- arbitrarily cleaned up extension dump from https://gist.github.com/ppisarczyk/43962d06686722d26d176fad46879d41
|
||||
-- nothing after this
|
||||
|
||||
-- 90% of these are still useless but cba
|
||||
|
||||
extensions = {
|
||||
['ats'] = { 'dats', 'hats', 'sats', },
|
||||
['ada'] = { 'adb', 'ada', 'ads', },
|
||||
['agda'] = { 'agda', },
|
||||
['asciidoc'] = { 'asciidoc', 'adoc', 'asc', },
|
||||
['assembly'] = { 'asm', 'nasm', },
|
||||
['autohotkey'] = { 'ahk', 'ahkl', },
|
||||
['awk'] = { 'awk', 'auk', 'gawk', 'mawk', 'nawk', },
|
||||
['batchfile'] = { 'bat', 'cmd', },
|
||||
['c'] = { 'c', 'h', },
|
||||
['c#'] = { 'cs', 'cake', 'cshtml', 'csx', },
|
||||
['c++'] = { 'cpp', 'c++', 'cc', 'cp', 'cxx', 'h', 'h++', 'hh', 'hpp', 'hxx', },
|
||||
['cmake'] = { 'cmake', 'cmake.in', },
|
||||
['cobol'] = { 'cob', 'cbl', 'ccp', 'cobol', 'cpy', },
|
||||
['css'] = { 'css', },
|
||||
['clean'] = { 'icl', 'dcl', },
|
||||
['clojure'] = { 'clj', 'boot', 'cl2', 'cljc', 'cljs', 'cljs.hl', 'cljscm', 'cljx', 'hic', },
|
||||
['common lisp'] = { 'lisp', 'asd', 'cl', 'l', 'lsp', 'ny', 'podsl', 'sexp', },
|
||||
['component pascal'] = { 'cp', 'cps', },
|
||||
['coq'] = { 'coq', 'v', },
|
||||
['crystal'] = { 'cr', },
|
||||
['cuda'] = { 'cu', 'cuh', },
|
||||
['d'] = { 'd', 'di', },
|
||||
['dart'] = { 'dart', },
|
||||
['dockerfile'] = { 'dockerfile', },
|
||||
['eiffel'] = { 'e', },
|
||||
['elixir'] = { 'ex', 'exs', },
|
||||
['elm'] = { 'elm', },
|
||||
['emacs lisp'] = { 'el', 'emacs', 'emacs.desktop', },
|
||||
['erlang'] = { 'erl', 'es', 'escript', 'hrl', 'xrl', 'yrl', },
|
||||
['f#'] = { 'fs', 'fsi', 'fsx', },
|
||||
['fortran'] = { 'f90', 'f', 'f03', 'f08', 'f77', 'f95', 'for', 'fpp', },
|
||||
['factor'] = { 'factor', },
|
||||
['forth'] = { 'fth', '4th', 'f', 'for', 'forth', 'fr', 'frt', 'fs', },
|
||||
['go'] = { 'go', },
|
||||
['groff'] = { 'man', '1', '1in', '1m', '1x', '2', '3', '3in', '3m', '3qt', '3x', '4', '5', '6', '7', '8', '9', 'l', 'me', 'ms', 'n', 'rno', 'roff', },
|
||||
['groovy'] = { 'groovy', 'grt', 'gtpl', 'gvy', },
|
||||
['html'] = { 'html', 'htm', 'html.hl', 'xht', 'xhtml', },
|
||||
['haskell'] = { 'hs', 'hsc', },
|
||||
['idris'] = { 'idr', 'lidr', },
|
||||
['jsx'] = { 'jsx', },
|
||||
['java'] = { 'java', },
|
||||
['javascript'] = { 'js', },
|
||||
['julia'] = { 'jl', },
|
||||
['jupyter notebook'] = { 'ipynb', },
|
||||
['kotlin'] = { 'kt', 'ktm', 'kts', },
|
||||
['lean'] = { 'lean', 'hlean', },
|
||||
['less'] = { 'less', },
|
||||
['lua'] = { 'lua', 'fcgi', 'nse', 'pd_lua', 'rbxs', 'wlua', },
|
||||
['markdown'] = { 'md', 'markdown', 'mkd', 'mkdn', 'mkdown', 'ron', },
|
||||
['modula-2'] = { 'mod', },
|
||||
['moonscript'] = { 'moon', },
|
||||
['ocaml'] = { 'ml', 'eliom', 'eliomi', 'ml4', 'mli', 'mll', 'mly', },
|
||||
['objective-c'] = { 'm', 'h', },
|
||||
['objective-c++'] = { 'mm', },
|
||||
['oz'] = { 'oz', },
|
||||
['php'] = { 'php', 'aw', 'ctp', 'fcgi', 'inc', 'php3', 'php4', 'php5', 'phps', 'phpt', },
|
||||
['plsql'] = { 'pls', 'pck', 'pkb', 'pks', 'plb', 'plsql', 'sql', },
|
||||
['plpgsql'] = { 'sql', },
|
||||
['pascal'] = { 'pas', 'dfm', 'dpr', 'inc', 'lpr', 'pp', },
|
||||
['perl'] = { 'pl', 'al', 'cgi', 'fcgi', 'perl', 'ph', 'plx', 'pm', 'pod', 'psgi', 't', },
|
||||
['perl6'] = { '6pl', '6pm', 'nqp', 'p6', 'p6l', 'p6m', 'pl', 'pl6', 'pm', 'pm6', 't', },
|
||||
['picolisp'] = { 'l', },
|
||||
['pike'] = { 'pike', 'pmod', },
|
||||
['pony'] = { 'pony', },
|
||||
['postscript'] = { 'ps', 'eps', },
|
||||
['powershell'] = { 'ps1', 'psd1', 'psm1', },
|
||||
['prolog'] = { 'pl', 'pro', 'prolog', 'yap', },
|
||||
['python'] = { 'py', 'bzl', 'cgi', 'fcgi', 'gyp', 'lmi', 'pyde', 'pyp', 'pyt', 'pyw', 'rpy', 'tac', 'wsgi', 'xpy', },
|
||||
['racket'] = { 'rkt', 'rktd', 'rktl', 'scrbl', },
|
||||
['rebol'] = { 'reb', 'r', 'r2', 'r3', 'rebol', },
|
||||
['ruby'] = { 'rb', 'builder', 'fcgi', 'gemspec', 'god', 'irbrc', 'jbuilder', 'mspec', 'pluginspec', 'podspec', 'rabl', 'rake', 'rbuild', 'rbw', 'rbx', 'ru', 'ruby', 'thor', 'watchr', },
|
||||
['rust'] = { 'rs', 'rs.in', },
|
||||
['scss'] = { 'scss', },
|
||||
['sql'] = { 'sql', 'cql', 'ddl', 'inc', 'prc', 'tab', 'udf', 'viw', },
|
||||
['sqlpl'] = { 'sql', 'db2', },
|
||||
['scala'] = { 'scala', 'sbt', 'sc', },
|
||||
['scheme'] = { 'scm', 'sld', 'sls', 'sps', 'ss', },
|
||||
['self'] = { 'self', },
|
||||
['shell'] = { 'sh', 'bash', 'bats', 'cgi', 'command', 'fcgi', 'ksh', 'sh.in', 'tmux', 'tool', 'zsh', },
|
||||
['smalltalk'] = { 'st', 'cs', },
|
||||
['standard ml'] = { 'ML', 'fun', 'sig', 'sml', },
|
||||
['swift'] = { 'swift', },
|
||||
['tcl'] = { 'tcl', 'adp', 'tm', },
|
||||
['tex'] = { 'tex', 'aux', 'bbx', 'bib', 'cbx', 'cls', 'dtx', 'ins', 'lbx', 'ltx', 'mkii', 'mkiv', 'mkvi', 'sty', 'toc', },
|
||||
['typescript'] = { 'ts', 'tsx', },
|
||||
['vala'] = { 'vala', 'vapi', },
|
||||
['verilog'] = { 'v', 'veo', },
|
||||
['visual basic'] = { 'vb', 'bas', 'cls', 'frm', 'frx', 'vba', 'vbhtml', 'vbs', },
|
||||
}
|
||||
|
||||
extensions.cpp = extensions['c++']
|
||||
extensions.csharp = extensions['c#']
|
||||
extensions.latex = extensions.tex
|
||||
extensions.objc = extensions['objective-c']
|
||||
|
||||
M.extensions = extensions
|
||||
|
||||
return M
|
||||
12
.config/lite-xl/plugins/lsp_yaml/init.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- mod-version:3
|
||||
|
||||
local lspconfig = require "plugins.lsp.config"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
||||
local installed_path = USERDIR .. PATHSEP .. "plugins" .. PATHSEP .. "lsp_yaml" .. PATHSEP .. "yaml-language-server" .. PATHSEP .. "dist" .. PATHSEP .. "index.js"
|
||||
local node = require "libraries.nodejs"
|
||||
|
||||
lspconfig.yamlls.setup(common.merge({
|
||||
command = { node.path_bin, installed_path, "--stdio" },
|
||||
}, config.plugins.lsp_yaml or {}))
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Red Hat Inc. and others.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6339
.config/lite-xl/plugins/lsp_yaml/yaml-language-server/dist/LICENSES
vendored
Normal file
106295
.config/lite-xl/plugins/lsp_yaml/yaml-language-server/dist/index.js
vendored
Normal file
633
.config/lite-xl/plugins/minimap.lua
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local Highlighter = require "core.doc.highlighter"
|
||||
local Object = require "core.object"
|
||||
local Scrollbar = require "core.scrollbar"
|
||||
|
||||
-- Sample configurations:
|
||||
-- full width:
|
||||
-- config.plugins.minimap.highlight_width = 100
|
||||
-- config.plugins.minimap.gutter_width = 0
|
||||
-- left side:
|
||||
-- config.plugins.minimap.highlight_align = 'left'
|
||||
-- config.plugins.minimap.highlight_width = 3
|
||||
-- config.plugins.minimap.gutter_width = 4
|
||||
-- right side:
|
||||
-- config.plugins.minimap.highlight_align = 'right'
|
||||
-- config.plugins.minimap.highlight_width = 5
|
||||
-- config.plugins.minimap.gutter_width = 0
|
||||
|
||||
-- General plugin settings
|
||||
config.plugins.minimap = common.merge({
|
||||
enabled = true,
|
||||
width = 100,
|
||||
instant_scroll = false,
|
||||
syntax_highlight = true,
|
||||
scale = 1,
|
||||
-- number of spaces needed to split a token
|
||||
spaces_to_split = 2,
|
||||
-- hide on small docs (can be true, false or min number of lines)
|
||||
avoid_small_docs = false,
|
||||
-- how many spaces one tab is equivalent to
|
||||
tab_width = 4,
|
||||
draw_background = true,
|
||||
-- you can override these colors
|
||||
selection_color = nil,
|
||||
caret_color = nil,
|
||||
-- If other plugins provide per-line highlights,
|
||||
-- this controls the placement. (e.g. gitdiff_highlight)
|
||||
highlight_align = 'left',
|
||||
highlight_width = 3,
|
||||
gutter_width = 5,
|
||||
-- The config specification used by the settings gui
|
||||
config_spec = {
|
||||
name = "Mini Map",
|
||||
{
|
||||
label = "Enabled",
|
||||
description = "Activate the minimap by default.",
|
||||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Width",
|
||||
description = "Width of the minimap in pixels.",
|
||||
path = "width",
|
||||
type = "number",
|
||||
default = 100,
|
||||
min = 50,
|
||||
max = 1000
|
||||
},
|
||||
{
|
||||
label = "Instant Scroll",
|
||||
description = "When enabled disables the scrolling animation.",
|
||||
path = "instant_scroll",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Syntax Highlighting",
|
||||
description = "Disable to improve performance.",
|
||||
path = "syntax_highlight",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Scale",
|
||||
description = "Size of the minimap using a scaling factor.",
|
||||
path = "scale",
|
||||
type = "number",
|
||||
default = 1,
|
||||
min = 0.5,
|
||||
max = 10,
|
||||
step = 0.1
|
||||
},
|
||||
{
|
||||
label = "Spaces to split",
|
||||
description = "Number of spaces needed to split a token.",
|
||||
path = "spaces_to_split",
|
||||
type = "number",
|
||||
default = 2,
|
||||
min = 1
|
||||
},
|
||||
{
|
||||
label = "Hide for small Docs",
|
||||
description = "Hide the minimap when a Doc is small enough.",
|
||||
path = "avoid_small_docs",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Small Docs definition",
|
||||
description = "Size of a Doc to be considered small. Use 0 to automatically decide.",
|
||||
path = "avoid_small_docs_len",
|
||||
type = "number",
|
||||
default = 0,
|
||||
min = 0,
|
||||
on_apply = function(value)
|
||||
if value == 0 then
|
||||
config.plugins.minimap.avoid_small_docs = true
|
||||
else
|
||||
config.plugins.minimap.avoid_small_docs = value
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Tabs Width",
|
||||
description = "The amount of spaces that represent a tab.",
|
||||
path = "tab_width",
|
||||
type = "number",
|
||||
default = 4,
|
||||
min = 1,
|
||||
max = 8
|
||||
},
|
||||
{
|
||||
label = "Draw Background",
|
||||
description = "When disabled makes the minimap transparent.",
|
||||
path = "draw_background",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Selection Color",
|
||||
description = "Background color of selected text.",
|
||||
path = "selection_color",
|
||||
type = "color",
|
||||
default = string.format("#%02X%02X%02X%02X",
|
||||
style.dim[1], style.dim[2], style.dim[3], style.dim[4]
|
||||
)
|
||||
},
|
||||
{
|
||||
label = "Caret Color",
|
||||
description = "Background color of active line.",
|
||||
path = "caret_color",
|
||||
type = "color",
|
||||
default = string.format("#%02X%02X%02X%02X",
|
||||
style.caret[1], style.caret[2], style.caret[3], style.caret[4]
|
||||
)
|
||||
},
|
||||
{
|
||||
label = "Highlight Alignment",
|
||||
path = "highlight_align",
|
||||
type = "selection",
|
||||
default = "left",
|
||||
values = {
|
||||
{"Left", "left"},
|
||||
{"Right", "right"}
|
||||
}
|
||||
},
|
||||
{
|
||||
label = "Highlight Width",
|
||||
path = "highlight_width",
|
||||
type = "number",
|
||||
default = 3,
|
||||
min = 0,
|
||||
max = 50
|
||||
},
|
||||
{
|
||||
label = "Gutter Width",
|
||||
description = "Left padding of the minimap.",
|
||||
path = "gutter_width",
|
||||
type = "number",
|
||||
default = 5,
|
||||
min = 0,
|
||||
max = 50
|
||||
},
|
||||
}
|
||||
}, config.plugins.minimap)
|
||||
|
||||
|
||||
-- contains the settings values that require a cache reset if changed
|
||||
local cached_settings = {
|
||||
color_scheme_canary = nil,
|
||||
syntax_highlight = nil,
|
||||
spaces_to_split = nil,
|
||||
scale = nil,
|
||||
width = nil,
|
||||
}
|
||||
|
||||
-- Configure size for rendering each char in the minimap
|
||||
local char_spacing
|
||||
local char_height
|
||||
local line_spacing
|
||||
|
||||
-- cache for the location of the rects for each Doc
|
||||
local highlighter_cache
|
||||
local function reset_cache()
|
||||
highlighter_cache = setmetatable({}, { __mode = "k" })
|
||||
cached_settings = {
|
||||
color_scheme_canary = style.syntax["normal"],
|
||||
syntax_highlight = config.plugins.minimap.syntax_highlight,
|
||||
spaces_to_split = config.plugins.minimap.spaces_to_split,
|
||||
scale = config.plugins.minimap.scale,
|
||||
width = config.plugins.minimap.width,
|
||||
}
|
||||
char_spacing = 0.8 * SCALE * config.plugins.minimap.scale
|
||||
-- keep y aligned to pixels
|
||||
char_height = math.max(1, math.floor(1 * SCALE * config.plugins.minimap.scale + 0.5))
|
||||
line_spacing = math.max(1, math.floor(2 * SCALE * config.plugins.minimap.scale + 0.5))
|
||||
end
|
||||
reset_cache()
|
||||
|
||||
|
||||
local function reset_cache_if_needed()
|
||||
if
|
||||
cached_settings.color_scheme_canary ~= style.syntax["normal"]
|
||||
or cached_settings.syntax_highlight ~= config.plugins.minimap.syntax_highlight
|
||||
or cached_settings.spaces_to_split ~= config.plugins.minimap.spaces_to_split
|
||||
or cached_settings.scale ~= config.plugins.minimap.scale
|
||||
or cached_settings.width ~= config.plugins.minimap.width
|
||||
then
|
||||
reset_cache()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Move cache to make space for new lines
|
||||
local prev_insert_notify = Highlighter.insert_notify
|
||||
function Highlighter:insert_notify(line, n, ...)
|
||||
prev_insert_notify(self, line, n, ...)
|
||||
local blanks = { }
|
||||
if not highlighter_cache[self] then
|
||||
highlighter_cache[self] = {}
|
||||
else
|
||||
local blanks = { }
|
||||
for i = 1, n do
|
||||
blanks[i] = false
|
||||
end
|
||||
common.splice(highlighter_cache[self], line, 0, blanks)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Close the cache gap created by removed lines
|
||||
local prev_remove_notify = Highlighter.remove_notify
|
||||
function Highlighter:remove_notify(line, n, ...)
|
||||
prev_remove_notify(self, line, n, ...)
|
||||
if not highlighter_cache[self] then
|
||||
highlighter_cache[self] = {}
|
||||
else
|
||||
common.splice(highlighter_cache[self], line, n)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Remove changed lines from the cache
|
||||
local prev_tokenize_line = Highlighter.tokenize_line
|
||||
function Highlighter:tokenize_line(idx, state, ...)
|
||||
local res = prev_tokenize_line(self, idx, state, ...)
|
||||
if not highlighter_cache[self] then
|
||||
highlighter_cache[self] = {}
|
||||
end
|
||||
highlighter_cache[self][idx] = false
|
||||
return res
|
||||
end
|
||||
|
||||
-- Ask the Highlighter to retokenize the lines we have in cache
|
||||
local prev_invalidate = Highlighter.invalidate
|
||||
function Highlighter:invalidate(idx, ...)
|
||||
local cache = highlighter_cache[self]
|
||||
if cache then
|
||||
self.max_wanted_line = math.max(self.max_wanted_line, #cache)
|
||||
end
|
||||
return prev_invalidate(self, idx, ...)
|
||||
end
|
||||
|
||||
|
||||
-- Remove cache on Highlighter reset (for example on syntax change)
|
||||
local prev_soft_reset = Highlighter.soft_reset
|
||||
function Highlighter:soft_reset(...)
|
||||
prev_soft_reset(self, ...)
|
||||
highlighter_cache[self] = {}
|
||||
end
|
||||
|
||||
|
||||
local MiniMap = Scrollbar:extend()
|
||||
|
||||
|
||||
function MiniMap:new(dv, original_v_scrollbar)
|
||||
MiniMap.super.new(self, { direction = "v", alignment = "e",
|
||||
force_status = "expanded",
|
||||
expanded_size = cached_settings.width,
|
||||
expanded_margin = 0 })
|
||||
self.original_force_status = original_v_scrollbar.force_status
|
||||
self.original_expanded_size = original_v_scrollbar.expanded_size
|
||||
self.original_expanded_margin = original_v_scrollbar.expanded_margin
|
||||
self.dv = dv
|
||||
self.enabled = nil
|
||||
self.was_enabled = true
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:swap_to_status()
|
||||
local enabled = self:is_minimap_enabled()
|
||||
if not enabled and self.was_enabled then
|
||||
self.force_status = self.original_force_status
|
||||
self.expanded_size = self.original_expanded_size
|
||||
self.expanded_margin = self.original_expanded_margin
|
||||
self.was_enabled = false
|
||||
elseif enabled and not self.was_enabled then
|
||||
self.force_status = "expanded"
|
||||
self.expanded_size = cached_settings.width
|
||||
self.expanded_margin = 0
|
||||
self.was_enabled = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:update()
|
||||
self:swap_to_status()
|
||||
if self:is_minimap_enabled() then
|
||||
reset_cache_if_needed()
|
||||
self.expanded_size = cached_settings.width
|
||||
local lh = self.dv:get_line_height()
|
||||
local nlines = self.dv.size.y / lh
|
||||
self.minimum_thumb_size = nlines * line_spacing
|
||||
end
|
||||
MiniMap.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:line_highlight_color(line_index, docview)
|
||||
-- other plugins can override this, and return a color
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:is_minimap_enabled()
|
||||
if self.enabled ~= nil then return self.enabled end
|
||||
if not config.plugins.minimap.enabled then return false end
|
||||
if config.plugins.minimap.avoid_small_docs then
|
||||
local last_line = #self.dv.doc.lines
|
||||
if type(config.plugins.minimap.avoid_small_docs) == "number" then
|
||||
return last_line > config.plugins.minimap.avoid_small_docs
|
||||
else
|
||||
local docview = self.dv
|
||||
local _, y = docview:get_line_screen_position(last_line, #docview.doc.lines[last_line])
|
||||
y = y + docview.scroll.y - docview.position.y + docview:get_line_height()
|
||||
return y > docview.size.y
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:_on_mouse_pressed_normal(button, x, y, clicks)
|
||||
local overlaps = self:_overlaps_normal(x, y)
|
||||
local percent = MiniMap.super._on_mouse_pressed_normal(self, button, x, y, clicks)
|
||||
if overlaps == "track" then
|
||||
-- We need to adjust the percentage to scroll to the line in the minimap
|
||||
-- that was "clicked"
|
||||
local minimap_line, _ = self:get_minimap_lines()
|
||||
local _, track_y, _, _ = self:_get_track_rect_normal()
|
||||
local line = minimap_line + (y - track_y) // line_spacing
|
||||
local _, y = self.dv:get_line_screen_position(line)
|
||||
local _, oy = self.dv:get_content_offset()
|
||||
local nr = self.normal_rect
|
||||
percent = common.clamp((y - oy - (self.dv.size.y) / 2) / (nr.scrollable - self.dv.size.y), 0, 1)
|
||||
end
|
||||
return percent
|
||||
end
|
||||
|
||||
|
||||
local function get_visible_minline(dv)
|
||||
local _, y, _, _ = dv:get_content_bounds()
|
||||
local lh = dv:get_line_height()
|
||||
local minline = math.max(0, y / lh + 1)
|
||||
return minline
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:get_minimap_lines()
|
||||
local _, track_y, _, h = self:_get_track_rect_normal()
|
||||
local _, thumb_y, _, _ = self:_get_thumb_rect_normal()
|
||||
|
||||
local nlines = h // line_spacing
|
||||
|
||||
local minline = get_visible_minline(self.dv)
|
||||
local top_lines = (thumb_y - track_y) / line_spacing
|
||||
local lines_start, offset = math.modf(minline - top_lines)
|
||||
if lines_start <= 1 and nlines >= #self.dv.doc.lines then
|
||||
offset = 0
|
||||
end
|
||||
return common.clamp(lines_start, 1, #self.dv.doc.lines), common.clamp(nlines, 1, #self.dv.doc.lines), offset * line_spacing
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:set_size(x, y, w, h, scrollable)
|
||||
if not self:is_minimap_enabled() then return MiniMap.super.set_size(self, x, y, w, h, scrollable) end
|
||||
-- If possible, use the size needed to only manage the visible minimap lines.
|
||||
-- This allows us to let Scrollbar manage the thumb.
|
||||
h = math.min(h, line_spacing * (scrollable // self.dv:get_line_height()))
|
||||
MiniMap.super.set_size(self, x, y, w, h, scrollable)
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:draw()
|
||||
if not self:is_minimap_enabled() then return MiniMap.super.draw(self) end
|
||||
local dv = self.dv
|
||||
local x, y, w, h = self:get_track_rect()
|
||||
|
||||
local highlight = dv.hovered_scrollbar or dv.dragging_scrollbar
|
||||
local visual_color = highlight and style.scrollbar2 or style.scrollbar
|
||||
|
||||
|
||||
if config.plugins.minimap.draw_background then
|
||||
renderer.draw_rect(x, y, w, self.dv.size.y, style.minimap_background or style.background)
|
||||
end
|
||||
self:draw_thumb()
|
||||
|
||||
local minimap_lines_start, minimap_lines_count, y_offset = self:get_minimap_lines()
|
||||
local line_selection_offset = line_spacing - char_height
|
||||
y = y - y_offset + line_selection_offset
|
||||
|
||||
-- highlight the selected lines, and the line with the caret on it
|
||||
local selection_color = config.plugins.minimap.selection_color or style.dim
|
||||
local caret_color = config.plugins.minimap.caret_color or style.caret
|
||||
|
||||
for _, line1, _, line2, _ in dv.doc:get_selections() do
|
||||
local selection1_y = y + (line1 - minimap_lines_start) * line_spacing - line_selection_offset
|
||||
local selection2_y = y + (line2 - minimap_lines_start) * line_spacing - line_selection_offset
|
||||
local selection_min_y = math.min(selection1_y, selection2_y)
|
||||
local selection_h = math.abs(selection2_y - selection1_y) + 1 + line_selection_offset
|
||||
renderer.draw_rect(x, selection_min_y, w, selection_h, selection_color)
|
||||
renderer.draw_rect(x, selection1_y, w, line_spacing + line_selection_offset, caret_color)
|
||||
end
|
||||
|
||||
local highlight_align = config.plugins.minimap.highlight_align
|
||||
local highlight_width = config.plugins.minimap.highlight_width
|
||||
local gutter_width = config.plugins.minimap.gutter_width
|
||||
|
||||
-- time to draw the actual code, setup some local vars that are used in both highlighted and plain rendering.
|
||||
local line_y = y
|
||||
|
||||
-- when not using syntax highlighted rendering, just use the normal color but dim it 50%.
|
||||
local color = style.syntax["normal"]
|
||||
color = {color[1], color[2], color[3], color[4] * 0.5}
|
||||
|
||||
-- we try to "batch" characters so that they can be rendered as just one rectangle instead of one for each.
|
||||
local batch_width = 0
|
||||
local batch_start = x
|
||||
local last_batch_end = -1
|
||||
local minimap_cutoff_x = config.plugins.minimap.width * SCALE
|
||||
local batch_syntax_type = nil
|
||||
local function flush_batch(type, cache)
|
||||
if batch_width > 0 then
|
||||
local lastidx = #cache
|
||||
local old_color = color
|
||||
color = style.syntax[type]
|
||||
if config.plugins.minimap.syntax_highlight and color ~= nil then
|
||||
-- fetch and dim colors
|
||||
color = {color[1], color[2], color[3], (color[4] or 255) * 0.5}
|
||||
else
|
||||
color = old_color
|
||||
end
|
||||
if #cache >= 3 then
|
||||
local last_color = cache[lastidx]
|
||||
if
|
||||
last_batch_end == batch_start -- no space skipped
|
||||
and (
|
||||
batch_syntax_type == type -- and same syntax
|
||||
or ( -- or same color
|
||||
last_color[1] == color[1]
|
||||
and last_color[2] == color[2]
|
||||
and last_color[3] == color[3]
|
||||
and last_color[4] == color[4]
|
||||
)
|
||||
)
|
||||
then
|
||||
batch_start = cache[lastidx - 2]
|
||||
batch_width = cache[lastidx - 1] + batch_width
|
||||
lastidx = lastidx - 3
|
||||
end
|
||||
end
|
||||
cache[lastidx + 1] = batch_start
|
||||
cache[lastidx + 2] = batch_width
|
||||
cache[lastidx + 3] = color
|
||||
end
|
||||
batch_syntax_type = type
|
||||
batch_start = batch_start + batch_width
|
||||
last_batch_end = batch_start
|
||||
batch_width = 0
|
||||
end
|
||||
|
||||
local highlight_x
|
||||
if highlight_align == 'left' then
|
||||
highlight_x = x
|
||||
else
|
||||
highlight_x = x + w - highlight_width
|
||||
end
|
||||
local function render_highlight(idx, line_y)
|
||||
local highlight_color = self:line_highlight_color(idx, self.dv)
|
||||
if highlight_color then
|
||||
renderer.draw_rect(highlight_x, line_y - line_selection_offset,
|
||||
highlight_width, line_spacing + line_selection_offset, highlight_color)
|
||||
end
|
||||
end
|
||||
|
||||
local endidx = math.min(minimap_lines_start + minimap_lines_count, #self.dv.doc.lines)
|
||||
|
||||
if not highlighter_cache[dv.doc.highlighter] then
|
||||
highlighter_cache[dv.doc.highlighter] = {}
|
||||
end
|
||||
|
||||
-- per line
|
||||
for idx = minimap_lines_start, endidx do
|
||||
batch_syntax_type = nil
|
||||
batch_start = 0
|
||||
batch_width = 0
|
||||
last_batch_end = -1
|
||||
|
||||
render_highlight(idx, line_y)
|
||||
local cache = highlighter_cache[dv.doc.highlighter][idx]
|
||||
if not highlighter_cache[dv.doc.highlighter][idx] then -- need to cache
|
||||
highlighter_cache[dv.doc.highlighter][idx] = {}
|
||||
cache = highlighter_cache[dv.doc.highlighter][idx]
|
||||
-- per token
|
||||
for _, type, text in dv.doc.highlighter:each_token(idx) do
|
||||
if not config.plugins.minimap.syntax_highlight then
|
||||
type = nil
|
||||
end
|
||||
local start = 1
|
||||
while true do
|
||||
-- find text followed spaces followed by newline
|
||||
local s, e, w, eol = string.ufind(text, "[^%s]*()[ \t]*()\n?", start)
|
||||
if not s then break end
|
||||
local nchars = w - s
|
||||
start = e + 1
|
||||
batch_width = batch_width + char_spacing * nchars
|
||||
|
||||
local nspaces = 0
|
||||
for i=w,e do
|
||||
local whitespace = string.sub(text, i, i)
|
||||
if whitespace == "\t" then
|
||||
nspaces = nspaces + config.plugins.minimap.tab_width
|
||||
elseif whitespace == " " then
|
||||
nspaces = nspaces + 1
|
||||
end
|
||||
end
|
||||
-- not enough spaces; consider them part of the batch
|
||||
if nspaces < config.plugins.minimap.spaces_to_split then
|
||||
batch_width = batch_width + nspaces * char_spacing
|
||||
end
|
||||
-- line has ended or no more space in the minimap;
|
||||
-- we can go to the next line
|
||||
if eol <= w or batch_start + batch_width > minimap_cutoff_x then
|
||||
if batch_width > 0 then
|
||||
flush_batch(type, cache)
|
||||
end
|
||||
break
|
||||
end
|
||||
-- enough spaces to split the batch
|
||||
if nspaces >= config.plugins.minimap.spaces_to_split then
|
||||
flush_batch(type, cache)
|
||||
batch_start = batch_start + nspaces * char_spacing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- draw from cache
|
||||
for i=1,#cache,3 do
|
||||
local batch_start = cache[i ] + x + gutter_width
|
||||
local batch_width = cache[i + 1]
|
||||
local color = cache[i + 2]
|
||||
renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
|
||||
end
|
||||
line_y = line_y + line_spacing
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local old_docview_new = DocView.new
|
||||
function DocView:new(doc)
|
||||
old_docview_new(self, doc)
|
||||
if self:is(DocView) then
|
||||
self.v_scrollbar = MiniMap(self, self.v_scrollbar)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_all_docviews(node, t)
|
||||
t = t or {}
|
||||
if not node then return end
|
||||
if node.type == "leaf" then
|
||||
for i,v in ipairs(node.views) do
|
||||
if v:is(DocView) then
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
get_all_docviews(node.a, t)
|
||||
get_all_docviews(node.b, t)
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["minimap:toggle-visibility"] = function()
|
||||
config.plugins.minimap.enabled = not config.plugins.minimap.enabled
|
||||
for i,v in ipairs(get_all_docviews(core.root_view.root_node)) do
|
||||
v.v_scrollbar.enabled = nil
|
||||
end
|
||||
end,
|
||||
["minimap:toggle-syntax-highlighting"] = function()
|
||||
config.plugins.minimap.syntax_highlight = not config.plugins.minimap.syntax_highlight
|
||||
end
|
||||
})
|
||||
|
||||
command.add("core.docview!", {
|
||||
["minimap:toggle-visibility-for-current-view"] = function(dv)
|
||||
local sb = dv.v_scrollbar
|
||||
if sb.enabled ~= nil then
|
||||
sb.enabled = not sb.enabled
|
||||
else
|
||||
sb.enabled = not config.plugins.minimap.enabled
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
return MiniMap
|
||||