diff --git a/nvim/lua/telescope_pickers/diagnostics.lua b/nvim/lua/telescope_pickers/diagnostics.lua new file mode 100644 index 0000000..efe94a8 --- /dev/null +++ b/nvim/lua/telescope_pickers/diagnostics.lua @@ -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) diff --git a/nvim/lua/telescope_pickers/init.lua b/nvim/lua/telescope_pickers/init.lua new file mode 100644 index 0000000..ba9f051 --- /dev/null +++ b/nvim/lua/telescope_pickers/init.lua @@ -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: +--- - ``: 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