Skip to content

Commit

Permalink
feat!: rule lookup directly at documentation sites
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisgrieser committed Sep 21, 2023
1 parent f364f37 commit a60690a
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 30 deletions.
47 changes: 32 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
<!-- TODO uncomment shields when available in dotfyle.com -->
<!-- <a href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook"><img src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield" /></a> -->

Add inline-comments to locally disable diagnostic rules.
Add inline-comments to rules. Lookup rule documentation online.

Most LSPs provide code actions for to do that – this plugin adds commands for linters and LSPs that don't. (As such, this plugin is partially a replacement for [null-ls](https://github.com/jose-elias-alvarez/null-ls.nvim/)'s code action feature.)
Some LSPs provide code actions for to do that – this plugin adds commands for linters and LSPs that don't.

<!--toc:start-->
- [Features](#features)
- [Supported Linters for adding ignore-comments](#supported-linters-for-adding-ignore-comments)
- [Supported Sources](#supported-sources)
- [For adding ignore comments](#for-adding-ignore-comments)
- [For looking up rule documentation](#for-looking-up-rule-documentation)
- [Installation](#installation)
- [Configuration](#configuration)
- [Limitations](#limitations)
- [Credits](#credits)
<!--toc:end-->

Expand All @@ -21,9 +24,13 @@ Most LSPs provide code actions for to do that – this plugin adds commands for
- Perform a web search for a diagnostic rule.
- Requires diagnostics provided by a source that supports neovim's builtin diagnostics system (`vim.diagnostic`). nvim's builtin LSP client and [nvim-lint](https://github.com/mfussenegger/nvim-lint) are such sources.

## Supported Linters for adding ignore-comments
<!-- TODO: AUTO-GENERATED this list -->
<!-- supported-linters start -->
## Supported Sources
You easily add a custom source via the [plugin configuration](#configuration). However, please consider making a PR to add support for a linter if it is missing.

[Ignore Rule Data for the supported linters](./lua/rulebook/ignoreRuleData.lua)

### For adding ignore comments
<!-- TODO: auto-generate this list -->
- selene
- shellcheck
- vale
Expand All @@ -32,11 +39,11 @@ Most LSPs provide code actions for to do that – this plugin adds commands for
- LTeX
- typescript
- pylint
<!-- supported-linters end -->

You easily add a custom via the [plugin configuration](#configuration). However, please consider making a PR to add support for a linter if it is missing.

[Ignore Rule Data for the supported linters](./lua/rulebook/ignoreRuleData.lua)
### For looking up rule documentation
- selene
- shellcheck
- pylint

## Installation

Expand Down Expand Up @@ -69,7 +76,8 @@ defaultConfig = {
comment = "-- selene: allow(%s)",
location = "prevLine",
},
-- full list of builtin linters found in README
-- ... (full list of supported sources can be found in the README)

yourCustomSource = {
-- %s will be replaced with rule-id
-- if location is "encloseLine", needs to be a list of two strings
Expand All @@ -80,17 +88,26 @@ defaultConfig = {
}
},

-- searchUrl for rule lookup. Default is the DuckDuckGo
-- "Ducky Search" (automatically opening first result)
searchUrl = "https://duckduckgo.com/?q=%s+%%21ducky&kl=en-us",
-- %s will be replaced with rule-id
ruleDocumentations = {
selene = "https://kampfkarren.github.io/selene/lints/%s.html"
-- ... (full list of supported sources can be found in the README)

yourCustomSource = "https://my-docs/%s.hthml"

-- Search URL when no documentation definition is available for a
-- diagnostic source. "%s" will be replaced with the diagnostic source & code.
-- Default is the DDG "Ducky Search" (automatically opening first result).
fallback = "https://duckduckgo.com/?q=%s+%21ducky&kl=en-us",
}
}
```

> [!NOTE]
> The plugin uses `vim.ui.select()`, so the appearance of the rule selection can be customized by using a ui-plugin like [dressing.nvim](https://github.com/stevearc/dressing.nvim).
## Limitations
- The diagnostics have to contain the necessary data, [that is a diagnostic code and diagnostic source](https://neovim.io/doc/user/diagnostic.html#diagnostic-structure). Most LSPs and most linters configured for `nvim-lint` do that, but some diagnostic sources do not (for example `efm-langserver` with certain linters). Please open an issue at the diagnostics provider to fix.
- The diagnostics have to contain the necessary data, [that is a diagnostic code and diagnostic source](https://neovim.io/doc/user/diagnostic.html#diagnostic-structure). Most LSPs and most linters configured for `nvim-lint` do that, but some diagnostic sources do not (for example `efm-langserver` with incorrectly defined errorformat). Please open an issue at the diagnostics provider to fix.
- This plugin does *not* hook into `vim.lsp.buf.code_action`, but provides its own independent selector.
- As opposed to [null-ls](https://github.com/jose-elias-alvarez/null-ls.nvim)'s code action feature, this plugin does not support arbitrary code actions, but only actions based on a diagnostic.

Expand Down
41 changes: 26 additions & 15 deletions lua/rulebook/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ local fn = vim.fn
-- CONFIG

---@class pluginConfig for this plugin
---@field ignoreRuleComments table<string, ruleIgnoreConfig>
---@field searchUrl string
---@field ignoreComments table<string, ruleIgnoreConfig>
---@field ruleDocs table<string, string>

---@type pluginConfig
local defaultConfig = {
ignoreRuleComments = require("rule-breaker.ignoreRuleData"),
searchUrl = "https://duckduckgo.com/?q=%s+%%21ducky&kl=en-us",
ignoreComments = require("rulebook.rule-data").ignoreComments,
ruleDocs = require("rulebook.rule-data").ruleDocs,
}
local config = defaultConfig -- if user does not call setup, use default
defaultConfig.ruleDocs.fallback = "https://duckduckgo.com/?q=%s+%%21ducky&kl=en-us"

-- if user does not call setup, use default
local config = defaultConfig

---@param userConfig table
function M.setup(userConfig) config = vim.tbl_deep_extend("force", defaultConfig, userConfig) end
Expand All @@ -25,10 +28,11 @@ function M.setup(userConfig) config = vim.tbl_deep_extend("force", defaultConfig
---@param level? "info"|"trace"|"debug"|"warn"|"error"
local function notify(msg, level)
if not level then level = "info" end
local pluginName = "nvim-rule-breaker"
local pluginName = "nvim-rulebook"
vim.notify(msg, vim.log.levels[level:upper()], { title = pluginName })
end

---checks whether rule has id and source, as prescribed in nvim diagnostic structure
---@param diag diagnostic
---@return boolean whether rule is valid
local function validDiagObj(diag)
Expand All @@ -48,11 +52,16 @@ end
---@param diag diagnostic
local function searchForTheRule(diag)
if not validDiagObj(diag) then return end
local query = (diag.code .. " " .. diag.source)
local escapedQuery = query:gsub(" ", "%%20")

fn.setreg("+", query)
local url = config.searchUrl:format(escapedQuery)
-- determine url to open
local docsUrl = config.ruleDocs[diag.source]
local urlToOpen
if docsUrl then
urlToOpen = docsUrl:format(diag.code)
else
local escapedQuery = (diag.code .. " " .. diag.source):gsub(" ", "%%20")
urlToOpen = config.ruleDocs.fallback:format(escapedQuery)
end

-- open with the OS-specific shell command
local opener
Expand All @@ -63,25 +72,27 @@ local function searchForTheRule(diag)
elseif fn.has("win64") == 1 or fn.has("win32") == 1 then
opener = "start"
end
local openCommand = string.format("%s '%s' >/dev/null 2>&1", opener, url)
local openCommand = string.format("%s '%s' >/dev/null 2>&1", opener, urlToOpen)
fn.system(openCommand)
end

---@param diag diagnostic
local function addIgnoreComment(diag)
if not validDiagObj(diag) then return end
local ignoreRuleData = config.ignoreRuleComments
-- INFO no need to check that source has rule-data, since filtered beforehand

local ignoreData = config.ignoreComments

-- add rule id and indentation into comment
local currentIndent = vim.api.nvim_get_current_line():match("^%s*")
local ignoreComment = ignoreRuleData[diag.source].comment
local ignoreComment = ignoreData[diag.source].comment
if type(ignoreComment) == "string" then ignoreComment = { ignoreComment } end
for i = 1, #ignoreComment, 1 do
ignoreComment[i] = currentIndent .. ignoreComment[i]:format(diag.code)
end

-- insert the comment
local ignoreLocation = ignoreRuleData[diag.source].location
local ignoreLocation = ignoreData[diag.source].location
if ignoreLocation == "prevLine" then
local prevLineNum = vim.api.nvim_win_get_cursor(0)[1] - 1
vim.api.nvim_buf_set_lines(0, prevLineNum, prevLineNum, false, ignoreComment)
Expand All @@ -107,7 +118,7 @@ local function selectRuleInCurrentLine(operation)
-- filter diagnostics for which there are no ignore comments defined
if operation == addIgnoreComment then
curLineDiags = vim.tbl_filter(
function(diag) return config.ignoreRuleComments[diag.source] ~= nil end,
function(diag) return config.ignoreComments[diag.source] ~= nil end,
curLineDiags
)
end
Expand Down

0 comments on commit a60690a

Please sign in to comment.