Initial commit
This commit is contained in:
commit
209ba130c0
4852 changed files with 1517959 additions and 0 deletions
281
.config/lite-xl/plugins/lintplus/README.md
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
BIN
.config/lite-xl/plugins/lintplus/screenshots/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
.config/lite-xl/plugins/lintplus/screenshots/2.png
Normal file
BIN
.config/lite-xl/plugins/lintplus/screenshots/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
Loading…
Add table
Add a link
Reference in a new issue