-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
gzagatti
committed
Jun 18, 2024
1 parent
d434957
commit 54e97eb
Showing
2 changed files
with
387 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
-- Custom telescope picker for diagnostics | ||
-- Modified from buil-in picker: https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/telescope/builtin/__diagnostics.lua | ||
local conf = require("telescope.config").values | ||
local finders = require "telescope.finders" | ||
local make_entry = require "telescope.make_entry" | ||
local pickers = require "telescope.pickers" | ||
local utils = require "telescope.utils" | ||
local entry_display = require "telescope.pickers.entry_display" | ||
|
||
local diagnostics = {} | ||
|
||
local sorting_comparator = function(opts) | ||
local current_buf = vim.api.nvim_get_current_buf() | ||
local comparators = { | ||
-- sort results by bufnr (prioritize cur buf), severity, lnum | ||
buffer = function(a, b) | ||
if a.bufnr == b.bufnr then | ||
if a.type == b.type then | ||
return a.lnum < b.lnum | ||
else | ||
return a.type < b.type | ||
end | ||
else | ||
if a.bufnr == current_buf then | ||
return true | ||
end | ||
if b.bufnr == current_buf then | ||
return false | ||
end | ||
return a.bufnr < b.bufnr | ||
end | ||
end, | ||
severity = function(a, b) | ||
if a.type < b.type then | ||
return true | ||
elseif a.type > b.type then | ||
return false | ||
end | ||
|
||
if a.bufnr == b.bufnr then | ||
return a.lnum < b.lnum | ||
elseif a.bufnr == current_buf then | ||
return true | ||
elseif b.bufnr == current_buf then | ||
return false | ||
else | ||
return a.bufnr < b.bufnr | ||
end | ||
end, | ||
} | ||
|
||
local sort_by = vim.F.if_nil(opts.sort_by, "buffer") | ||
return comparators[sort_by] | ||
end | ||
|
||
local convert_diagnostic_type = function(severities, severity) | ||
-- convert from string to int | ||
if type(severity) == "string" then | ||
-- make sure that e.g. error is uppercased to ERROR | ||
return severities[severity:upper()] | ||
end | ||
-- otherwise keep original value, incl. nil | ||
return severity | ||
end | ||
|
||
local diagnostics_to_tbl = function(opts) | ||
opts = vim.F.if_nil(opts, {}) | ||
local items = {} | ||
local severities = vim.diagnostic.severity | ||
|
||
opts.severity = convert_diagnostic_type(severities, opts.severity) | ||
opts.severity_limit = convert_diagnostic_type(severities, opts.severity_limit) | ||
opts.severity_bound = convert_diagnostic_type(severities, opts.severity_bound) | ||
|
||
local diagnosis_opts = { severity = {}, namespace = opts.namespace } | ||
if opts.severity ~= nil then | ||
if opts.severity_limit ~= nil or opts.severity_bound ~= nil then | ||
utils.notify("builtin.diagnostics", { | ||
msg = "Invalid severity parameters. Both a specific severity and a limit/bound is not allowed", | ||
level = "ERROR", | ||
}) | ||
return {} | ||
end | ||
diagnosis_opts.severity = opts.severity | ||
else | ||
if opts.severity_limit ~= nil then | ||
diagnosis_opts.severity["min"] = opts.severity_limit | ||
end | ||
if opts.severity_bound ~= nil then | ||
diagnosis_opts.severity["max"] = opts.severity_bound | ||
end | ||
if vim.version().minor > 9 and vim.tbl_isempty(diagnosis_opts.severity) then | ||
diagnosis_opts.severity = nil | ||
end | ||
end | ||
|
||
opts.root_dir = opts.root_dir == true and vim.loop.cwd() or opts.root_dir | ||
|
||
local bufnr_name_map = {} | ||
local filter_diag = function(diagnostic) | ||
if bufnr_name_map[diagnostic.bufnr] == nil then | ||
bufnr_name_map[diagnostic.bufnr] = vim.api.nvim_buf_get_name(diagnostic.bufnr) | ||
end | ||
|
||
local root_dir_test = not opts.root_dir | ||
or string.sub(bufnr_name_map[diagnostic.bufnr], 1, #opts.root_dir) == opts.root_dir | ||
local listed_test = not opts.no_unlisted or vim.api.nvim_buf_get_option(diagnostic.bufnr, "buflisted") | ||
|
||
return root_dir_test and listed_test | ||
end | ||
|
||
local preprocess_diag = function(diagnostic) | ||
local text = "" | ||
if diagnostic.code ~= nil then | ||
code_text = ("[%s]"):format(vim.trim(diagnostic.code:gsub("[\n]", ""))) | ||
else | ||
code_text = "" | ||
end | ||
if diagnostic.source ~= nil then | ||
diagnostic_text = ("(%s)"):format(vim.trim(diagnostic.source:gsub("[\n]", ""))) | ||
else | ||
diagnostic_text = "" | ||
end | ||
text = ("%s %s %s"):format(code_text, vim.trim(diagnostic.message:gsub("[\n]", "")), diagnostic_text) | ||
return { | ||
bufnr = diagnostic.bufnr, | ||
filename = bufnr_name_map[diagnostic.bufnr], | ||
lnum = diagnostic.lnum + 1, | ||
col = diagnostic.col + 1, | ||
text = text, | ||
type = severities[diagnostic.severity] or severities[1], | ||
} | ||
end | ||
|
||
for _, d in ipairs(vim.diagnostic.get(opts.bufnr, diagnosis_opts)) do | ||
if filter_diag(d) then | ||
table.insert(items, preprocess_diag(d)) | ||
end | ||
end | ||
|
||
table.sort(items, sorting_comparator(opts)) | ||
|
||
return items | ||
end | ||
|
||
function gen_from_diagnostics(opts) | ||
opts = opts or {} | ||
|
||
local type_diagnostic = vim.diagnostic.severity | ||
local signs = (function() | ||
if opts.no_sign then | ||
return | ||
end | ||
local signs = {} | ||
for _, severity in ipairs(type_diagnostic) do | ||
local status, sign = pcall(function() | ||
-- only the first char is upper all others are lowercalse | ||
return vim.trim(vim.fn.sign_getdefined("DiagnosticSign" .. severity:lower():gsub("^%l", string.upper))[1].text) | ||
end) | ||
if not status then | ||
sign = severity:sub(1, 1) | ||
end | ||
signs[severity] = sign | ||
end | ||
return signs | ||
end)() | ||
|
||
local sign_width | ||
if opts.disable_coordinates then | ||
sign_width = signs ~= nil and 2 or 0 | ||
else | ||
sign_width = signs ~= nil and 10 or 8 | ||
end | ||
|
||
local display_items = { | ||
{ width = sign_width }, | ||
{ remaining = true }, | ||
} | ||
local line_width = vim.F.if_nil(opts.line_width, 0.5) | ||
local line_width_opts = { width = line_width } | ||
if type(line_width) == "string" and line_width == "full" then | ||
line_width_opts = {} | ||
end | ||
local hidden = utils.is_path_hidden(opts) | ||
if not hidden then | ||
table.insert(display_items, 2, line_width_opts) | ||
end | ||
local displayer = entry_display.create { | ||
separator = "▏", | ||
items = display_items, | ||
} | ||
|
||
local make_display = function(entry) | ||
local display_path, path_style = utils.transform_path(opts, entry.filename) | ||
|
||
-- add styling of entries | ||
local pos = string.format("%4d:%2d", entry.lnum, entry.col) | ||
local line_info_text = signs and signs[entry.type] .. " " or "" | ||
local line_info = { | ||
opts.disable_coordinates and line_info_text or line_info_text .. pos, | ||
"DiagnosticSign" .. entry.type, | ||
} | ||
|
||
return displayer { | ||
line_info, | ||
entry.text, | ||
{ | ||
display_path, | ||
function() | ||
return path_style | ||
end, | ||
}, | ||
} | ||
end | ||
|
||
local errlist_type_map = { | ||
[type_diagnostic.ERROR] = "E", | ||
[type_diagnostic.WARN] = "W", | ||
[type_diagnostic.INFO] = "I", | ||
[type_diagnostic.HINT] = "N", | ||
} | ||
|
||
return function(entry) | ||
return make_entry.set_default_entry_mt({ | ||
value = entry, | ||
ordinal = ("%s %s"):format(not hidden and entry.filename or "", entry.text), | ||
display = make_display, | ||
filename = entry.filename, | ||
type = entry.type, | ||
lnum = entry.lnum, | ||
col = entry.col, | ||
text = entry.text, | ||
qf_type = errlist_type_map[type_diagnostic[entry.type]], | ||
}, opts) | ||
end | ||
end | ||
|
||
diagnostics.get = function(opts) | ||
if opts.bufnr ~= 0 then | ||
opts.bufnr = nil | ||
end | ||
if type(opts.bufnr) == "string" then | ||
opts.bufnr = tonumber(opts.bufnr) | ||
end | ||
if opts.bufnr ~= nil then | ||
opts.path_display = vim.F.if_nil(opts.path_display, "hidden") | ||
end | ||
|
||
local locations = diagnostics_to_tbl(opts) | ||
|
||
if vim.tbl_isempty(locations) then | ||
utils.notify("builtin.diagnostics", { | ||
msg = "No diagnostics found", | ||
level = "INFO", | ||
}) | ||
return | ||
end | ||
|
||
if type(opts.line_width) == "string" and opts.line_width ~= "full" then | ||
utils.notify("builtin.diagnostics", { | ||
msg = string.format("'%s' is not a valid value for line_width", opts.line_width), | ||
level = "ERROR", | ||
}) | ||
return | ||
end | ||
|
||
pickers | ||
.new(opts, { | ||
prompt_title = opts.bufnr == nil and "Workspace Diagnostics" or "Document Diagnostics", | ||
finder = finders.new_table { | ||
results = locations, | ||
entry_maker = gen_from_diagnostics(opts), | ||
}, | ||
previewer = conf.qflist_previewer(opts), | ||
sorter = conf.prefilter_sorter { | ||
tag = "type", | ||
sorter = conf.generic_sorter(opts), | ||
}, | ||
}) | ||
:find() | ||
end | ||
|
||
local function apply_checks(mod) | ||
for k, v in pairs(mod) do | ||
mod[k] = function(opts) | ||
opts = opts or {} | ||
v(opts) | ||
end | ||
end | ||
|
||
return mod | ||
end | ||
|
||
return apply_checks(diagnostics) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
-- Custom telescope pickers | ||
-- Modified from buil-in picker: https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/telescope/builtin/init.lua | ||
local pickers = {} | ||
|
||
-- Ref: https://github.com/tjdevries/lazy.nvim | ||
local function require_on_exported_call(mod) | ||
return setmetatable({}, { | ||
__index = function(_, picker) | ||
return function(...) | ||
return require(mod)[picker](...) | ||
end | ||
end, | ||
}) | ||
end | ||
|
||
--- Lists diagnostics | ||
--- - Fields: | ||
--- - `All severity flags can be passed as `string` or `number` as per `:vim.diagnostic.severity:` | ||
--- - Default keymaps: | ||
--- - `<C-l>`: show autocompletion menu to prefilter your query with the diagnostic you want to see (i.e. `:warning:`) | ||
--- - sort_by option: | ||
--- - "buffer": order by bufnr (prioritizing current bufnr), severity, lnum | ||
--- - "severity": order by severity, bufnr (prioritizing current bufnr), lnum | ||
---@param opts table: options to pass to the picker | ||
---@field bufnr number|nil: Buffer number to get diagnostics from. Use 0 for current buffer or nil for all buffers | ||
---@field severity string|number: filter diagnostics by severity name (string) or id (number) | ||
---@field severity_limit string|number: keep diagnostics equal or more severe wrt severity name (string) or id (number) | ||
---@field severity_bound string|number: keep diagnostics equal or less severe wrt severity name (string) or id (number) | ||
---@field root_dir string|boolean: if set to string, get diagnostics only for buffers under this dir otherwise cwd | ||
---@field no_unlisted boolean: if true, get diagnostics only for listed buffers | ||
---@field no_sign boolean: hide DiagnosticSigns from Results (default: false) | ||
---@field line_width string|number: set length of diagnostic entry text in Results. Use 'full' for full untruncated text | ||
---@field namespace number: limit your diagnostics to a specific namespace | ||
---@field disable_coordinates boolean: don't show the line & row numbers (default: false) | ||
---@field sort_by string: sort order of the diagnostics results; see above notes (default: "buffer") | ||
-- pickers.diagnostics = require_on_exported_call("diagnostics").get | ||
-- local diagnostics = require"telescope_pickers.diagnostics" | ||
-- pickers.diagnostics = diagnostics.get | ||
pickers.diagnostics = require_on_exported_call("telescope_pickers.diagnostics").get | ||
|
||
local apply_config = function(mod) | ||
for k, v in pairs(mod) do | ||
mod[k] = function(opts) | ||
local pickers_conf = require("telescope.config").pickers | ||
|
||
opts = opts or {} | ||
opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf() | ||
opts.winnr = opts.winnr or vim.api.nvim_get_current_win() | ||
local pconf = pickers_conf[k] or {} | ||
local defaults = (function() | ||
if pconf.theme then | ||
return require("telescope.themes")["get_" .. pconf.theme](pconf) | ||
end | ||
return vim.deepcopy(pconf) | ||
end)() | ||
|
||
if pconf.mappings then | ||
defaults.attach_mappings = function(_, map) | ||
for mode, tbl in pairs(pconf.mappings) do | ||
for key, action in pairs(tbl) do | ||
map(mode, key, action) | ||
end | ||
end | ||
return true | ||
end | ||
end | ||
|
||
if pconf.attach_mappings and opts.attach_mappings then | ||
local opts_attach = opts.attach_mappings | ||
opts.attach_mappings = function(prompt_bufnr, map) | ||
pconf.attach_mappings(prompt_bufnr, map) | ||
return opts_attach(prompt_bufnr, map) | ||
end | ||
end | ||
|
||
if defaults.attach_mappings and opts.attach_mappings then | ||
local opts_attach = opts.attach_mappings | ||
opts.attach_mappings = function(prompt_bufnr, map) | ||
defaults.attach_mappings(prompt_bufnr, map) | ||
return opts_attach(prompt_bufnr, map) | ||
end | ||
end | ||
|
||
v(vim.tbl_extend("force", defaults, opts)) | ||
end | ||
end | ||
|
||
return mod | ||
end | ||
|
||
pickers = apply_config(pickers) | ||
|
||
return pickers |