Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ This is a plugin to display spelling errors as diagnostics. Some language server
A simpler solution, therefore, is to use Neovim's existing spellchecking and diagnostics features. This is done by iterating through the flagged words in a buffer and passing them to `vim.diagnostic.set()`. Neovim is fast enough that this is ~instantaneous for most files. See below for installation and configuration instructions.

## Installation

I recommend using [Lazy.nvim](https://github.com/folke/lazy.nvim):

```lua
{
"ravibrock/spellwarn.nvim",
Expand All @@ -23,7 +25,9 @@ I recommend using [Lazy.nvim](https://github.com/folke/lazy.nvim):
Note that this uses Neovim's built-in spellchecking. This requires putting `vim.opt.spell = true` and `vim.opt.spelllang = [YOUR LANGUAGE HERE]` somewhere in your Neovim config if you haven't already. You may also want to add the word "spellwarn" to your Neovim dictionary. This can be done by putting the cursor onto "spellwarn" and hitting `zg`.

## Configuration

Pass any of the following options to `require("spellwarn").setup()`:

```lua
{
event = { -- event(s) to refresh diagnostics on
Expand All @@ -33,40 +37,55 @@ Pass any of the following options to `require("spellwarn").setup()`:
"TextChangedI",
"TextChangedP",
},

enable = true, -- enable diagnostics on startup

func_preprocess = function() end, -- function to do any custom processing of the diagnostics table before passing it to vim.diagnostic.set

bt_config = { -- buffer types to run on
[""] = true,
},
bt_default = false, -- default for types not in bt_config.

ft_config = { -- spellcheck method: "cursor", "iter", or boolean
alpha = false,
help = false,
lazy = false,
alpha = false,
help = false,
lazy = false,
lspinfo = false,
mason = false,
mason = false,
},
ft_default = true, -- default option for unspecified filetypes

max_file_size = nil, -- maximum file size to check in lines (nil for no limit)

severity = { -- severity for each spelling error type (false to disable diagnostics for that type)
spellbad = "WARN",
spellcap = "HINT",
spellbad = "WARN",
spellcap = "HINT",
spelllocal = "HINT",
spellrare = "INFO",
spellrare = "INFO",
},
suggest = false, -- show spelling suggestions in diagnostic message (works best with window-style message)
num_suggest = 3, -- number of spelling suggestions shown in diagnostic message
suggest = false, -- show spelling suggestions in diagnostic message
num_suggest = 3, -- number of suggestions shown in diagnostic message
prefix = "possible misspelling(s): ", -- prefix for each diagnostic message
diagnostic_opts = { severity_sort = true }, -- options for diagnostic display
}
```

Most options are overwritten (e.g. passing `ft_config = { python = false }` will mean that `alpha`, `mason`, etc. are set to true) but `severity` and `diagnostic_opts` are merged, so that (for example) passing `{ spellbad = "HINT" }` won't cause `spellcap` to be nil. You can pass any of `cursor`, `iter`, `treesitter`, `false`, or `true` as options to `ft_config`. The default method is `cursor`, which iterates through the buffer with `]s`. There is also `iter`, which uses Treesitter (if available) and the Lua API. Finally, `false` disables Spellwarn for that filetype and `true` uses the default (`cursor`). The `suggest` option adds spelling suggestions to the diagnostic message, it does not allow auto-complete. `num_suggest` specifies the number of suggestions to show in the diagnostic message. **If you have `suggest` set to `true`, you need to also have `num_suggest >= 1` or else you will have an error.**

*Note: `iter` doesn't show `spellcap` errors, but works well other than that. I recommend it.*

## Usage

The plugin should be good to go after installation with the provided snippet. It has sensible defaults. Run `:Spellwarn enable`, `:Spellwarn disable`, or `:Spellwarn toggle` to enable/disable/toggle during runtime (though this will *not* override `max_file_size`, `ft_config`, or `ft_default`). You can also add keybindings for any of these (one possible usecase would be disabling by default with the `enable` key of the configuration table and then only enabling when needed). To disable diagnostics on a specific line, add `spellwarn:disable-next-line` to the line immediately above or `spellwarn:disable-line` to a comment at the end of the line. To disable diagnostics in a file, add a comment with `spellwarn:disable` to the *first or second* line of the file.

## Lua API

The Lua API matches the arguments for the `Spellwarn` command:
- `require("spellwarn").disable()` to disable
- `require("spellwarn").enable()` to enable
- `require("spellwarn").toggle()` to toggle

## Contributing

PRs and issues welcome! Please consult `CONTRIBUTING.md` for style guidelines.
31 changes: 23 additions & 8 deletions lua/spellwarn/diagnostics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,41 @@ function M.update_diagnostics(opts, bufnr)
end
end
end

-- Pre-process, if a function is set in opts to do anything.
diags = opts.func_preprocess(diags)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, would check length or nil before calling following.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also pass the bufnr to it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured if they change it from the default and it breaks then that's on them, and it should break so that they get hit with the errors.

Would you silently let if fail if it isn't assigned to a function? Or do some other one-time alert? I'm not sure the best way to go about this other than just let it fall on its face.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not just about avoiding accidental errors; in fact, users may want to filter out all diagnostics entirely.

I'm not sure if vim.diagnostic.set can handle nil or {} correctly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also pass the bufnr to it?

Probably a good idea.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not just about avoiding accidental errors; in fact, users may want to filter out all diagnostics entirely.

I'm not sure if vim.diagnostic.set can handle nil or {} correctly.

I'm at the tail-end of a long day/night... let me know a bit more specifically what you mean, if possible?

Copy link
Copy Markdown

@chaneyzorn chaneyzorn Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me know a bit more specifically what you mean, if possible?

My consideration is to include return nil or zero length (empty list) as part of the contractual agreement for this function interface, to express the intent of "users wanting to silence all diagnostics".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the other PR I've altered it so that nil is converted to an empty table, where an empty table clears the diagnostics.


-- TODO: Add suffix diagnostics with type of spelling error the way that LSP diagnostics do
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just see this here, if suffix present, I won't need different prefix. thx.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow... that's your own code, or from some older PR :P

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized you aren't ravi... my bad. Regardless, that TODO is not a part of anything I've done. You'll need to ask ravibrock about that.

Copy link
Copy Markdown

@chaneyzorn chaneyzorn Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha, Just realized you are not ravibrock too.
Yes, I requested a feat issue about that.

Have a good rest. I would help reviewing some code in PR #22.

vim.diagnostic.set(namespace, bufnr, diags, opts.diagnostic_opts)
end

local function can_update(opts, bufnr)
local winid = vim.api.nvim_get_current_win()
if winid then
if not vim.wo[winid].spell then
vim.diagnostic.reset(namespace, bufnr) -- ensure old are cleared if spell is toggled to off.
return
end
end

local buftype = vim.api.nvim_get_option_value("buftype", { buf = bufnr })
if opts.bt_config[buftype] then
return opts.bt_config[buftype]
end

return opts.bt_default
end

function M.setup(opts)
function M.enable()
vim.api.nvim_create_augroup("Spellwarn", {})
vim.api.nvim_create_autocmd(opts.event, {
group = "Spellwarn",
callback = function()
local winid = vim.api.nvim_get_current_win()
local bufnr = vim.fn.bufnr("%")
if winid then
if not vim.wo[winid].spell then
vim.diagnostic.reset(namespace, bufnr) -- ensure old are cleared if spell is toggled to off.
return
end
if can_update(opts, bufnr) then
M.update_diagnostics(opts, bufnr)
end

M.update_diagnostics(opts, bufnr)
end,
desc = "Update Spellwarn diagnostics",
})
Expand Down
13 changes: 13 additions & 0 deletions lua/spellwarn/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ local defaults = {
"TextChangedI",
"TextChangedP",
},

enable = true, -- enable diagnostics on startup

func_preprocess = function(diag_tbl)
return diag_tbl
end, -- function to do any custom processing of the diagnostics table before passing it to vim.diagnostic.set

bt_config = { -- buffer types to run on
[""] = true,
},
bt_default = false, -- default for types not in bt_config.

ft_config = { -- spellcheck method: "cursor", "iter", or boolean
alpha = false,
help = false,
Expand All @@ -17,7 +28,9 @@ local defaults = {
mason = false,
},
ft_default = true, -- default option for unspecified filetypes

max_file_size = nil, -- maximum file size to check in lines (nil for no limit)

severity = { -- severity for each spelling error type (false to disable diagnostics for that type)
spellbad = "WARN",
spellcap = "HINT",
Expand Down