diagnostic.lua 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319
  1. local api, if_nil = vim.api, vim.F.if_nil
  2. local M = {}
  3. --- @param title string
  4. --- @return integer?
  5. local function get_qf_id_for_title(title)
  6. local lastqflist = vim.fn.getqflist({ nr = '$' })
  7. for i = 1, lastqflist.nr do
  8. local qflist = vim.fn.getqflist({ nr = i, id = 0, title = 0 })
  9. if qflist.title == title then
  10. return qflist.id
  11. end
  12. end
  13. return nil
  14. end
  15. --- [diagnostic-structure]()
  16. ---
  17. --- Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based
  18. --- rows and columns). |api-indexing|
  19. --- @class vim.Diagnostic
  20. ---
  21. --- Buffer number
  22. --- @field bufnr? integer
  23. ---
  24. --- The starting line of the diagnostic (0-indexed)
  25. --- @field lnum integer
  26. ---
  27. --- The final line of the diagnostic (0-indexed)
  28. --- @field end_lnum? integer
  29. ---
  30. --- The starting column of the diagnostic (0-indexed)
  31. --- @field col integer
  32. ---
  33. --- The final column of the diagnostic (0-indexed)
  34. --- @field end_col? integer
  35. ---
  36. --- The severity of the diagnostic |vim.diagnostic.severity|
  37. --- @field severity? vim.diagnostic.Severity
  38. ---
  39. --- The diagnostic text
  40. --- @field message string
  41. ---
  42. --- The source of the diagnostic
  43. --- @field source? string
  44. ---
  45. --- The diagnostic code
  46. --- @field code? string|integer
  47. ---
  48. --- @field _tags? { deprecated: boolean, unnecessary: boolean}
  49. ---
  50. --- Arbitrary data plugins or users can add
  51. --- @field user_data? any arbitrary data plugins can add
  52. ---
  53. --- @field namespace? integer
  54. --- Many of the configuration options below accept one of the following:
  55. --- - `false`: Disable this feature
  56. --- - `true`: Enable this feature, use default settings.
  57. --- - `table`: Enable this feature with overrides. Use an empty table to use default values.
  58. --- - `function`: Function with signature (namespace, bufnr) that returns any of the above.
  59. --- @class vim.diagnostic.Opts
  60. ---
  61. --- Use underline for diagnostics.
  62. --- (default: `true`)
  63. --- @field underline? boolean|vim.diagnostic.Opts.Underline|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Underline
  64. ---
  65. --- Use virtual text for diagnostics. If multiple diagnostics are set for a
  66. --- namespace, one prefix per diagnostic + the last diagnostic message are
  67. --- shown.
  68. --- (default: `true`)
  69. --- @field virtual_text? boolean|vim.diagnostic.Opts.VirtualText|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualText
  70. ---
  71. --- Use signs for diagnostics |diagnostic-signs|.
  72. --- (default: `true`)
  73. --- @field signs? boolean|vim.diagnostic.Opts.Signs|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Signs
  74. ---
  75. --- Options for floating windows. See |vim.diagnostic.Opts.Float|.
  76. --- @field float? boolean|vim.diagnostic.Opts.Float|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Float
  77. ---
  78. --- Update diagnostics in Insert mode
  79. --- (if `false`, diagnostics are updated on |InsertLeave|)
  80. --- (default: `false`)
  81. --- @field update_in_insert? boolean
  82. ---
  83. --- Sort diagnostics by severity. This affects the order in which signs,
  84. --- virtual text, and highlights are displayed. When true, higher severities are
  85. --- displayed before lower severities (e.g. ERROR is displayed before WARN).
  86. --- Options:
  87. --- - {reverse}? (boolean) Reverse sort order
  88. --- (default: `false`)
  89. --- @field severity_sort? boolean|{reverse?:boolean}
  90. ---
  91. --- Default values for |vim.diagnostic.jump()|. See |vim.diagnostic.Opts.Jump|.
  92. --- @field jump? vim.diagnostic.Opts.Jump
  93. --- @class (private) vim.diagnostic.OptsResolved
  94. --- @field float vim.diagnostic.Opts.Float
  95. --- @field update_in_insert boolean
  96. --- @field underline vim.diagnostic.Opts.Underline
  97. --- @field virtual_text vim.diagnostic.Opts.VirtualText
  98. --- @field signs vim.diagnostic.Opts.Signs
  99. --- @field severity_sort {reverse?:boolean}
  100. --- @class vim.diagnostic.Opts.Float
  101. ---
  102. --- Buffer number to show diagnostics from.
  103. --- (default: current buffer)
  104. --- @field bufnr? integer
  105. ---
  106. --- Limit diagnostics to the given namespace
  107. --- @field namespace? integer
  108. ---
  109. --- Show diagnostics from the whole buffer (`buffer"`, the current cursor line
  110. --- (`line`), or the current cursor position (`cursor`). Shorthand versions
  111. --- are also accepted (`c` for `cursor`, `l` for `line`, `b` for `buffer`).
  112. --- (default: `line`)
  113. --- @field scope? 'line'|'buffer'|'cursor'|'c'|'l'|'b'
  114. ---
  115. --- If {scope} is "line" or "cursor", use this position rather than the cursor
  116. --- position. If a number, interpreted as a line number; otherwise, a
  117. --- (row, col) tuple.
  118. --- @field pos? integer|[integer,integer]
  119. ---
  120. --- Sort diagnostics by severity.
  121. --- Overrides the setting from |vim.diagnostic.config()|.
  122. --- (default: `false`)
  123. --- @field severity_sort? boolean|{reverse?:boolean}
  124. ---
  125. --- See |diagnostic-severity|.
  126. --- Overrides the setting from |vim.diagnostic.config()|.
  127. --- @field severity? vim.diagnostic.SeverityFilter
  128. ---
  129. --- String to use as the header for the floating window. If a table, it is
  130. --- interpreted as a `[text, hl_group]` tuple.
  131. --- Overrides the setting from |vim.diagnostic.config()|.
  132. --- @field header? string|[string,any]
  133. ---
  134. --- Include the diagnostic source in the message.
  135. --- Use "if_many" to only show sources if there is more than one source of
  136. --- diagnostics in the buffer. Otherwise, any truthy value means to always show
  137. --- the diagnostic source.
  138. --- Overrides the setting from |vim.diagnostic.config()|.
  139. --- @field source? boolean|'if_many'
  140. ---
  141. --- A function that takes a diagnostic as input and returns a string.
  142. --- The return value is the text used to display the diagnostic.
  143. --- Overrides the setting from |vim.diagnostic.config()|.
  144. --- @field format? fun(diagnostic:vim.Diagnostic): string
  145. ---
  146. --- Prefix each diagnostic in the floating window:
  147. --- - If a `function`, {i} is the index of the diagnostic being evaluated and
  148. --- {total} is the total number of diagnostics displayed in the window. The
  149. --- function should return a `string` which is prepended to each diagnostic
  150. --- in the window as well as an (optional) highlight group which will be
  151. --- used to highlight the prefix.
  152. --- - If a `table`, it is interpreted as a `[text, hl_group]` tuple as
  153. --- in |nvim_echo()|
  154. --- - If a `string`, it is prepended to each diagnostic in the window with no
  155. --- highlight.
  156. --- Overrides the setting from |vim.diagnostic.config()|.
  157. --- @field prefix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string)
  158. ---
  159. --- Same as {prefix}, but appends the text to the diagnostic instead of
  160. --- prepending it.
  161. --- Overrides the setting from |vim.diagnostic.config()|.
  162. --- @field suffix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string)
  163. ---
  164. --- @field focus_id? string
  165. ---
  166. --- @field border? string see |nvim_open_win()|.
  167. --- @class vim.diagnostic.Opts.Underline
  168. ---
  169. --- Only underline diagnostics matching the given
  170. --- severity |diagnostic-severity|.
  171. --- @field severity? vim.diagnostic.SeverityFilter
  172. --- @class vim.diagnostic.Opts.VirtualText
  173. ---
  174. --- Only show virtual text for diagnostics matching the given
  175. --- severity |diagnostic-severity|
  176. --- @field severity? vim.diagnostic.SeverityFilter
  177. ---
  178. --- Include the diagnostic source in virtual text. Use `'if_many'` to only
  179. --- show sources if there is more than one diagnostic source in the buffer.
  180. --- Otherwise, any truthy value means to always show the diagnostic source.
  181. --- @field source? boolean|"if_many"
  182. ---
  183. --- Amount of empty spaces inserted at the beginning of the virtual text.
  184. --- @field spacing? integer
  185. ---
  186. --- Prepend diagnostic message with prefix. If a `function`, {i} is the index
  187. --- of the diagnostic being evaluated, and {total} is the total number of
  188. --- diagnostics for the line. This can be used to render diagnostic symbols
  189. --- or error codes.
  190. --- @field prefix? string|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string)
  191. ---
  192. --- Append diagnostic message with suffix.
  193. --- This can be used to render an LSP diagnostic error code.
  194. --- @field suffix? string|(fun(diagnostic:vim.Diagnostic): string)
  195. ---
  196. --- The return value is the text used to display the diagnostic. Example:
  197. --- ```lua
  198. --- function(diagnostic)
  199. --- if diagnostic.severity == vim.diagnostic.severity.ERROR then
  200. --- return string.format("E: %s", diagnostic.message)
  201. --- end
  202. --- return diagnostic.message
  203. --- end
  204. --- ```
  205. --- @field format? fun(diagnostic:vim.Diagnostic): string
  206. ---
  207. --- See |nvim_buf_set_extmark()|.
  208. --- @field hl_mode? 'replace'|'combine'|'blend'
  209. ---
  210. --- See |nvim_buf_set_extmark()|.
  211. --- @field virt_text? [string,any][]
  212. ---
  213. --- See |nvim_buf_set_extmark()|.
  214. --- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline'
  215. ---
  216. --- See |nvim_buf_set_extmark()|.
  217. --- @field virt_text_win_col? integer
  218. ---
  219. --- See |nvim_buf_set_extmark()|.
  220. --- @field virt_text_hide? boolean
  221. --- @class vim.diagnostic.Opts.Signs
  222. ---
  223. --- Only show virtual text for diagnostics matching the given
  224. --- severity |diagnostic-severity|
  225. --- @field severity? vim.diagnostic.SeverityFilter
  226. ---
  227. --- Base priority to use for signs. When {severity_sort} is used, the priority
  228. --- of a sign is adjusted based on its severity.
  229. --- Otherwise, all signs use the same priority.
  230. --- (default: `10`)
  231. --- @field priority? integer
  232. ---
  233. --- A table mapping |diagnostic-severity| to the sign text to display in the
  234. --- sign column. The default is to use `"E"`, `"W"`, `"I"`, and `"H"` for errors,
  235. --- warnings, information, and hints, respectively. Example:
  236. --- ```lua
  237. --- vim.diagnostic.config({
  238. --- signs = { text = { [vim.diagnostic.severity.ERROR] = 'E', ... } }
  239. --- })
  240. --- ```
  241. --- @field text? table<vim.diagnostic.Severity,string>
  242. ---
  243. --- A table mapping |diagnostic-severity| to the highlight group used for the
  244. --- line number where the sign is placed.
  245. --- @field numhl? table<vim.diagnostic.Severity,string>
  246. ---
  247. --- A table mapping |diagnostic-severity| to the highlight group used for the
  248. --- whole line the sign is placed in.
  249. --- @field linehl? table<vim.diagnostic.Severity,string>
  250. --- @class vim.diagnostic.Opts.Jump
  251. ---
  252. --- Default value of the {float} parameter of |vim.diagnostic.jump()|.
  253. --- (default: false)
  254. --- @field float? boolean|vim.diagnostic.Opts.Float
  255. ---
  256. --- Default value of the {wrap} parameter of |vim.diagnostic.jump()|.
  257. --- (default: true)
  258. --- @field wrap? boolean
  259. ---
  260. --- Default value of the {severity} parameter of |vim.diagnostic.jump()|.
  261. --- @field severity? vim.diagnostic.SeverityFilter
  262. ---
  263. --- Default value of the {_highest} parameter of |vim.diagnostic.jump()|.
  264. --- @field package _highest? boolean
  265. -- TODO: inherit from `vim.diagnostic.Opts`, implement its fields.
  266. --- Optional filters |kwargs|, or `nil` for all.
  267. --- @class vim.diagnostic.Filter
  268. --- @inlinedoc
  269. ---
  270. --- Diagnostic namespace, or `nil` for all.
  271. --- @field ns_id? integer
  272. ---
  273. --- Buffer number, or 0 for current buffer, or `nil` for all buffers.
  274. --- @field bufnr? integer
  275. --- @nodoc
  276. --- @enum vim.diagnostic.Severity
  277. M.severity = {
  278. ERROR = 1,
  279. WARN = 2,
  280. INFO = 3,
  281. HINT = 4,
  282. [1] = 'ERROR',
  283. [2] = 'WARN',
  284. [3] = 'INFO',
  285. [4] = 'HINT',
  286. --- Mappings from qflist/loclist error types to severities
  287. E = 1,
  288. W = 2,
  289. I = 3,
  290. N = 4,
  291. }
  292. --- @alias vim.diagnostic.SeverityInt 1|2|3|4
  293. --- See |diagnostic-severity| and |vim.diagnostic.get()|
  294. --- @alias vim.diagnostic.SeverityFilter vim.diagnostic.Severity|vim.diagnostic.Severity[]|{min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
  295. --- @type vim.diagnostic.Opts
  296. local global_diagnostic_options = {
  297. signs = true,
  298. underline = true,
  299. virtual_text = true,
  300. float = true,
  301. update_in_insert = false,
  302. severity_sort = false,
  303. jump = {
  304. -- Do not show floating window
  305. float = false,
  306. -- Wrap around buffer
  307. wrap = true,
  308. },
  309. }
  310. --- @class (private) vim.diagnostic.Handler
  311. --- @field show? fun(namespace: integer, bufnr: integer, diagnostics: vim.Diagnostic[], opts?: vim.diagnostic.OptsResolved)
  312. --- @field hide? fun(namespace:integer, bufnr:integer)
  313. --- @nodoc
  314. --- @type table<string,vim.diagnostic.Handler>
  315. M.handlers = setmetatable({}, {
  316. __newindex = function(t, name, handler)
  317. vim.validate('handler', handler, 'table')
  318. rawset(t, name, handler)
  319. if global_diagnostic_options[name] == nil then
  320. global_diagnostic_options[name] = true
  321. end
  322. end,
  323. })
  324. -- Metatable that automatically creates an empty table when assigning to a missing key
  325. local bufnr_and_namespace_cacher_mt = {
  326. --- @param t table<integer,table>
  327. --- @param bufnr integer
  328. --- @return table
  329. __index = function(t, bufnr)
  330. assert(bufnr > 0, 'Invalid buffer number')
  331. t[bufnr] = {}
  332. return t[bufnr]
  333. end,
  334. }
  335. -- bufnr -> ns -> Diagnostic[]
  336. local diagnostic_cache = {} --- @type table<integer,table<integer,vim.Diagnostic[]>>
  337. do
  338. local group = api.nvim_create_augroup('DiagnosticBufWipeout', {})
  339. setmetatable(diagnostic_cache, {
  340. --- @param t table<integer,vim.Diagnostic[]>
  341. --- @param bufnr integer
  342. __index = function(t, bufnr)
  343. assert(bufnr > 0, 'Invalid buffer number')
  344. api.nvim_create_autocmd('BufWipeout', {
  345. group = group,
  346. buffer = bufnr,
  347. callback = function()
  348. rawset(t, bufnr, nil)
  349. end,
  350. })
  351. t[bufnr] = {}
  352. return t[bufnr]
  353. end,
  354. })
  355. end
  356. --- @class (private) vim.diagnostic._extmark
  357. --- @field [1] integer id
  358. --- @field [2] integer start
  359. --- @field [3] integer end
  360. --- @field [4] table details
  361. --- @type table<integer,table<integer,vim.diagnostic._extmark[]>>
  362. local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt)
  363. --- @type table<integer,true>
  364. local diagnostic_attached_buffers = {}
  365. --- @type table<integer,true|table<integer,true>>
  366. local diagnostic_disabled = {}
  367. --- @type table<integer,table<integer,table>>
  368. local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt)
  369. --- @class vim.diagnostic.NS
  370. --- @field name string
  371. --- @field opts vim.diagnostic.Opts
  372. --- @field user_data table
  373. --- @field disabled? boolean
  374. --- @type table<integer,vim.diagnostic.NS>
  375. local all_namespaces = {}
  376. ---@param severity string|vim.diagnostic.Severity
  377. ---@return vim.diagnostic.Severity?
  378. local function to_severity(severity)
  379. if type(severity) == 'string' then
  380. assert(M.severity[string.upper(severity)], string.format('Invalid severity: %s', severity))
  381. return M.severity[string.upper(severity)]
  382. end
  383. return severity
  384. end
  385. --- @param severity vim.diagnostic.SeverityFilter
  386. --- @return fun(vim.Diagnostic):boolean
  387. local function severity_predicate(severity)
  388. if type(severity) ~= 'table' then
  389. severity = assert(to_severity(severity))
  390. ---@param d vim.Diagnostic
  391. return function(d)
  392. return d.severity == severity
  393. end
  394. end
  395. if severity.min or severity.max then
  396. --- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
  397. local min_severity = to_severity(severity.min) or M.severity.HINT
  398. local max_severity = to_severity(severity.max) or M.severity.ERROR
  399. --- @param d vim.Diagnostic
  400. return function(d)
  401. return d.severity <= min_severity and d.severity >= max_severity
  402. end
  403. end
  404. --- @cast severity vim.diagnostic.Severity[]
  405. local severities = {} --- @type table<vim.diagnostic.Severity,true>
  406. for _, s in ipairs(severity) do
  407. severities[assert(to_severity(s))] = true
  408. end
  409. --- @param d vim.Diagnostic
  410. return function(d)
  411. return severities[d.severity]
  412. end
  413. end
  414. --- @param severity vim.diagnostic.SeverityFilter
  415. --- @param diagnostics vim.Diagnostic[]
  416. --- @return vim.Diagnostic[]
  417. local function filter_by_severity(severity, diagnostics)
  418. if not severity then
  419. return diagnostics
  420. end
  421. return vim.tbl_filter(severity_predicate(severity), diagnostics)
  422. end
  423. --- @param bufnr integer
  424. --- @return integer
  425. local function count_sources(bufnr)
  426. local seen = {} --- @type table<string,true>
  427. local count = 0
  428. for _, namespace_diagnostics in pairs(diagnostic_cache[bufnr]) do
  429. for _, diagnostic in ipairs(namespace_diagnostics) do
  430. local source = diagnostic.source
  431. if source and not seen[source] then
  432. seen[source] = true
  433. count = count + 1
  434. end
  435. end
  436. end
  437. return count
  438. end
  439. --- @param diagnostics vim.Diagnostic[]
  440. --- @return vim.Diagnostic[]
  441. local function prefix_source(diagnostics)
  442. --- @param d vim.Diagnostic
  443. return vim.tbl_map(function(d)
  444. if not d.source then
  445. return d
  446. end
  447. local t = vim.deepcopy(d, true)
  448. t.message = string.format('%s: %s', d.source, d.message)
  449. return t
  450. end, diagnostics)
  451. end
  452. --- @param diagnostics vim.Diagnostic[]
  453. --- @return vim.Diagnostic[]
  454. local function reformat_diagnostics(format, diagnostics)
  455. vim.validate('format', format, 'function')
  456. vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  457. local formatted = vim.deepcopy(diagnostics, true)
  458. for _, diagnostic in ipairs(formatted) do
  459. diagnostic.message = format(diagnostic)
  460. end
  461. return formatted
  462. end
  463. --- @param option string
  464. --- @param namespace integer?
  465. --- @return table
  466. local function enabled_value(option, namespace)
  467. local ns = namespace and M.get_namespace(namespace) or {}
  468. if ns.opts and type(ns.opts[option]) == 'table' then
  469. return ns.opts[option]
  470. end
  471. local global_opt = global_diagnostic_options[option]
  472. if type(global_opt) == 'table' then
  473. return global_opt
  474. end
  475. return {}
  476. end
  477. --- @param option string
  478. --- @param value any?
  479. --- @param namespace integer?
  480. --- @param bufnr integer
  481. --- @return any
  482. local function resolve_optional_value(option, value, namespace, bufnr)
  483. if not value then
  484. return false
  485. elseif value == true then
  486. return enabled_value(option, namespace)
  487. elseif type(value) == 'function' then
  488. local val = value(namespace, bufnr) --- @type any
  489. if val == true then
  490. return enabled_value(option, namespace)
  491. else
  492. return val
  493. end
  494. elseif type(value) == 'table' then
  495. return value
  496. end
  497. error('Unexpected option type: ' .. vim.inspect(value))
  498. end
  499. --- @param opts vim.diagnostic.Opts?
  500. --- @param namespace integer?
  501. --- @param bufnr integer
  502. --- @return vim.diagnostic.OptsResolved
  503. local function get_resolved_options(opts, namespace, bufnr)
  504. local ns = namespace and M.get_namespace(namespace) or {}
  505. -- Do not use tbl_deep_extend so that an empty table can be used to reset to default values
  506. local resolved = vim.tbl_extend('keep', opts or {}, ns.opts or {}, global_diagnostic_options) --- @type table<string,any>
  507. for k in pairs(global_diagnostic_options) do
  508. if resolved[k] ~= nil then
  509. resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr)
  510. end
  511. end
  512. return resolved
  513. end
  514. -- Default diagnostic highlights
  515. local diagnostic_severities = {
  516. [M.severity.ERROR] = { ctermfg = 1, guifg = 'Red' },
  517. [M.severity.WARN] = { ctermfg = 3, guifg = 'Orange' },
  518. [M.severity.INFO] = { ctermfg = 4, guifg = 'LightBlue' },
  519. [M.severity.HINT] = { ctermfg = 7, guifg = 'LightGrey' },
  520. }
  521. --- Make a map from vim.diagnostic.Severity -> Highlight Name
  522. --- @param base_name string
  523. --- @return table<vim.diagnostic.SeverityInt,string>
  524. local function make_highlight_map(base_name)
  525. local result = {} --- @type table<vim.diagnostic.SeverityInt,string>
  526. for k in pairs(diagnostic_severities) do
  527. local name = M.severity[k]
  528. name = name:sub(1, 1) .. name:sub(2):lower()
  529. result[k] = 'Diagnostic' .. base_name .. name
  530. end
  531. return result
  532. end
  533. -- TODO(lewis6991): these highlight maps can only be indexed with an integer, however there usage
  534. -- implies they can be indexed with any vim.diagnostic.Severity
  535. local virtual_text_highlight_map = make_highlight_map('VirtualText')
  536. local underline_highlight_map = make_highlight_map('Underline')
  537. local floating_highlight_map = make_highlight_map('Floating')
  538. local sign_highlight_map = make_highlight_map('Sign')
  539. --- @param diagnostics vim.Diagnostic[]
  540. --- @return table<integer,vim.Diagnostic[]>
  541. local function diagnostic_lines(diagnostics)
  542. if not diagnostics then
  543. return {}
  544. end
  545. local diagnostics_by_line = {} --- @type table<integer,vim.Diagnostic[]>
  546. for _, diagnostic in ipairs(diagnostics) do
  547. local line_diagnostics = diagnostics_by_line[diagnostic.lnum]
  548. if not line_diagnostics then
  549. line_diagnostics = {}
  550. diagnostics_by_line[diagnostic.lnum] = line_diagnostics
  551. end
  552. table.insert(line_diagnostics, diagnostic)
  553. end
  554. return diagnostics_by_line
  555. end
  556. --- @param namespace integer
  557. --- @param bufnr integer
  558. --- @param diagnostics vim.Diagnostic[]
  559. local function set_diagnostic_cache(namespace, bufnr, diagnostics)
  560. for _, diagnostic in ipairs(diagnostics) do
  561. assert(diagnostic.lnum, 'Diagnostic line number is required')
  562. assert(diagnostic.col, 'Diagnostic column is required')
  563. diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity)
  564. or M.severity.ERROR
  565. diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
  566. diagnostic.end_col = diagnostic.end_col or diagnostic.col
  567. diagnostic.namespace = namespace
  568. diagnostic.bufnr = bufnr
  569. end
  570. diagnostic_cache[bufnr][namespace] = diagnostics
  571. end
  572. --- @param bufnr integer
  573. --- @param last integer
  574. local function restore_extmarks(bufnr, last)
  575. for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
  576. local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
  577. local found = {} --- @type table<integer,true>
  578. for _, extmark in ipairs(extmarks_current) do
  579. -- nvim_buf_set_lines will move any extmark to the line after the last
  580. -- nvim_buf_set_text will move any extmark to the last line
  581. if extmark[2] ~= last + 1 then
  582. found[extmark[1]] = true
  583. end
  584. end
  585. for _, extmark in ipairs(extmarks) do
  586. if not found[extmark[1]] then
  587. local opts = extmark[4]
  588. opts.id = extmark[1]
  589. pcall(api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
  590. end
  591. end
  592. end
  593. end
  594. --- @param namespace integer
  595. --- @param bufnr? integer
  596. local function save_extmarks(namespace, bufnr)
  597. bufnr = vim._resolve_bufnr(bufnr)
  598. if not diagnostic_attached_buffers[bufnr] then
  599. api.nvim_buf_attach(bufnr, false, {
  600. on_lines = function(_, _, _, _, _, last)
  601. restore_extmarks(bufnr, last - 1)
  602. end,
  603. on_detach = function()
  604. diagnostic_cache_extmarks[bufnr] = nil
  605. end,
  606. })
  607. diagnostic_attached_buffers[bufnr] = true
  608. end
  609. diagnostic_cache_extmarks[bufnr][namespace] =
  610. api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true })
  611. end
  612. --- Create a function that converts a diagnostic severity to an extmark priority.
  613. --- @param priority integer Base priority
  614. --- @param opts vim.diagnostic.OptsResolved
  615. --- @return fun(severity: vim.diagnostic.Severity): integer
  616. local function severity_to_extmark_priority(priority, opts)
  617. if opts.severity_sort then
  618. if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
  619. return function(severity)
  620. return priority + (severity - vim.diagnostic.severity.ERROR)
  621. end
  622. end
  623. return function(severity)
  624. return priority + (vim.diagnostic.severity.HINT - severity)
  625. end
  626. end
  627. return function()
  628. return priority
  629. end
  630. end
  631. --- @type table<string,true>
  632. local registered_autocmds = {}
  633. local function make_augroup_key(namespace, bufnr)
  634. local ns = M.get_namespace(namespace)
  635. return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name)
  636. end
  637. --- @param namespace integer
  638. --- @param bufnr integer
  639. local function execute_scheduled_display(namespace, bufnr)
  640. local args = bufs_waiting_to_update[bufnr][namespace]
  641. if not args then
  642. return
  643. end
  644. -- Clear the args so we don't display unnecessarily.
  645. bufs_waiting_to_update[bufnr][namespace] = nil
  646. M.show(namespace, bufnr, nil, args)
  647. end
  648. --- Table of autocmd events to fire the update for displaying new diagnostic information
  649. local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
  650. --- @param namespace integer
  651. --- @param bufnr integer
  652. --- @param args any[]
  653. local function schedule_display(namespace, bufnr, args)
  654. bufs_waiting_to_update[bufnr][namespace] = args
  655. local key = make_augroup_key(namespace, bufnr)
  656. if not registered_autocmds[key] then
  657. local group = api.nvim_create_augroup(key, { clear = true })
  658. api.nvim_create_autocmd(insert_leave_auto_cmds, {
  659. group = group,
  660. buffer = bufnr,
  661. callback = function()
  662. execute_scheduled_display(namespace, bufnr)
  663. end,
  664. desc = 'vim.diagnostic: display diagnostics',
  665. })
  666. registered_autocmds[key] = true
  667. end
  668. end
  669. --- @param namespace integer
  670. --- @param bufnr integer
  671. local function clear_scheduled_display(namespace, bufnr)
  672. local key = make_augroup_key(namespace, bufnr)
  673. if registered_autocmds[key] then
  674. api.nvim_del_augroup_by_name(key)
  675. registered_autocmds[key] = nil
  676. end
  677. end
  678. --- @param bufnr integer?
  679. --- @param opts vim.diagnostic.GetOpts?
  680. --- @param clamp boolean
  681. --- @return vim.Diagnostic[]
  682. local function get_diagnostics(bufnr, opts, clamp)
  683. opts = opts or {}
  684. local namespace = opts.namespace
  685. if type(namespace) == 'number' then
  686. namespace = { namespace }
  687. end
  688. ---@cast namespace integer[]
  689. local diagnostics = {}
  690. -- Memoized results of buf_line_count per bufnr
  691. --- @type table<integer,integer>
  692. local buf_line_count = setmetatable({}, {
  693. --- @param t table<integer,integer>
  694. --- @param k integer
  695. --- @return integer
  696. __index = function(t, k)
  697. t[k] = api.nvim_buf_line_count(k)
  698. return rawget(t, k)
  699. end,
  700. })
  701. local match_severity = opts.severity and severity_predicate(opts.severity)
  702. or function(_)
  703. return true
  704. end
  705. ---@param b integer
  706. ---@param d vim.Diagnostic
  707. local function add(b, d)
  708. if
  709. match_severity(d)
  710. and (not opts.lnum or (opts.lnum >= d.lnum and opts.lnum <= (d.end_lnum or d.lnum)))
  711. then
  712. if clamp and api.nvim_buf_is_loaded(b) then
  713. local line_count = buf_line_count[b] - 1
  714. if
  715. d.lnum > line_count
  716. or d.end_lnum > line_count
  717. or d.lnum < 0
  718. or d.end_lnum < 0
  719. or d.col < 0
  720. or d.end_col < 0
  721. then
  722. d = vim.deepcopy(d, true)
  723. d.lnum = math.max(math.min(d.lnum, line_count), 0)
  724. d.end_lnum = math.max(math.min(assert(d.end_lnum), line_count), 0)
  725. d.col = math.max(d.col, 0)
  726. d.end_col = math.max(d.end_col, 0)
  727. end
  728. end
  729. table.insert(diagnostics, d)
  730. end
  731. end
  732. --- @param buf integer
  733. --- @param diags vim.Diagnostic[]
  734. local function add_all_diags(buf, diags)
  735. for _, diagnostic in pairs(diags) do
  736. add(buf, diagnostic)
  737. end
  738. end
  739. if namespace == nil and bufnr == nil then
  740. for b, t in pairs(diagnostic_cache) do
  741. for _, v in pairs(t) do
  742. add_all_diags(b, v)
  743. end
  744. end
  745. elseif namespace == nil then
  746. bufnr = vim._resolve_bufnr(bufnr)
  747. for iter_namespace in pairs(diagnostic_cache[bufnr]) do
  748. add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace])
  749. end
  750. elseif bufnr == nil then
  751. for b, t in pairs(diagnostic_cache) do
  752. for _, iter_namespace in ipairs(namespace) do
  753. add_all_diags(b, t[iter_namespace] or {})
  754. end
  755. end
  756. else
  757. bufnr = vim._resolve_bufnr(bufnr)
  758. for _, iter_namespace in ipairs(namespace) do
  759. add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace] or {})
  760. end
  761. end
  762. return diagnostics
  763. end
  764. --- @param loclist boolean
  765. --- @param opts vim.diagnostic.setqflist.Opts|vim.diagnostic.setloclist.Opts?
  766. local function set_list(loclist, opts)
  767. opts = opts or {}
  768. local open = if_nil(opts.open, true)
  769. local title = opts.title or 'Diagnostics'
  770. local winnr = opts.winnr or 0
  771. local bufnr --- @type integer?
  772. if loclist then
  773. bufnr = api.nvim_win_get_buf(winnr)
  774. end
  775. -- Don't clamp line numbers since the quickfix list can already handle line
  776. -- numbers beyond the end of the buffer
  777. local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], false)
  778. local items = M.toqflist(diagnostics)
  779. local qf_id = nil
  780. if loclist then
  781. vim.fn.setloclist(winnr, {}, 'u', { title = title, items = items })
  782. else
  783. qf_id = get_qf_id_for_title(title)
  784. -- If we already have a diagnostics quickfix, update it rather than creating a new one.
  785. -- This avoids polluting the finite set of quickfix lists, and preserves the currently selected
  786. -- entry.
  787. vim.fn.setqflist({}, qf_id and 'u' or ' ', {
  788. title = title,
  789. items = items,
  790. id = qf_id,
  791. })
  792. end
  793. if open then
  794. if not loclist then
  795. -- First navigate to the diagnostics quickfix list.
  796. local nr = vim.fn.getqflist({ id = qf_id, nr = 0 }).nr
  797. api.nvim_command(('silent %dchistory'):format(nr))
  798. -- Now open the quickfix list.
  799. api.nvim_command('botright cwindow')
  800. else
  801. api.nvim_command('lwindow')
  802. end
  803. end
  804. end
  805. --- Jump to the diagnostic with the highest severity. First sort the
  806. --- diagnostics by severity. The first diagnostic then contains the highest severity, and we can
  807. --- discard all diagnostics with a lower severity.
  808. --- @param diagnostics vim.Diagnostic[]
  809. local function filter_highest(diagnostics)
  810. table.sort(diagnostics, function(a, b)
  811. return a.severity < b.severity
  812. end)
  813. -- Find the first diagnostic where the severity does not match the highest severity, and remove
  814. -- that element and all subsequent elements from the array
  815. local worst = (diagnostics[1] or {}).severity
  816. local len = #diagnostics
  817. for i = 2, len do
  818. if diagnostics[i].severity ~= worst then
  819. for j = i, len do
  820. diagnostics[j] = nil
  821. end
  822. break
  823. end
  824. end
  825. end
  826. --- @param search_forward boolean
  827. --- @param opts vim.diagnostic.JumpOpts?
  828. --- @return vim.Diagnostic?
  829. local function next_diagnostic(search_forward, opts)
  830. opts = opts or {}
  831. -- Support deprecated win_id alias
  832. if opts.win_id then
  833. vim.deprecate('opts.win_id', 'opts.winid', '0.13')
  834. opts.winid = opts.win_id
  835. opts.win_id = nil --- @diagnostic disable-line
  836. end
  837. -- Support deprecated cursor_position alias
  838. if opts.cursor_position then
  839. vim.deprecate('opts.cursor_position', 'opts.pos', '0.13')
  840. opts.pos = opts.cursor_position
  841. opts.cursor_position = nil --- @diagnostic disable-line
  842. end
  843. local winid = opts.winid or api.nvim_get_current_win()
  844. local bufnr = api.nvim_win_get_buf(winid)
  845. local position = opts.pos or api.nvim_win_get_cursor(winid)
  846. -- Adjust row to be 0-indexed
  847. position[1] = position[1] - 1
  848. local wrap = if_nil(opts.wrap, true)
  849. local diagnostics = get_diagnostics(bufnr, opts, true)
  850. if opts._highest then
  851. filter_highest(diagnostics)
  852. end
  853. local line_diagnostics = diagnostic_lines(diagnostics)
  854. local line_count = api.nvim_buf_line_count(bufnr)
  855. for i = 0, line_count do
  856. local offset = i * (search_forward and 1 or -1)
  857. local lnum = position[1] + offset
  858. if lnum < 0 or lnum >= line_count then
  859. if not wrap then
  860. return
  861. end
  862. lnum = (lnum + line_count) % line_count
  863. end
  864. if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then
  865. local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
  866. --- @type function, function
  867. local sort_diagnostics, is_next
  868. if search_forward then
  869. sort_diagnostics = function(a, b)
  870. return a.col < b.col
  871. end
  872. is_next = function(d)
  873. return math.min(d.col, math.max(line_length - 1, 0)) > position[2]
  874. end
  875. else
  876. sort_diagnostics = function(a, b)
  877. return a.col > b.col
  878. end
  879. is_next = function(d)
  880. return math.min(d.col, math.max(line_length - 1, 0)) < position[2]
  881. end
  882. end
  883. table.sort(line_diagnostics[lnum], sort_diagnostics)
  884. if i == 0 then
  885. for _, v in
  886. pairs(line_diagnostics[lnum] --[[@as table<string,any>]])
  887. do
  888. if is_next(v) then
  889. return v
  890. end
  891. end
  892. else
  893. return line_diagnostics[lnum][1]
  894. end
  895. end
  896. end
  897. end
  898. --- Move the cursor to the given diagnostic.
  899. ---
  900. --- @param diagnostic vim.Diagnostic?
  901. --- @param opts vim.diagnostic.JumpOpts?
  902. local function goto_diagnostic(diagnostic, opts)
  903. if not diagnostic then
  904. api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {})
  905. return
  906. end
  907. opts = opts or {}
  908. -- Support deprecated win_id alias
  909. if opts.win_id then
  910. vim.deprecate('opts.win_id', 'opts.winid', '0.13')
  911. opts.winid = opts.win_id
  912. opts.win_id = nil --- @diagnostic disable-line
  913. end
  914. local winid = opts.winid or api.nvim_get_current_win()
  915. vim._with({ win = winid }, function()
  916. -- Save position in the window's jumplist
  917. vim.cmd("normal! m'")
  918. api.nvim_win_set_cursor(winid, { diagnostic.lnum + 1, diagnostic.col })
  919. -- Open folds under the cursor
  920. vim.cmd('normal! zv')
  921. end)
  922. local float_opts = opts.float
  923. if float_opts then
  924. float_opts = type(float_opts) == 'table' and float_opts or {}
  925. vim.schedule(function()
  926. M.open_float(vim.tbl_extend('keep', float_opts, {
  927. bufnr = api.nvim_win_get_buf(winid),
  928. scope = 'cursor',
  929. focus = false,
  930. }))
  931. end)
  932. end
  933. end
  934. --- Configure diagnostic options globally or for a specific diagnostic
  935. --- namespace.
  936. ---
  937. --- Configuration can be specified globally, per-namespace, or ephemerally
  938. --- (i.e. only for a single call to |vim.diagnostic.set()| or
  939. --- |vim.diagnostic.show()|). Ephemeral configuration has highest priority,
  940. --- followed by namespace configuration, and finally global configuration.
  941. ---
  942. --- For example, if a user enables virtual text globally with
  943. ---
  944. --- ```lua
  945. --- vim.diagnostic.config({ virtual_text = true })
  946. --- ```
  947. ---
  948. --- and a diagnostic producer sets diagnostics with
  949. ---
  950. --- ```lua
  951. --- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
  952. --- ```
  953. ---
  954. --- then virtual text will not be enabled for those diagnostics.
  955. ---
  956. ---@param opts vim.diagnostic.Opts? When omitted or `nil`, retrieve the current
  957. --- configuration. Otherwise, a configuration table (see |vim.diagnostic.Opts|).
  958. ---@param namespace integer? Update the options for the given namespace.
  959. --- When omitted, update the global diagnostic options.
  960. ---@return vim.diagnostic.Opts? : Current diagnostic config if {opts} is omitted.
  961. function M.config(opts, namespace)
  962. vim.validate('opts', opts, 'table', true)
  963. vim.validate('namespace', namespace, 'number', true)
  964. local t --- @type vim.diagnostic.Opts
  965. if namespace then
  966. local ns = M.get_namespace(namespace)
  967. t = ns.opts
  968. else
  969. t = global_diagnostic_options
  970. end
  971. if not opts then
  972. -- Return current config
  973. return vim.deepcopy(t, true)
  974. end
  975. for k, v in
  976. pairs(opts --[[@as table<any,any>]])
  977. do
  978. t[k] = v
  979. end
  980. if namespace then
  981. for bufnr, v in pairs(diagnostic_cache) do
  982. if v[namespace] then
  983. M.show(namespace, bufnr)
  984. end
  985. end
  986. else
  987. for bufnr, v in pairs(diagnostic_cache) do
  988. for ns in pairs(v) do
  989. M.show(ns, bufnr)
  990. end
  991. end
  992. end
  993. end
  994. --- Set diagnostics for the given namespace and buffer.
  995. ---
  996. ---@param namespace integer The diagnostic namespace
  997. ---@param bufnr integer Buffer number
  998. ---@param diagnostics vim.Diagnostic[]
  999. ---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()|
  1000. function M.set(namespace, bufnr, diagnostics, opts)
  1001. vim.validate('namespace', namespace, 'number')
  1002. vim.validate('bufnr', bufnr, 'number')
  1003. vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  1004. vim.validate('opts', opts, 'table', true)
  1005. bufnr = vim._resolve_bufnr(bufnr)
  1006. if vim.tbl_isempty(diagnostics) then
  1007. diagnostic_cache[bufnr][namespace] = nil
  1008. else
  1009. set_diagnostic_cache(namespace, bufnr, diagnostics)
  1010. end
  1011. M.show(namespace, bufnr, nil, opts)
  1012. api.nvim_exec_autocmds('DiagnosticChanged', {
  1013. modeline = false,
  1014. buffer = bufnr,
  1015. -- TODO(lewis6991): should this be deepcopy()'d like they are in vim.diagnostic.get()
  1016. data = { diagnostics = diagnostics },
  1017. })
  1018. end
  1019. --- Get namespace metadata.
  1020. ---
  1021. ---@param namespace integer Diagnostic namespace
  1022. ---@return vim.diagnostic.NS : Namespace metadata
  1023. function M.get_namespace(namespace)
  1024. vim.validate('namespace', namespace, 'number')
  1025. if not all_namespaces[namespace] then
  1026. local name --- @type string?
  1027. for k, v in pairs(api.nvim_get_namespaces()) do
  1028. if namespace == v then
  1029. name = k
  1030. break
  1031. end
  1032. end
  1033. assert(name, 'namespace does not exist or is anonymous')
  1034. all_namespaces[namespace] = {
  1035. name = name,
  1036. opts = {},
  1037. user_data = {},
  1038. }
  1039. end
  1040. return all_namespaces[namespace]
  1041. end
  1042. --- Get current diagnostic namespaces.
  1043. ---
  1044. ---@return table<integer,vim.diagnostic.NS> : List of active diagnostic namespaces |vim.diagnostic|.
  1045. function M.get_namespaces()
  1046. return vim.deepcopy(all_namespaces, true)
  1047. end
  1048. --- Get current diagnostics.
  1049. ---
  1050. --- Modifying diagnostics in the returned table has no effect.
  1051. --- To set diagnostics in a buffer, use |vim.diagnostic.set()|.
  1052. ---
  1053. ---@param bufnr integer? Buffer number to get diagnostics from. Use 0 for
  1054. --- current buffer or nil for all buffers.
  1055. ---@param opts? vim.diagnostic.GetOpts
  1056. ---@return vim.Diagnostic[] : Fields `bufnr`, `end_lnum`, `end_col`, and `severity`
  1057. --- are guaranteed to be present.
  1058. function M.get(bufnr, opts)
  1059. vim.validate('bufnr', bufnr, 'number', true)
  1060. vim.validate('opts', opts, 'table', true)
  1061. return vim.deepcopy(get_diagnostics(bufnr, opts, false), true)
  1062. end
  1063. --- Get current diagnostics count.
  1064. ---
  1065. ---@param bufnr? integer Buffer number to get diagnostics from. Use 0 for
  1066. --- current buffer or nil for all buffers.
  1067. ---@param opts? vim.diagnostic.GetOpts
  1068. ---@return table : Table with actually present severity values as keys
  1069. --- (see |diagnostic-severity|) and integer counts as values.
  1070. function M.count(bufnr, opts)
  1071. vim.validate('bufnr', bufnr, 'number', true)
  1072. vim.validate('opts', opts, 'table', true)
  1073. local diagnostics = get_diagnostics(bufnr, opts, false)
  1074. local count = {} --- @type table<integer,integer>
  1075. for i = 1, #diagnostics do
  1076. local severity = diagnostics[i].severity --[[@as integer]]
  1077. count[severity] = (count[severity] or 0) + 1
  1078. end
  1079. return count
  1080. end
  1081. --- Get the previous diagnostic closest to the cursor position.
  1082. ---
  1083. ---@param opts? vim.diagnostic.JumpOpts
  1084. ---@return vim.Diagnostic? : Previous diagnostic
  1085. function M.get_prev(opts)
  1086. return next_diagnostic(false, opts)
  1087. end
  1088. --- Return the position of the previous diagnostic in the current buffer.
  1089. ---
  1090. ---@param opts? vim.diagnostic.JumpOpts
  1091. ---@return table|false: Previous diagnostic position as a `(row, col)` tuple
  1092. --- or `false` if there is no prior diagnostic.
  1093. ---@deprecated
  1094. function M.get_prev_pos(opts)
  1095. vim.deprecate(
  1096. 'vim.diagnostic.get_prev_pos()',
  1097. 'access the lnum and col fields from get_prev() instead',
  1098. '0.13'
  1099. )
  1100. local prev = M.get_prev(opts)
  1101. if not prev then
  1102. return false
  1103. end
  1104. return { prev.lnum, prev.col }
  1105. end
  1106. --- Move to the previous diagnostic in the current buffer.
  1107. ---@param opts? vim.diagnostic.JumpOpts
  1108. ---@deprecated
  1109. function M.goto_prev(opts)
  1110. vim.deprecate('vim.diagnostic.goto_prev()', 'vim.diagnostic.jump()', '0.13')
  1111. opts = opts or {}
  1112. opts.float = if_nil(opts.float, true)
  1113. goto_diagnostic(M.get_prev(opts), opts)
  1114. end
  1115. --- Get the next diagnostic closest to the cursor position.
  1116. ---
  1117. ---@param opts? vim.diagnostic.JumpOpts
  1118. ---@return vim.Diagnostic? : Next diagnostic
  1119. function M.get_next(opts)
  1120. return next_diagnostic(true, opts)
  1121. end
  1122. --- Return the position of the next diagnostic in the current buffer.
  1123. ---
  1124. ---@param opts? vim.diagnostic.JumpOpts
  1125. ---@return table|false : Next diagnostic position as a `(row, col)` tuple or false if no next
  1126. --- diagnostic.
  1127. ---@deprecated
  1128. function M.get_next_pos(opts)
  1129. vim.deprecate(
  1130. 'vim.diagnostic.get_next_pos()',
  1131. 'access the lnum and col fields from get_next() instead',
  1132. '0.13'
  1133. )
  1134. local next = M.get_next(opts)
  1135. if not next then
  1136. return false
  1137. end
  1138. return { next.lnum, next.col }
  1139. end
  1140. --- A table with the following keys:
  1141. --- @class vim.diagnostic.GetOpts
  1142. ---
  1143. --- Limit diagnostics to one or more namespaces.
  1144. --- @field namespace? integer[]|integer
  1145. ---
  1146. --- Limit diagnostics to those spanning the specified line number.
  1147. --- @field lnum? integer
  1148. ---
  1149. --- See |diagnostic-severity|.
  1150. --- @field severity? vim.diagnostic.SeverityFilter
  1151. --- Configuration table with the keys listed below. Some parameters can have their default values
  1152. --- changed with |vim.diagnostic.config()|.
  1153. --- @class vim.diagnostic.JumpOpts : vim.diagnostic.GetOpts
  1154. ---
  1155. --- The diagnostic to jump to. Mutually exclusive with {count}, {namespace},
  1156. --- and {severity}.
  1157. --- @field diagnostic? vim.Diagnostic
  1158. ---
  1159. --- The number of diagnostics to move by, starting from {pos}. A positive
  1160. --- integer moves forward by {count} diagnostics, while a negative integer moves
  1161. --- backward by {count} diagnostics. Mutually exclusive with {diagnostic}.
  1162. --- @field count? integer
  1163. ---
  1164. --- Cursor position as a `(row, col)` tuple. See |nvim_win_get_cursor()|. Used
  1165. --- to find the nearest diagnostic when {count} is used. Only used when {count}
  1166. --- is non-nil. Default is the current cursor position.
  1167. --- @field pos? [integer,integer]
  1168. ---
  1169. --- Whether to loop around file or not. Similar to 'wrapscan'.
  1170. --- (default: `true`)
  1171. --- @field wrap? boolean
  1172. ---
  1173. --- See |diagnostic-severity|.
  1174. --- @field severity? vim.diagnostic.SeverityFilter
  1175. ---
  1176. --- Go to the diagnostic with the highest severity.
  1177. --- (default: `false`)
  1178. --- @field package _highest? boolean
  1179. ---
  1180. --- If `true`, call |vim.diagnostic.open_float()| after moving.
  1181. --- If a table, pass the table as the {opts} parameter to |vim.diagnostic.open_float()|.
  1182. --- Unless overridden, the float will show diagnostics at the new cursor
  1183. --- position (as if "cursor" were passed to the "scope" option).
  1184. --- (default: `false`)
  1185. --- @field float? boolean|vim.diagnostic.Opts.Float
  1186. ---
  1187. --- Window ID
  1188. --- (default: `0`)
  1189. --- @field winid? integer
  1190. --- Move to a diagnostic.
  1191. ---
  1192. --- @param opts vim.diagnostic.JumpOpts
  1193. --- @return vim.Diagnostic? # The diagnostic that was moved to.
  1194. function M.jump(opts)
  1195. vim.validate('opts', opts, 'table')
  1196. -- One of "diagnostic" or "count" must be provided
  1197. assert(
  1198. opts.diagnostic or opts.count,
  1199. 'One of "diagnostic" or "count" must be specified in the options to vim.diagnostic.jump()'
  1200. )
  1201. -- Apply configuration options from vim.diagnostic.config()
  1202. opts = vim.tbl_deep_extend('keep', opts, global_diagnostic_options.jump)
  1203. if opts.diagnostic then
  1204. goto_diagnostic(opts.diagnostic, opts)
  1205. return opts.diagnostic
  1206. end
  1207. local count = opts.count
  1208. if count == 0 then
  1209. return nil
  1210. end
  1211. -- Support deprecated cursor_position alias
  1212. if opts.cursor_position then
  1213. vim.deprecate('opts.cursor_position', 'opts.pos', '0.13')
  1214. opts.pos = opts.cursor_position
  1215. opts.cursor_position = nil --- @diagnostic disable-line
  1216. end
  1217. local diag = nil
  1218. while count ~= 0 do
  1219. local next = next_diagnostic(count > 0, opts)
  1220. if not next then
  1221. break
  1222. end
  1223. -- Update cursor position
  1224. opts.pos = { next.lnum + 1, next.col }
  1225. if count > 0 then
  1226. count = count - 1
  1227. else
  1228. count = count + 1
  1229. end
  1230. diag = next
  1231. end
  1232. goto_diagnostic(diag, opts)
  1233. return diag
  1234. end
  1235. --- Move to the next diagnostic.
  1236. ---
  1237. ---@param opts? vim.diagnostic.JumpOpts
  1238. ---@deprecated
  1239. function M.goto_next(opts)
  1240. vim.deprecate('vim.diagnostic.goto_next()', 'vim.diagnostic.jump()', '0.13')
  1241. opts = opts or {}
  1242. opts.float = if_nil(opts.float, true)
  1243. goto_diagnostic(M.get_next(opts), opts)
  1244. end
  1245. M.handlers.signs = {
  1246. show = function(namespace, bufnr, diagnostics, opts)
  1247. vim.validate('namespace', namespace, 'number')
  1248. vim.validate('bufnr', bufnr, 'number')
  1249. vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  1250. vim.validate('opts', opts, 'table', true)
  1251. bufnr = vim._resolve_bufnr(bufnr)
  1252. opts = opts or {}
  1253. if not api.nvim_buf_is_loaded(bufnr) then
  1254. return
  1255. end
  1256. if opts.signs and opts.signs.severity then
  1257. diagnostics = filter_by_severity(opts.signs.severity, diagnostics)
  1258. end
  1259. -- 10 is the default sign priority when none is explicitly specified
  1260. local priority = opts.signs and opts.signs.priority or 10
  1261. local get_priority = severity_to_extmark_priority(priority, opts)
  1262. local ns = M.get_namespace(namespace)
  1263. if not ns.user_data.sign_ns then
  1264. ns.user_data.sign_ns =
  1265. api.nvim_create_namespace(string.format('%s/diagnostic/signs', ns.name))
  1266. end
  1267. -- Handle legacy diagnostic sign definitions
  1268. -- These were deprecated in 0.10 and will be removed in 0.12
  1269. if opts.signs and not opts.signs.text and not opts.signs.numhl then
  1270. for _, v in ipairs({ 'Error', 'Warn', 'Info', 'Hint' }) do
  1271. local name = string.format('DiagnosticSign%s', v)
  1272. local sign = vim.fn.sign_getdefined(name)[1]
  1273. if sign then
  1274. local severity = M.severity[v:upper()]
  1275. vim.deprecate(
  1276. 'Defining diagnostic signs with :sign-define or sign_define()',
  1277. 'vim.diagnostic.config()',
  1278. '0.12'
  1279. )
  1280. if not opts.signs.text then
  1281. opts.signs.text = {}
  1282. end
  1283. if not opts.signs.numhl then
  1284. opts.signs.numhl = {}
  1285. end
  1286. if not opts.signs.linehl then
  1287. opts.signs.linehl = {}
  1288. end
  1289. if opts.signs.text[severity] == nil then
  1290. opts.signs.text[severity] = sign.text or ''
  1291. end
  1292. if opts.signs.numhl[severity] == nil then
  1293. opts.signs.numhl[severity] = sign.numhl
  1294. end
  1295. if opts.signs.linehl[severity] == nil then
  1296. opts.signs.linehl[severity] = sign.linehl
  1297. end
  1298. end
  1299. end
  1300. end
  1301. local text = {} ---@type table<vim.diagnostic.Severity|string, string>
  1302. for k in pairs(M.severity) do
  1303. if opts.signs.text and opts.signs.text[k] then
  1304. text[k] = opts.signs.text[k]
  1305. elseif type(k) == 'string' and not text[k] then
  1306. text[k] = string.sub(k, 1, 1):upper()
  1307. end
  1308. end
  1309. local numhl = opts.signs.numhl or {}
  1310. local linehl = opts.signs.linehl or {}
  1311. local line_count = api.nvim_buf_line_count(bufnr)
  1312. for _, diagnostic in ipairs(diagnostics) do
  1313. if diagnostic.lnum <= line_count then
  1314. api.nvim_buf_set_extmark(bufnr, ns.user_data.sign_ns, diagnostic.lnum, 0, {
  1315. sign_text = text[diagnostic.severity] or text[M.severity[diagnostic.severity]] or 'U',
  1316. sign_hl_group = sign_highlight_map[diagnostic.severity],
  1317. number_hl_group = numhl[diagnostic.severity],
  1318. line_hl_group = linehl[diagnostic.severity],
  1319. priority = get_priority(diagnostic.severity),
  1320. })
  1321. end
  1322. end
  1323. end,
  1324. --- @param namespace integer
  1325. --- @param bufnr integer
  1326. hide = function(namespace, bufnr)
  1327. local ns = M.get_namespace(namespace)
  1328. if ns.user_data.sign_ns and api.nvim_buf_is_valid(bufnr) then
  1329. api.nvim_buf_clear_namespace(bufnr, ns.user_data.sign_ns, 0, -1)
  1330. end
  1331. end,
  1332. }
  1333. M.handlers.underline = {
  1334. show = function(namespace, bufnr, diagnostics, opts)
  1335. vim.validate('namespace', namespace, 'number')
  1336. vim.validate('bufnr', bufnr, 'number')
  1337. vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  1338. vim.validate('opts', opts, 'table', true)
  1339. bufnr = vim._resolve_bufnr(bufnr)
  1340. opts = opts or {}
  1341. if not vim.api.nvim_buf_is_loaded(bufnr) then
  1342. return
  1343. end
  1344. if opts.underline and opts.underline.severity then
  1345. diagnostics = filter_by_severity(opts.underline.severity, diagnostics)
  1346. end
  1347. local ns = M.get_namespace(namespace)
  1348. if not ns.user_data.underline_ns then
  1349. ns.user_data.underline_ns =
  1350. api.nvim_create_namespace(string.format('%s/diagnostic/underline', ns.name))
  1351. end
  1352. local underline_ns = ns.user_data.underline_ns
  1353. local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts)
  1354. for _, diagnostic in ipairs(diagnostics) do
  1355. -- Default to error if we don't have a highlight associated
  1356. local higroup = underline_highlight_map[assert(diagnostic.severity)]
  1357. or underline_highlight_map[vim.diagnostic.severity.ERROR]
  1358. if diagnostic._tags then
  1359. -- TODO(lewis6991): we should be able to stack these.
  1360. if diagnostic._tags.unnecessary then
  1361. higroup = 'DiagnosticUnnecessary'
  1362. end
  1363. if diagnostic._tags.deprecated then
  1364. higroup = 'DiagnosticDeprecated'
  1365. end
  1366. end
  1367. vim.hl.range(
  1368. bufnr,
  1369. underline_ns,
  1370. higroup,
  1371. { diagnostic.lnum, diagnostic.col },
  1372. { diagnostic.end_lnum, diagnostic.end_col },
  1373. { priority = get_priority(diagnostic.severity) }
  1374. )
  1375. end
  1376. save_extmarks(underline_ns, bufnr)
  1377. end,
  1378. hide = function(namespace, bufnr)
  1379. local ns = M.get_namespace(namespace)
  1380. if ns.user_data.underline_ns then
  1381. diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {}
  1382. if api.nvim_buf_is_valid(bufnr) then
  1383. api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1)
  1384. end
  1385. end
  1386. end,
  1387. }
  1388. M.handlers.virtual_text = {
  1389. show = function(namespace, bufnr, diagnostics, opts)
  1390. vim.validate('namespace', namespace, 'number')
  1391. vim.validate('bufnr', bufnr, 'number')
  1392. vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  1393. vim.validate('opts', opts, 'table', true)
  1394. bufnr = vim._resolve_bufnr(bufnr)
  1395. opts = opts or {}
  1396. if not vim.api.nvim_buf_is_loaded(bufnr) then
  1397. return
  1398. end
  1399. local severity --- @type vim.diagnostic.SeverityFilter?
  1400. if opts.virtual_text then
  1401. if opts.virtual_text.format then
  1402. diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
  1403. end
  1404. if
  1405. opts.virtual_text.source
  1406. and (opts.virtual_text.source ~= 'if_many' or count_sources(bufnr) > 1)
  1407. then
  1408. diagnostics = prefix_source(diagnostics)
  1409. end
  1410. if opts.virtual_text.severity then
  1411. severity = opts.virtual_text.severity
  1412. end
  1413. end
  1414. local ns = M.get_namespace(namespace)
  1415. if not ns.user_data.virt_text_ns then
  1416. ns.user_data.virt_text_ns =
  1417. api.nvim_create_namespace(string.format('%s/diagnostic/virtual_text', ns.name))
  1418. end
  1419. local virt_text_ns = ns.user_data.virt_text_ns
  1420. local buffer_line_diagnostics = diagnostic_lines(diagnostics)
  1421. for line, line_diagnostics in pairs(buffer_line_diagnostics) do
  1422. if severity then
  1423. line_diagnostics = filter_by_severity(severity, line_diagnostics)
  1424. end
  1425. local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text)
  1426. if virt_texts then
  1427. api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
  1428. hl_mode = opts.virtual_text.hl_mode or 'combine',
  1429. virt_text = virt_texts,
  1430. virt_text_pos = opts.virtual_text.virt_text_pos,
  1431. virt_text_hide = opts.virtual_text.virt_text_hide,
  1432. virt_text_win_col = opts.virtual_text.virt_text_win_col,
  1433. })
  1434. end
  1435. end
  1436. save_extmarks(virt_text_ns, bufnr)
  1437. end,
  1438. hide = function(namespace, bufnr)
  1439. local ns = M.get_namespace(namespace)
  1440. if ns.user_data.virt_text_ns then
  1441. diagnostic_cache_extmarks[bufnr][ns.user_data.virt_text_ns] = {}
  1442. if api.nvim_buf_is_valid(bufnr) then
  1443. api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_text_ns, 0, -1)
  1444. end
  1445. end
  1446. end,
  1447. }
  1448. --- Get virtual text chunks to display using |nvim_buf_set_extmark()|.
  1449. ---
  1450. --- Exported for backward compatibility with
  1451. --- vim.lsp.diagnostic.get_virtual_text_chunks_for_line(). When that function is eventually removed,
  1452. --- this can be made local.
  1453. --- @private
  1454. --- @param line_diags table<integer,vim.Diagnostic>
  1455. --- @param opts vim.diagnostic.Opts.VirtualText
  1456. function M._get_virt_text_chunks(line_diags, opts)
  1457. if #line_diags == 0 then
  1458. return nil
  1459. end
  1460. opts = opts or {}
  1461. local prefix = opts.prefix or '■'
  1462. local suffix = opts.suffix or ''
  1463. local spacing = opts.spacing or 4
  1464. -- Create a little more space between virtual text and contents
  1465. local virt_texts = { { string.rep(' ', spacing) } }
  1466. for i = 1, #line_diags do
  1467. local resolved_prefix = prefix
  1468. if type(prefix) == 'function' then
  1469. resolved_prefix = prefix(line_diags[i], i, #line_diags) or ''
  1470. end
  1471. table.insert(
  1472. virt_texts,
  1473. { resolved_prefix, virtual_text_highlight_map[line_diags[i].severity] }
  1474. )
  1475. end
  1476. local last = line_diags[#line_diags]
  1477. -- TODO(tjdevries): Allow different servers to be shown first somehow?
  1478. -- TODO(tjdevries): Display server name associated with these?
  1479. if last.message then
  1480. if type(suffix) == 'function' then
  1481. suffix = suffix(last) or ''
  1482. end
  1483. table.insert(virt_texts, {
  1484. string.format(' %s%s', last.message:gsub('\r', ''):gsub('\n', ' '), suffix),
  1485. virtual_text_highlight_map[last.severity],
  1486. })
  1487. return virt_texts
  1488. end
  1489. end
  1490. --- Hide currently displayed diagnostics.
  1491. ---
  1492. --- This only clears the decorations displayed in the buffer. Diagnostics can
  1493. --- be redisplayed with |vim.diagnostic.show()|. To completely remove
  1494. --- diagnostics, use |vim.diagnostic.reset()|.
  1495. ---
  1496. --- To hide diagnostics and prevent them from re-displaying, use
  1497. --- |vim.diagnostic.enable()|.
  1498. ---
  1499. ---@param namespace integer? Diagnostic namespace. When omitted, hide
  1500. --- diagnostics from all namespaces.
  1501. ---@param bufnr integer? Buffer number, or 0 for current buffer. When
  1502. --- omitted, hide diagnostics in all buffers.
  1503. function M.hide(namespace, bufnr)
  1504. vim.validate('namespace', namespace, 'number', true)
  1505. vim.validate('bufnr', bufnr, 'number', true)
  1506. local buffers = bufnr and { vim._resolve_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
  1507. for _, iter_bufnr in ipairs(buffers) do
  1508. local namespaces = namespace and { namespace } or vim.tbl_keys(diagnostic_cache[iter_bufnr])
  1509. for _, iter_namespace in ipairs(namespaces) do
  1510. for _, handler in pairs(M.handlers) do
  1511. if handler.hide then
  1512. handler.hide(iter_namespace, iter_bufnr)
  1513. end
  1514. end
  1515. end
  1516. end
  1517. end
  1518. --- Check whether diagnostics are enabled.
  1519. ---
  1520. --- @param filter vim.diagnostic.Filter?
  1521. --- @return boolean
  1522. --- @since 12
  1523. function M.is_enabled(filter)
  1524. filter = filter or {}
  1525. if filter.ns_id and M.get_namespace(filter.ns_id).disabled then
  1526. return false
  1527. elseif filter.bufnr == nil then
  1528. -- See enable() logic.
  1529. return vim.tbl_isempty(diagnostic_disabled) and not diagnostic_disabled[1]
  1530. end
  1531. local bufnr = vim._resolve_bufnr(filter.bufnr)
  1532. if type(diagnostic_disabled[bufnr]) == 'table' then
  1533. return not diagnostic_disabled[bufnr][filter.ns_id]
  1534. end
  1535. return diagnostic_disabled[bufnr] == nil
  1536. end
  1537. --- @deprecated use `vim.diagnostic.is_enabled()`
  1538. function M.is_disabled(bufnr, namespace)
  1539. vim.deprecate('vim.diagnostic.is_disabled()', 'vim.diagnostic.is_enabled()', '0.12')
  1540. return not M.is_enabled { bufnr = bufnr or 0, ns_id = namespace }
  1541. end
  1542. --- Display diagnostics for the given namespace and buffer.
  1543. ---
  1544. ---@param namespace integer? Diagnostic namespace. When omitted, show
  1545. --- diagnostics from all namespaces.
  1546. ---@param bufnr integer? Buffer number, or 0 for current buffer. When omitted, show
  1547. --- diagnostics in all buffers.
  1548. ---@param diagnostics vim.Diagnostic[]? The diagnostics to display. When omitted, use the
  1549. --- saved diagnostics for the given namespace and
  1550. --- buffer. This can be used to display a list of diagnostics
  1551. --- without saving them or to display only a subset of
  1552. --- diagnostics. May not be used when {namespace}
  1553. --- or {bufnr} is nil.
  1554. ---@param opts? vim.diagnostic.Opts Display options.
  1555. function M.show(namespace, bufnr, diagnostics, opts)
  1556. vim.validate('namespace', namespace, 'number', true)
  1557. vim.validate('bufnr', bufnr, 'number', true)
  1558. vim.validate('diagnostics', diagnostics, vim.islist, true, 'a list of diagnostics')
  1559. vim.validate('opts', opts, 'table', true)
  1560. if not bufnr or not namespace then
  1561. assert(not diagnostics, 'Cannot show diagnostics without a buffer and namespace')
  1562. if not bufnr then
  1563. for iter_bufnr in pairs(diagnostic_cache) do
  1564. M.show(namespace, iter_bufnr, nil, opts)
  1565. end
  1566. else
  1567. -- namespace is nil
  1568. bufnr = vim._resolve_bufnr(bufnr)
  1569. for iter_namespace in pairs(diagnostic_cache[bufnr]) do
  1570. M.show(iter_namespace, bufnr, nil, opts)
  1571. end
  1572. end
  1573. return
  1574. end
  1575. if not M.is_enabled { bufnr = bufnr or 0, ns_id = namespace } then
  1576. return
  1577. end
  1578. M.hide(namespace, bufnr)
  1579. diagnostics = diagnostics or get_diagnostics(bufnr, { namespace = namespace }, true)
  1580. if vim.tbl_isempty(diagnostics) then
  1581. return
  1582. end
  1583. local opts_res = get_resolved_options(opts, namespace, bufnr)
  1584. if opts_res.update_in_insert then
  1585. clear_scheduled_display(namespace, bufnr)
  1586. else
  1587. local mode = api.nvim_get_mode()
  1588. if string.sub(mode.mode, 1, 1) == 'i' then
  1589. schedule_display(namespace, bufnr, opts_res)
  1590. return
  1591. end
  1592. end
  1593. if if_nil(opts_res.severity_sort, false) then
  1594. if type(opts_res.severity_sort) == 'table' and opts_res.severity_sort.reverse then
  1595. table.sort(diagnostics, function(a, b)
  1596. return a.severity < b.severity
  1597. end)
  1598. else
  1599. table.sort(diagnostics, function(a, b)
  1600. return a.severity > b.severity
  1601. end)
  1602. end
  1603. end
  1604. for handler_name, handler in pairs(M.handlers) do
  1605. if handler.show and opts_res[handler_name] then
  1606. handler.show(namespace, bufnr, diagnostics, opts_res)
  1607. end
  1608. end
  1609. end
  1610. --- Show diagnostics in a floating window.
  1611. ---
  1612. ---@param opts vim.diagnostic.Opts.Float?
  1613. ---@return integer? float_bufnr
  1614. ---@return integer? winid
  1615. function M.open_float(opts, ...)
  1616. -- Support old (bufnr, opts) signature
  1617. local bufnr --- @type integer?
  1618. if opts == nil or type(opts) == 'number' then
  1619. bufnr = opts
  1620. opts = ... --- @type vim.diagnostic.Opts.Float
  1621. else
  1622. vim.validate('opts', opts, 'table', true)
  1623. end
  1624. opts = opts or {}
  1625. bufnr = vim._resolve_bufnr(bufnr or opts.bufnr)
  1626. do
  1627. -- Resolve options with user settings from vim.diagnostic.config
  1628. -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float`
  1629. -- does not have a dedicated table for configuration options; instead, the options are mixed in
  1630. -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated
  1631. -- options table that inherits missing keys from the global configuration before resolving.
  1632. local t = global_diagnostic_options.float
  1633. local float_opts = vim.tbl_extend('keep', opts, type(t) == 'table' and t or {})
  1634. opts = get_resolved_options({ float = float_opts }, nil, bufnr).float
  1635. end
  1636. local scope = ({ l = 'line', c = 'cursor', b = 'buffer' })[opts.scope] or opts.scope or 'line'
  1637. local lnum, col --- @type integer, integer
  1638. local opts_pos = opts.pos
  1639. if scope == 'line' or scope == 'cursor' then
  1640. if not opts_pos then
  1641. local pos = api.nvim_win_get_cursor(0)
  1642. lnum = pos[1] - 1
  1643. col = pos[2]
  1644. elseif type(opts_pos) == 'number' then
  1645. lnum = opts_pos
  1646. elseif type(opts_pos) == 'table' then
  1647. lnum, col = opts_pos[1], opts_pos[2]
  1648. else
  1649. error("Invalid value for option 'pos'")
  1650. end
  1651. elseif scope ~= 'buffer' then
  1652. error("Invalid value for option 'scope'")
  1653. end
  1654. local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], true)
  1655. if scope == 'line' then
  1656. --- @param d vim.Diagnostic
  1657. diagnostics = vim.tbl_filter(function(d)
  1658. return lnum >= d.lnum
  1659. and lnum <= d.end_lnum
  1660. and (d.lnum == d.end_lnum or lnum ~= d.end_lnum or d.end_col ~= 0)
  1661. end, diagnostics)
  1662. elseif scope == 'cursor' then
  1663. -- If `col` is past the end of the line, show if the cursor is on the last char in the line
  1664. local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
  1665. --- @param d vim.Diagnostic
  1666. diagnostics = vim.tbl_filter(function(d)
  1667. return lnum >= d.lnum
  1668. and lnum <= d.end_lnum
  1669. and (lnum ~= d.lnum or col >= math.min(d.col, line_length - 1))
  1670. and ((d.lnum == d.end_lnum and d.col == d.end_col) or lnum ~= d.end_lnum or col < d.end_col)
  1671. end, diagnostics)
  1672. end
  1673. if vim.tbl_isempty(diagnostics) then
  1674. return
  1675. end
  1676. local severity_sort = if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
  1677. if severity_sort then
  1678. if type(severity_sort) == 'table' and severity_sort.reverse then
  1679. table.sort(diagnostics, function(a, b)
  1680. return a.severity > b.severity
  1681. end)
  1682. else
  1683. table.sort(diagnostics, function(a, b)
  1684. return a.severity < b.severity
  1685. end)
  1686. end
  1687. end
  1688. local lines = {} --- @type string[]
  1689. local highlights = {} --- @type table[]
  1690. local header = if_nil(opts.header, 'Diagnostics:')
  1691. if header then
  1692. vim.validate('header', header, { 'string', 'table' }, "'string' or 'table'")
  1693. if type(header) == 'table' then
  1694. -- Don't insert any lines for an empty string
  1695. if string.len(if_nil(header[1], '')) > 0 then
  1696. table.insert(lines, header[1])
  1697. table.insert(highlights, { hlname = header[2] or 'Bold' })
  1698. end
  1699. elseif #header > 0 then
  1700. table.insert(lines, header)
  1701. table.insert(highlights, { hlname = 'Bold' })
  1702. end
  1703. end
  1704. if opts.format then
  1705. diagnostics = reformat_diagnostics(opts.format, diagnostics)
  1706. end
  1707. if opts.source and (opts.source ~= 'if_many' or count_sources(bufnr) > 1) then
  1708. diagnostics = prefix_source(diagnostics)
  1709. end
  1710. local prefix_opt =
  1711. if_nil(opts.prefix, (scope == 'cursor' and #diagnostics <= 1) and '' or function(_, i)
  1712. return string.format('%d. ', i)
  1713. end)
  1714. local prefix, prefix_hl_group --- @type string?, string?
  1715. if prefix_opt then
  1716. vim.validate(
  1717. 'prefix',
  1718. prefix_opt,
  1719. { 'string', 'table', 'function' },
  1720. "'string' or 'table' or 'function'"
  1721. )
  1722. if type(prefix_opt) == 'string' then
  1723. prefix, prefix_hl_group = prefix_opt, 'NormalFloat'
  1724. elseif type(prefix_opt) == 'table' then
  1725. prefix, prefix_hl_group = prefix_opt[1] or '', prefix_opt[2] or 'NormalFloat'
  1726. end
  1727. end
  1728. local suffix_opt = if_nil(opts.suffix, function(diagnostic)
  1729. return diagnostic.code and string.format(' [%s]', diagnostic.code) or ''
  1730. end)
  1731. local suffix, suffix_hl_group --- @type string?, string?
  1732. if suffix_opt then
  1733. vim.validate(
  1734. 'suffix',
  1735. suffix_opt,
  1736. { 'string', 'table', 'function' },
  1737. "'string' or 'table' or 'function'"
  1738. )
  1739. if type(suffix_opt) == 'string' then
  1740. suffix, suffix_hl_group = suffix_opt, 'NormalFloat'
  1741. elseif type(suffix_opt) == 'table' then
  1742. suffix, suffix_hl_group = suffix_opt[1] or '', suffix_opt[2] or 'NormalFloat'
  1743. end
  1744. end
  1745. for i, diagnostic in ipairs(diagnostics) do
  1746. if type(prefix_opt) == 'function' then
  1747. --- @cast prefix_opt fun(...): string?, string?
  1748. local prefix0, prefix_hl_group0 = prefix_opt(diagnostic, i, #diagnostics)
  1749. prefix, prefix_hl_group = prefix0 or '', prefix_hl_group0 or 'NormalFloat'
  1750. end
  1751. if type(suffix_opt) == 'function' then
  1752. --- @cast suffix_opt fun(...): string?, string?
  1753. local suffix0, suffix_hl_group0 = suffix_opt(diagnostic, i, #diagnostics)
  1754. suffix, suffix_hl_group = suffix0 or '', suffix_hl_group0 or 'NormalFloat'
  1755. end
  1756. --- @type string?
  1757. local hiname = floating_highlight_map[assert(diagnostic.severity)]
  1758. local message_lines = vim.split(diagnostic.message, '\n')
  1759. for j = 1, #message_lines do
  1760. local pre = j == 1 and prefix or string.rep(' ', #prefix)
  1761. local suf = j == #message_lines and suffix or ''
  1762. table.insert(lines, pre .. message_lines[j] .. suf)
  1763. table.insert(highlights, {
  1764. hlname = hiname,
  1765. prefix = {
  1766. length = j == 1 and #prefix or 0,
  1767. hlname = prefix_hl_group,
  1768. },
  1769. suffix = {
  1770. length = j == #message_lines and #suffix or 0,
  1771. hlname = suffix_hl_group,
  1772. },
  1773. })
  1774. end
  1775. end
  1776. -- Used by open_floating_preview to allow the float to be focused
  1777. if not opts.focus_id then
  1778. opts.focus_id = scope
  1779. end
  1780. local float_bufnr, winnr = vim.lsp.util.open_floating_preview(lines, 'plaintext', opts)
  1781. vim.bo[float_bufnr].path = vim.bo[bufnr].path
  1782. for i, hl in ipairs(highlights) do
  1783. local line = lines[i]
  1784. local prefix_len = hl.prefix and hl.prefix.length or 0
  1785. local suffix_len = hl.suffix and hl.suffix.length or 0
  1786. if prefix_len > 0 then
  1787. api.nvim_buf_add_highlight(float_bufnr, -1, hl.prefix.hlname, i - 1, 0, prefix_len)
  1788. end
  1789. api.nvim_buf_add_highlight(float_bufnr, -1, hl.hlname, i - 1, prefix_len, #line - suffix_len)
  1790. if suffix_len > 0 then
  1791. api.nvim_buf_add_highlight(float_bufnr, -1, hl.suffix.hlname, i - 1, #line - suffix_len, -1)
  1792. end
  1793. end
  1794. return float_bufnr, winnr
  1795. end
  1796. --- Remove all diagnostics from the given namespace.
  1797. ---
  1798. --- Unlike |vim.diagnostic.hide()|, this function removes all saved
  1799. --- diagnostics. They cannot be redisplayed using |vim.diagnostic.show()|. To
  1800. --- simply remove diagnostic decorations in a way that they can be
  1801. --- re-displayed, use |vim.diagnostic.hide()|.
  1802. ---
  1803. ---@param namespace integer? Diagnostic namespace. When omitted, remove
  1804. --- diagnostics from all namespaces.
  1805. ---@param bufnr integer? Remove diagnostics for the given buffer. When omitted,
  1806. --- diagnostics are removed for all buffers.
  1807. function M.reset(namespace, bufnr)
  1808. vim.validate('namespace', namespace, 'number', true)
  1809. vim.validate('bufnr', bufnr, 'number', true)
  1810. local buffers = bufnr and { vim._resolve_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
  1811. for _, iter_bufnr in ipairs(buffers) do
  1812. local namespaces = namespace and { namespace } or vim.tbl_keys(diagnostic_cache[iter_bufnr])
  1813. for _, iter_namespace in ipairs(namespaces) do
  1814. diagnostic_cache[iter_bufnr][iter_namespace] = nil
  1815. M.hide(iter_namespace, iter_bufnr)
  1816. end
  1817. if api.nvim_buf_is_valid(iter_bufnr) then
  1818. api.nvim_exec_autocmds('DiagnosticChanged', {
  1819. modeline = false,
  1820. buffer = iter_bufnr,
  1821. data = { diagnostics = {} },
  1822. })
  1823. else
  1824. diagnostic_cache[iter_bufnr] = nil
  1825. end
  1826. end
  1827. end
  1828. --- Configuration table with the following keys:
  1829. --- @class vim.diagnostic.setqflist.Opts
  1830. --- @inlinedoc
  1831. ---
  1832. --- Only add diagnostics from the given namespace.
  1833. --- @field namespace? integer
  1834. ---
  1835. --- Open quickfix list after setting.
  1836. --- (default: `true`)
  1837. --- @field open? boolean
  1838. ---
  1839. --- Title of quickfix list. Defaults to "Diagnostics". If there's already a quickfix list with this
  1840. --- title, it's updated. If not, a new quickfix list is created.
  1841. --- @field title? string
  1842. ---
  1843. --- See |diagnostic-severity|.
  1844. --- @field severity? vim.diagnostic.SeverityFilter
  1845. --- Add all diagnostics to the quickfix list.
  1846. ---
  1847. ---@param opts? vim.diagnostic.setqflist.Opts
  1848. function M.setqflist(opts)
  1849. set_list(false, opts)
  1850. end
  1851. ---Configuration table with the following keys:
  1852. --- @class vim.diagnostic.setloclist.Opts
  1853. --- @inlinedoc
  1854. ---
  1855. --- Only add diagnostics from the given namespace.
  1856. --- @field namespace? integer
  1857. ---
  1858. --- Window number to set location list for.
  1859. --- (default: `0`)
  1860. --- @field winnr? integer
  1861. ---
  1862. --- Open the location list after setting.
  1863. --- (default: `true`)
  1864. --- @field open? boolean
  1865. ---
  1866. --- Title of the location list. Defaults to "Diagnostics".
  1867. --- @field title? string
  1868. ---
  1869. --- See |diagnostic-severity|.
  1870. --- @field severity? vim.diagnostic.SeverityFilter
  1871. --- Add buffer diagnostics to the location list.
  1872. ---
  1873. ---@param opts? vim.diagnostic.setloclist.Opts
  1874. function M.setloclist(opts)
  1875. set_list(true, opts)
  1876. end
  1877. --- @deprecated use `vim.diagnostic.enable(false, …)`
  1878. function M.disable(bufnr, namespace)
  1879. vim.deprecate('vim.diagnostic.disable()', 'vim.diagnostic.enable(false, …)', '0.12')
  1880. M.enable(false, { bufnr = bufnr, ns_id = namespace })
  1881. end
  1882. --- Enables or disables diagnostics.
  1883. ---
  1884. --- To "toggle", pass the inverse of `is_enabled()`:
  1885. ---
  1886. --- ```lua
  1887. --- vim.diagnostic.enable(not vim.diagnostic.is_enabled())
  1888. --- ```
  1889. ---
  1890. --- @param enable (boolean|nil) true/nil to enable, false to disable
  1891. --- @param filter vim.diagnostic.Filter?
  1892. function M.enable(enable, filter)
  1893. -- Deprecated signature. Drop this in 0.12
  1894. local legacy = (enable or filter)
  1895. and vim.tbl_contains({ 'number', 'nil' }, type(enable))
  1896. and vim.tbl_contains({ 'number', 'nil' }, type(filter))
  1897. if legacy then
  1898. vim.deprecate(
  1899. 'vim.diagnostic.enable(buf:number, namespace:number)',
  1900. 'vim.diagnostic.enable(enable:boolean, filter:table)',
  1901. '0.12'
  1902. )
  1903. vim.validate('enable', enable, 'number', true) -- Legacy `bufnr` arg.
  1904. vim.validate('filter', filter, 'number', true) -- Legacy `namespace` arg.
  1905. local ns_id = type(filter) == 'number' and filter or nil
  1906. filter = {}
  1907. filter.ns_id = ns_id
  1908. filter.bufnr = type(enable) == 'number' and enable or nil
  1909. enable = true
  1910. else
  1911. filter = filter or {}
  1912. vim.validate('enable', enable, 'boolean', true)
  1913. vim.validate('filter', filter, 'table', true)
  1914. end
  1915. enable = enable == nil and true or enable
  1916. local bufnr = filter.bufnr
  1917. local ns_id = filter.ns_id
  1918. if not bufnr then
  1919. if not ns_id then
  1920. diagnostic_disabled = (
  1921. enable
  1922. -- Enable everything by setting diagnostic_disabled to an empty table.
  1923. and {}
  1924. -- Disable everything (including as yet non-existing buffers and namespaces) by setting
  1925. -- diagnostic_disabled to an empty table and set its metatable to always return true.
  1926. or setmetatable({}, {
  1927. __index = function()
  1928. return true
  1929. end,
  1930. })
  1931. )
  1932. else
  1933. local ns = M.get_namespace(ns_id)
  1934. ns.disabled = not enable
  1935. end
  1936. else
  1937. bufnr = vim._resolve_bufnr(bufnr)
  1938. if not ns_id then
  1939. diagnostic_disabled[bufnr] = (not enable) and true or nil
  1940. else
  1941. if type(diagnostic_disabled[bufnr]) ~= 'table' then
  1942. if enable then
  1943. return
  1944. else
  1945. diagnostic_disabled[bufnr] = {}
  1946. end
  1947. end
  1948. diagnostic_disabled[bufnr][ns_id] = (not enable) and true or nil
  1949. end
  1950. end
  1951. if enable then
  1952. M.show(ns_id, bufnr)
  1953. else
  1954. M.hide(ns_id, bufnr)
  1955. end
  1956. end
  1957. --- Parse a diagnostic from a string.
  1958. ---
  1959. --- For example, consider a line of output from a linter:
  1960. ---
  1961. --- ```
  1962. --- WARNING filename:27:3: Variable 'foo' does not exist
  1963. --- ```
  1964. ---
  1965. --- This can be parsed into |vim.Diagnostic| structure with:
  1966. ---
  1967. --- ```lua
  1968. --- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
  1969. --- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
  1970. --- local groups = { "severity", "lnum", "col", "message" }
  1971. --- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
  1972. --- ```
  1973. ---
  1974. ---@param str string String to parse diagnostics from.
  1975. ---@param pat string Lua pattern with capture groups.
  1976. ---@param groups string[] List of fields in a |vim.Diagnostic| structure to
  1977. --- associate with captures from {pat}.
  1978. ---@param severity_map table A table mapping the severity field from {groups}
  1979. --- with an item from |vim.diagnostic.severity|.
  1980. ---@param defaults table? Table of default values for any fields not listed in {groups}.
  1981. --- When omitted, numeric values default to 0 and "severity" defaults to
  1982. --- ERROR.
  1983. ---@return vim.Diagnostic?: |vim.Diagnostic| structure or `nil` if {pat} fails to match {str}.
  1984. function M.match(str, pat, groups, severity_map, defaults)
  1985. vim.validate('str', str, 'string')
  1986. vim.validate('pat', pat, 'string')
  1987. vim.validate('groups', groups, 'table')
  1988. vim.validate('severity_map', severity_map, 'table', true)
  1989. vim.validate('defaults', defaults, 'table', true)
  1990. --- @type table<string,vim.diagnostic.Severity>
  1991. severity_map = severity_map or M.severity
  1992. local matches = { str:match(pat) } --- @type any[]
  1993. if vim.tbl_isempty(matches) then
  1994. return
  1995. end
  1996. local diagnostic = {} --- @type type<string,any>
  1997. for i, match in ipairs(matches) do
  1998. local field = groups[i]
  1999. if field == 'severity' then
  2000. match = severity_map[match]
  2001. elseif field == 'lnum' or field == 'end_lnum' or field == 'col' or field == 'end_col' then
  2002. match = assert(tonumber(match)) - 1
  2003. end
  2004. diagnostic[field] = match --- @type any
  2005. end
  2006. diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {}) --- @type vim.Diagnostic
  2007. diagnostic.severity = diagnostic.severity or M.severity.ERROR
  2008. diagnostic.col = diagnostic.col or 0
  2009. diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
  2010. diagnostic.end_col = diagnostic.end_col or diagnostic.col
  2011. return diagnostic
  2012. end
  2013. local errlist_type_map = {
  2014. [M.severity.ERROR] = 'E',
  2015. [M.severity.WARN] = 'W',
  2016. [M.severity.INFO] = 'I',
  2017. [M.severity.HINT] = 'N',
  2018. }
  2019. --- Convert a list of diagnostics to a list of quickfix items that can be
  2020. --- passed to |setqflist()| or |setloclist()|.
  2021. ---
  2022. ---@param diagnostics vim.Diagnostic[]
  2023. ---@return table[] : Quickfix list items |setqflist-what|
  2024. function M.toqflist(diagnostics)
  2025. vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  2026. local list = {} --- @type table[]
  2027. for _, v in ipairs(diagnostics) do
  2028. local item = {
  2029. bufnr = v.bufnr,
  2030. lnum = v.lnum + 1,
  2031. col = v.col and (v.col + 1) or nil,
  2032. end_lnum = v.end_lnum and (v.end_lnum + 1) or nil,
  2033. end_col = v.end_col and (v.end_col + 1) or nil,
  2034. text = v.message,
  2035. type = errlist_type_map[v.severity] or 'E',
  2036. }
  2037. table.insert(list, item)
  2038. end
  2039. table.sort(list, function(a, b)
  2040. if a.bufnr == b.bufnr then
  2041. if a.lnum == b.lnum then
  2042. return a.col < b.col
  2043. else
  2044. return a.lnum < b.lnum
  2045. end
  2046. else
  2047. return a.bufnr < b.bufnr
  2048. end
  2049. end)
  2050. return list
  2051. end
  2052. --- Convert a list of quickfix items to a list of diagnostics.
  2053. ---
  2054. ---@param list table[] List of quickfix items from |getqflist()| or |getloclist()|.
  2055. ---@return vim.Diagnostic[]
  2056. function M.fromqflist(list)
  2057. vim.validate('list', list, 'table')
  2058. local diagnostics = {} --- @type vim.Diagnostic[]
  2059. for _, item in ipairs(list) do
  2060. if item.valid == 1 then
  2061. local lnum = math.max(0, item.lnum - 1)
  2062. local col = math.max(0, item.col - 1)
  2063. local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum
  2064. local end_col = item.end_col > 0 and (item.end_col - 1) or col
  2065. local severity = item.type ~= '' and M.severity[item.type] or M.severity.ERROR
  2066. diagnostics[#diagnostics + 1] = {
  2067. bufnr = item.bufnr,
  2068. lnum = lnum,
  2069. col = col,
  2070. end_lnum = end_lnum,
  2071. end_col = end_col,
  2072. severity = severity,
  2073. message = item.text,
  2074. }
  2075. end
  2076. end
  2077. return diagnostics
  2078. end
  2079. return M