-
Notifications
You must be signed in to change notification settings - Fork 170
/
Copy pathdiagnostic.lua
255 lines (231 loc) · 8.89 KB
/
diagnostic.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
local uv = vim.uv or vim.loop
local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local make_entry = require "fzf-lua.make_entry"
local M = {}
local convert_diagnostic_type = function(severity)
-- convert from string to int
if type(severity) == "string" and not tonumber(severity) then
-- make sure that e.g. error is uppercased to Error
return vim.diagnostic and vim.diagnostic.severity[severity:upper()] or
vim.lsp.protocol.DiagnosticSeverity[severity:gsub("^%l", string.upper)]
else
-- otherwise keep original value, incl. nil
return tonumber(severity)
end
end
local filter_diag_severity = function(opts, severity)
if opts.severity_only ~= nil then
return tonumber(opts.severity_only) == severity
elseif opts.severity_limit ~= nil then
return severity <= tonumber(opts.severity_limit)
elseif opts.severity_bound ~= nil then
return severity >= tonumber(opts.severity_bound)
else
return true
end
end
M.diagnostics = function(opts)
opts = config.normalize_opts(opts, "diagnostics")
if not opts then return end
-- required for relative paths presentation
if not opts.cwd or #opts.cwd == 0 then
opts.cwd = uv.cwd()
else
opts.cwd_only = true
end
if not vim.diagnostic then
---@diagnostic disable-next-line: deprecated
local lsp_clients = vim.lsp.buf_get_clients(0)
if utils.tbl_isempty(lsp_clients) then
utils.info("LSP: no client attached")
return
end
end
-- configure signs and highlights
local signs = vim.diagnostic and {
["Error"] = { severity = 1, default = "E", name = "DiagnosticSignError" },
["Warn"] = { severity = 2, default = "W", name = "DiagnosticSignWarn" },
["Info"] = { severity = 3, default = "I", name = "DiagnosticSignInfo" },
["Hint"] = { severity = 4, default = "H", name = "DiagnosticSignHint" },
} or {
-- At one point or another, we'll drop support for the old LSP diag
["Error"] = { severity = 1, default = "E", name = "LspDiagnosticsSignError" },
["Warn"] = { severity = 2, default = "W", name = "LspDiagnosticsSignWarning" },
["Info"] = { severity = 3, default = "I", name = "LspDiagnosticsSignInformation" },
["Hint"] = { severity = 4, default = "H", name = "LspDiagnosticsSignHint" },
}
opts.__signs = {}
for k, v in pairs(signs) do
opts.__signs[v.severity] = {}
-- from vim.diagnostic
if utils.__HAS_NVIM_010 then
local sign_confs = vim.diagnostic.config().signs
local level = vim.diagnostic.severity[k:upper()]
if type(sign_confs) ~= "table" or utils.tbl_isempty(sign_confs) then sign_confs = nil end
opts.__signs[v.severity].text =
(not opts.diag_icons or not sign_confs or not sign_confs.text or not sign_confs.text[level])
and v.default or vim.trim(sign_confs.text[level])
opts.__signs[v.severity].texthl = v.name
else
local sign_def = vim.fn.sign_getdefined(v.name)
-- can be empty when config set to (#480):
-- vim.diagnostic.config({ signs = false })
if utils.tbl_isempty(sign_def) then sign_def = nil end
opts.__signs[v.severity].text =
(not opts.diag_icons or not sign_def or not sign_def[1].text)
and v.default or vim.trim(sign_def[1].text)
opts.__signs[v.severity].texthl = sign_def and sign_def[1].texthl or nil
end
-- from user config
if opts.signs and opts.signs[k] and opts.signs[k].text then
opts.__signs[v.severity].text = opts.signs[k].text
end
if opts.signs and opts.signs[k] and opts.signs[k].texthl then
opts.__signs[v.severity].texthl = opts.signs[k].texthl
end
end
-- hint = 4
-- information = 3
-- warning = 2
-- error = 1
-- severity_only: keep any matching exact severity
-- severity_limit: keep any equal or more severe (lower)
-- severity_bound: keep any equal or less severe (higher)
opts.severity_only = convert_diagnostic_type(opts.severity_only)
opts.severity_limit = convert_diagnostic_type(opts.severity_limit)
opts.severity_bound = convert_diagnostic_type(opts.severity_bound)
local diag_opts = { severity = {}, namespace = opts.namespace }
if opts.severity_only ~= nil then
if opts.severity_limit ~= nil or opts.severity_bound ~= nil then
utils.warn("Invalid severity parameters." ..
" Both a specific severity and a limit/bound is not allowed")
return {}
end
diag_opts.severity = opts.severity_only
else
diag_opts.severity["min"] = opts.severity_limit or 4
diag_opts.severity["max"] = opts.severity_bound or 1
end
local curbuf = vim.api.nvim_get_current_buf()
local diag_results = vim.diagnostic and
vim.diagnostic.get(not opts.diag_all and curbuf or nil, diag_opts) or
opts.diag_all and vim.lsp.diagnostic.get_all() or
{ [curbuf] = vim.lsp.diagnostic.get(curbuf, opts.client_id) }
if opts.sort then
if opts.sort == 2 or opts.sort == "2" then
-- ascending: hint, info, warn, error
table.sort(diag_results, function(a, b) return a.severity > b.severity end)
else
-- descending: error, warn, info, hint
table.sort(diag_results, function(a, b) return a.severity < b.severity end)
end
end
local has_diags = false
if vim.diagnostic then
-- format: { <diag array> }
has_diags = not utils.tbl_isempty(diag_results)
else
-- format: { [bufnr] = <diag array>, ... }
for _, diags in pairs(diag_results) do
if #diags > 0 then has_diags = true end
end
end
if not has_diags then
utils.info(string.format("No %s found", "diagnostics"))
return
end
local preprocess_diag = function(diag, bufnr)
bufnr = bufnr or diag.bufnr
if not vim.api.nvim_buf_is_valid(bufnr) then
return nil
end
local filename = vim.api.nvim_buf_get_name(bufnr)
-- pre vim.diagnostic (vim.lsp.diagnostic)
-- has 'start|finish' instead of 'end_col|end_lnum'
local start = diag.range and diag.range["start"]
-- local finish = diag.range and diag.range['end']
local row = diag.lnum or start.line
local col = diag.col or start.character
local buffer_diag = {
bufnr = bufnr,
filename = filename,
lnum = row + 1,
col = col + 1,
text = vim.trim(opts.multiline and diag.message or diag.message:match("^[^\n]+")),
type = diag.severity or 1
}
return buffer_diag
end
local contents = function(fzf_cb)
coroutine.wrap(function()
local co = coroutine.running()
local function process_diagnostics(diags, bufnr)
for _, diag in ipairs(diags) do
-- workspace diagnostics may include
-- empty tables for unused buffers
if not utils.tbl_isempty(diag) and filter_diag_severity(opts, diag.severity) then
-- wrap with 'vim.schedule' or calls to vim.{fn|api} fail:
-- E5560: vimL function must not be called in a lua loop callback
vim.schedule(function()
local diag_entry = preprocess_diag(diag, bufnr)
if diag_entry == nil then
coroutine.resume(co)
return
end
local entry = make_entry.lcol(diag_entry, opts)
entry = make_entry.file(entry, opts)
if not entry then
-- entry to be skipped (e.g. 'cwd_only')
coroutine.resume(co)
else
local icon = nil
if opts.__signs[diag.severity] then
local sign_def = opts.__signs[diag.severity]
icon = sign_def.text
if opts.color_icons then
icon = utils.ansi_from_hl(sign_def.texthl, icon)
end
end
entry = string.format("%s%s%s",
icon and string.format("%s%s%s", icon, opts.icon_padding or "", utils.nbsp)
or "",
opts.diag_source and string.format(
"%s%s%s%s",
"[", --utils.ansi_codes.bold("["),
diag.source,
"]", --utils.ansi_codes.bold("]"),
utils.nbsp)
or "",
entry)
fzf_cb(entry, function() coroutine.resume(co) end)
end
end)
-- wait here for 'vim.schedule' to return
coroutine.yield()
end
end
end
if vim.diagnostic then
process_diagnostics(diag_results)
else
for bufnr, diags in pairs(diag_results) do
process_diagnostics(diags, bufnr)
end
end
-- close the pipe to fzf, this
-- removes the loading indicator
fzf_cb(nil)
end)()
end
opts = core.set_header(opts, opts.headers or { "cwd" })
opts = core.set_fzf_field_index(opts)
return core.fzf_exec(contents, opts)
end
M.all = function(opts)
if not opts then opts = {} end
opts.diag_all = true
return M.diagnostics(opts)
end
return M