-
Notifications
You must be signed in to change notification settings - Fork 21
/
diagnostics.lua
305 lines (259 loc) · 8.08 KB
/
diagnostics.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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
-- Store diagnostic messages received by an LSP.
-- @copyright Jefferson Gonzalez
-- @license MIT
local core = require "core"
local config = require "core.config"
local util = require "plugins.lsp.util"
local Timer = require "plugins.lsp.timer"
---@class lsp.diagnostics
local diagnostics = {}
---@class lsp.diagnostics.position
---@field line integer
---@field character integer
---@class lsp.diagnostics.range
---@field start lsp.diagnostics.position
---@field end lsp.diagnostics.position
---@class lsp.diagnostics.severity
---@field ERROR integer
---@field WARNING integer
---@field INFO integer
---@field HINT integer
diagnostics.severity = {
ERROR = 1,
WARNING = 2,
INFO = 3,
HINT = 4
}
---@alias lsp.diagnostics.severity_code
---|>`diagnostics.severity.ERROR`
---| `diagnostics.severity.WARNING`
---| `diagnostics.severity.INFO`
---| `diagnostics.severity.HINT`
---@class lsp.diagnostics.code_description
---@field href string
---@class lsp.diagnostics.tag
---@field UNNECESSARY integer
---@field DEPRECATED integer
diagnostics.tag = {
UNNECESSARY = 1,
DEPRECATED = 2
}
---@alias lsp.diagnostics.tag_code
---|>`diagnostics.tag.UNNECESSARY`
---| `diagnostics.tag.DEPRECATED`
---@class lsp.diagnostics.location
---@field uri string
---@field range lsp.diagnostics.range
---@class lsp.diagnostics.related_information
---@field location lsp.diagnostics.location
---@field message string
---A diagnostic message.
---@class lsp.diagnostics.message
---@field filename string
---@field range lsp.diagnostics.position
---@field severity lsp.diagnostics.severity_code | integer
---@field code integer | string
---@field codeDescription lsp.diagnostics.code_description
---@field source string
---@field message string
---@field tags lsp.diagnostics.tag_code[]
---@field relatedInformation lsp.diagnostics.related_information
---A diagnostic item.
---@class lsp.diagnostics.item
---@field filename string
---@field messages lsp.diagnostics.message[]
---@type table<integer, lsp.diagnostics.item>
diagnostics.list = {}
---@type integer
diagnostics.count = 0
-- Try to load lintplus plugin if available for diagnostics rendering
local lintplus_found, lintplus = nil, nil
if config.plugins.lintplus ~= false then
lintplus_found, lintplus = pcall(require, "plugins.lintplus")
end
local lintplus_kinds = { "error", "warning", "info", "hint" }
---List of linplus coroutines to delay messages population
---@type table<string,lsp.timer>
local lintplus_delays = {}
---Used to set proper diagnostic type on lintplus
---@type table<integer, string>
diagnostics.lintplus_kinds = lintplus_kinds
---@type boolean
diagnostics.lintplus_found = lintplus_found
---@param a lsp.diagnostics.message
---@param b lsp.diagnostics.message
local function sort_helper(a, b)
return a.severity < b.severity
end
---Helper to catch some trange occurances where nil is given as filename
---@param filename string|nil
---@return string | nil
local function get_absolute_path(filename)
if not filename then
core.error(
"[LSP Diagnostics]: nil filename given",
tostring(filename)
)
return nil
end
return core.project_absolute_path(filename)
end
---Get the position of diagnostics associated to a file.
---@param filename string
---@return integer | nil
function diagnostics.get_index(filename)
---@cast filename +nil
filename = get_absolute_path(filename)
if not filename then return nil end
for index, diagnostic in ipairs(diagnostics.list) do
if diagnostic.filename == filename then
return index
end
end
return nil
end
---Get the diagnostics associated to a file.
---@param filename string
---@param severity? lsp.diagnostics.severity_code | integer
---@return lsp.diagnostics.message[] | nil
function diagnostics.get(filename, severity)
---@cast filename +nil
filename = get_absolute_path(filename)
if not filename then return nil end
for _, diagnostic in ipairs(diagnostics.list) do
if diagnostic.filename == filename then
if not severity then return diagnostic.messages end
local results = {}
for _, message in ipairs(diagnostic.messages) do
if message.severity == severity then table.insert(results, message) end
end
return #results > 0 and results or nil
end
end
return nil
end
---Adds a new list of diagnostics associated to a file replacing previous one.
---@param filename string
---@param messages lsp.diagnostics.message[]
---@return boolean
function diagnostics.add(filename, messages)
local index = diagnostics.get_index(filename)
---@cast filename +nil
filename = get_absolute_path(filename)
if not filename then return false end
table.sort(messages, sort_helper)
if not index then
diagnostics.count = diagnostics.count + 1
table.insert(diagnostics.list, {
filename = filename, messages = messages
})
else
diagnostics.list[index].messages = messages
end
return true
end
---Removes all diagnostics associated to a file.
---@param filename string
function diagnostics.clear(filename)
local index = diagnostics.get_index(filename)
if index then
table.remove(diagnostics.list, index)
diagnostics.count = diagnostics.count - 1
end
end
---Get the amount of diagnostics associated to a file.
---@param filename string
---@param severity? lsp.diagnostics.severity_code | integer
function diagnostics.get_messages_count(filename, severity)
local index = diagnostics.get_index(filename)
if not index then return 0 end
if not severity then return #diagnostics.list[index].messages end
local count = 0
for _, message in ipairs(diagnostics.list[index].messages) do
if message.severity == severity then count = count + 1 end
end
return count
end
---@param doc core.doc
function diagnostics.lintplus_init_doc(doc)
if lintplus_found then
lintplus.init_doc(doc.filename, doc)
end
end
---Remove registered diagnostics from lintplus for the given file or for
---all files if no filename is given.
---@param filename? string
---@param force boolean
function diagnostics.lintplus_clear_messages(filename, force)
if lintplus_found then
if
not force and lintplus_delays[filename]
and
lintplus_delays[filename]:running()
then
return
end
if filename then
lintplus.clear_messages(filename)
else
for fname, _ in pairs(lintplus.messages) do
if lintplus_delays[fname] then
lintplus_delays[fname]:stop()
lintplus_delays[fname] = nil
end
lintplus.clear_messages(fname)
end
end
end
end
---@param filename string
function diagnostics.lintplus_populate(filename)
if lintplus_found then
diagnostics.lintplus_clear_messages(filename, true)
if not filename then
for _, diagnostic in ipairs(diagnostics.list) do
local fname = core.normalize_to_project_dir(diagnostic.filename)
for _, message in pairs(diagnostic.messages) do
local line, col = util.toselection(message.range)
local text = message.message
local kind = lintplus_kinds[message.severity]
lintplus.add_message(fname, line, col, kind, text)
end
end
else
local messages = diagnostics.get(filename)
if messages then
for _, message in pairs(messages) do
local line, col = util.toselection(message.range)
local text = message.message
local kind = lintplus_kinds[message.severity]
lintplus.add_message(
core.normalize_to_project_dir(filename),
line, col, kind, text
)
end
end
end
end
end
---@param filename string
---@param user_typed boolean
function diagnostics.lintplus_populate_delayed(filename)
if lintplus_found then
if not lintplus_delays[filename] then
lintplus_delays[filename] = Timer(
config.plugins.lsp.diagnostics_delay or 500,
true
)
lintplus_delays[filename].on_timer = function()
diagnostics.lintplus_populate(filename)
lintplus_delays[filename] = nil
end
lintplus_delays[filename]:start()
else
lintplus_delays[filename]:reset()
lintplus_delays[filename]:start()
end
end
end
return diagnostics