null-ls.nvim
null-ls.nvim copied to clipboard
Add code action and diagnostic builtins for typos
typos is a code spell checker for common misspelled words written in Rust.
typos
has a brief format for output which is easier to parse for diagnostic source but I decided to go for json format instead (it outputs 1 json object per typo). The code action is entirely yanked from https://github.com/jose-elias-alvarez/null-ls.nvim/pull/915.
A screenshot for diagnostic:
And here is the output formats:
$ typos diagnostics_spec.lua
error: `langauge` should be `language`
--> diagnostics_spec.lua:757:30
|
757 | message = [["langauge" is a misspelling of "language"]],
| ^^^^^^^^
|
error: `Ba` should be `By`, `Be`
--> diagnostics_spec.lua:1154:33
|
1154 | message = "`Ba` should be `By`, `Be`",
| ^^
|
$ typos --format brief diagnostics_spec.lua
diagnostics_spec.lua:757:29: `langauge` -> `language`
diagnostics_spec.lua:1154:32: `Ba` -> `By`, `Be`
$ typos --format json diagnostics_spec.lua
{"type":"typo","path":"diagnostics_spec.lua","line_num":757,"byte_offset":29,"typo":"langauge","corrections":["language"]}
{"type":"typo","path":"diagnostics_spec.lua","line_num":1146,"byte_offset":115,"typo":"Ba","corrections":["By","Be"]}
At a glance this looks good - I'll have time for hands-on testing later this week and will give more detailed feedback then. In the meantime, I'll ask a couple of the same questions that came up in #915
- How long does it take to generate suggestions on demand (that is, in the code action generator itself)?
- How much (if any) overhead does it add to generate corrections in the diagnostics source?
As I mentioned in the other PR, I generally prefer for sources to remain independent unless there's a clear advantage to using them together. In this case, it looks like corrections are automatically generated, so I don't think there's really any overhead, but I think it still adds some complexity to make one source dependent on the other.
I don't think a user would use the code action without the diagnostic so I group them together in this PR (this way the user of both doesn't run the same typos
command twice each time).
I can separate them if you prefer, using --format brief
for diagnostic and --format json
for code action.
Unlike cspell
the linting message of typos
always contains suggestions so yes, there shouldn't be any significant overhead.
I don't think a user would use the code action without the diagnostic so I group them together in this PR (this way the user of both doesn't run the same
typos
command twice each time).I can separate them if you prefer, using
--format brief
for diagnostic and--format json
for code action.Unlike
cspell
the linting message oftypos
always contains suggestions so yes, there shouldn't be any significant overhead.
Thanks for checking!
I installed the typos
CLI and saw that running typos --format json $FILENAME
is very quick (20-30ms on my end, even with 100+ misspelled words). The only reason for the unusual situation in #915 is that cspell
takes about a second to run when requesting code actions, which is a pretty noticeable delay for the user. If startup time is not an issue, I'd lean towards keeping the two sources separate, but it's up to you, especially if you think users will always want to run both sources (and if we add a mention of the code action source's dependency in the documentation).
I'm more convinced of separating the code action and diagnostic source now (I don't mind running the command twice as it is pretty fast anyways).
I'll change the PR in the weekend.
I changed the diagnostic source. For the code action, here is my attempt so far. It doesn't seem to work so I'd love to have some help.
local h = require("null-ls.helpers")
local methods = require("null-ls.methods")
local CODE_ACTION = methods.internal.CODE_ACTION
return h.make_builtin({
name = "typos",
meta = {
url = "https://github.com/crate-ci/typos",
description = "Source code spell checker written in Rust.",
},
method = CODE_ACTION,
filetypes = {},
generator_opts = {
command = "typos",
args = {
"--format",
"json",
"$FILENAME",
},
to_stdin = true,
format = "line",
check_exit_code = function(code)
return code == 2
end,
on_output = function(line, params)
local actions = {}
local ok, decoded = pcall(vim.json.decode, line)
if not ok then
return nil
end
local typo = decoded.typo
local col = decoded.byte_offset + 1
local end_col = col + typo:len()
local lnum = decoded.line_num
if params.col >= col and params.col < end_col and params.row == lnum then
for _, correction in ipairs(decoded.corrections) do
table.insert(actions, {
title = string.format("Use `%s`", correction),
action = function()
vim.api.nvim_buf_set_text(
params.bufnr,
lnum - 1,
col,
lnum,
end_col,
{ correction }
)
end,
})
end
return actions
end
return nil
end,
},
factory = h.generator_factory,
})
Here is the error I get when hovering the code action range:
Error executing vim.schedule lua callback: ...pack/packer/opt/plenary.nvim/lua/plenary/async/async.lua:14: The coroutine failed with this message: .../site/pack/packer/opt/null-ls.nvim/lua/null-ls/state.lua:28:
table index is nil
stack traceback:
[C]: in function 'error'
...pack/packer/opt/plenary.nvim/lua/plenary/async/async.lua:14: in function 'callback_or_next'
...pack/packer/opt/plenary.nvim/lua/plenary/async/async.lua:40: in function <...pack/packer/opt/plenary.nvim/lua/plenary/async/async.lua:39>
Hmm, not 100% sure what that error is about, but I fixed a couple of issues in the code:
local h = require("null-ls.helpers")
local methods = require("null-ls.methods")
local CODE_ACTION = methods.internal.CODE_ACTION
return h.make_builtin({
name = "typos",
meta = {
url = "https://github.com/crate-ci/typos",
description = "Source code spell checker written in Rust.",
},
method = CODE_ACTION,
filetypes = {},
generator_opts = {
command = "typos",
args = {
"--format",
"json",
"$FILENAME",
},
to_stdin = true,
check_exit_code = function(code)
return code == 2
end,
on_output = function(params, done)
local output = {}
-- manually decode and handle each line here to make sure code actions are simultaneously sent to the handler
for _, line in ipairs(vim.split(params.output, "\n")) do
local ok, decoded = pcall(vim.json.decode, line)
if ok then
table.insert(output, decoded)
end
end
local actions = {}
for _, decoded in ipairs(output) do
local typo = decoded.typo
local col = decoded.byte_offset + 1
local end_col = col + typo:len()
local lnum = decoded.line_num
-- simply match against the current line, since params.end_col is undefined
if params.row == lnum then
for _, correction in ipairs(decoded.corrections) do
table.insert(actions, {
title = string.format("Use `%s`", correction),
action = function()
-- this logic is not currently working on my end
vim.api.nvim_buf_set_text(params.bufnr, lnum - 1, col, lnum, end_col, { correction })
end,
})
end
end
end
return done(actions)
end,
},
factory = h.generator_factory,
})
This correctly generates code actions, but the logic to replace the misspelled word is not currently working on my end (it either replaces nothing or replaces a mismatching area of the buffer). Hopefully with that we can get this over the finish line!
Thanks for the code snippet. I'll look into it.
@FollieHiyuki Hello! Can you tell please what plugin/config are you using to show diagnostics under line?
@RayJameson it's lsp_lines.nvim.
@FollieHiyuki
Hi, I was wondering how you got the red "L" line for the diagnostics message as shown here: https://user-images.githubusercontent.com/67634026/183258932-a3d2c209-f983-4b9f-8670-cd5390c4058b.png
@FollieHiyuki
Hi, I was wondering how you got the red "L" line for the diagnostics message as shown here: https://user-images.githubusercontent.com/67634026/183258932-a3d2c209-f983-4b9f-8670-cd5390c4058b.png
Check my comment above, I asked the same question. And he already answered it...
@FollieHiyuki Hey, I just wrote the diagnostic source for the typos and then I came across this PR. Looks like you are almost done with the code_actions as well. Any progress on the above issue ? If it is taking time to fix the action issue, can we at least get the diagnosis source merged than we can work on action source separately.
I currently don't have much free time to fix this PR. If you can open another one to implement typos
as a diagnostic source (and maybe also the formatting source) then feel free to do so. I'll close this PR in favor of yours.
@FollieHiyuki I have separated the diagnostic source from this PR and raising a separator PR. Maybe by tomorrow I'll add the formatting source as well.