Skip to content

Commit

Permalink
custom telescope pickers
Browse files Browse the repository at this point in the history
  • Loading branch information
gzagatti committed Jun 18, 2024
1 parent d434957 commit 54e97eb
Show file tree
Hide file tree
Showing 2 changed files with 387 additions and 0 deletions.
294 changes: 294 additions & 0 deletions nvim/lua/telescope_pickers/diagnostics.lua
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)
93 changes: 93 additions & 0 deletions nvim/lua/telescope_pickers/init.lua
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

0 comments on commit 54e97eb

Please sign in to comment.