diagnostic_spec.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local t_lsp = require('test.functional.plugin.lsp.testutil')
  4. local clear = n.clear
  5. local exec_lua = n.exec_lua
  6. local eq = t.eq
  7. local neq = t.neq
  8. local create_server_definition = t_lsp.create_server_definition
  9. describe('vim.lsp.diagnostic', function()
  10. local fake_uri --- @type string
  11. local client_id --- @type integer
  12. local diagnostic_bufnr --- @type integer
  13. before_each(function()
  14. clear { env = {
  15. NVIM_LUA_NOTRACK = '1',
  16. VIMRUNTIME = os.getenv 'VIMRUNTIME',
  17. } }
  18. exec_lua(function()
  19. require('vim.lsp')
  20. _G.make_range = function(x1, y1, x2, y2)
  21. return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } }
  22. end
  23. _G.make_error = function(msg, x1, y1, x2, y2)
  24. return {
  25. range = _G.make_range(x1, y1, x2, y2),
  26. message = msg,
  27. severity = 1,
  28. }
  29. end
  30. _G.make_warning = function(msg, x1, y1, x2, y2)
  31. return {
  32. range = _G.make_range(x1, y1, x2, y2),
  33. message = msg,
  34. severity = 2,
  35. }
  36. end
  37. _G.make_information = function(msg, x1, y1, x2, y2)
  38. return {
  39. range = _G.make_range(x1, y1, x2, y2),
  40. message = msg,
  41. severity = 3,
  42. }
  43. end
  44. function _G.get_extmarks(bufnr, client_id0)
  45. local namespace = vim.lsp.diagnostic.get_namespace(client_id0)
  46. local ns = vim.diagnostic.get_namespace(namespace)
  47. local extmarks = {}
  48. if ns.user_data.virt_text_ns then
  49. for _, e in
  50. pairs(
  51. vim.api.nvim_buf_get_extmarks(
  52. bufnr,
  53. ns.user_data.virt_text_ns,
  54. 0,
  55. -1,
  56. { details = true }
  57. )
  58. )
  59. do
  60. table.insert(extmarks, e)
  61. end
  62. end
  63. if ns.user_data.underline_ns then
  64. for _, e in
  65. pairs(
  66. vim.api.nvim_buf_get_extmarks(
  67. bufnr,
  68. ns.user_data.underline_ns,
  69. 0,
  70. -1,
  71. { details = true }
  72. )
  73. )
  74. do
  75. table.insert(extmarks, e)
  76. end
  77. end
  78. return extmarks
  79. end
  80. client_id = assert(vim.lsp.start({
  81. cmd_env = {
  82. NVIM_LUA_NOTRACK = '1',
  83. },
  84. cmd = {
  85. vim.v.progpath,
  86. '-es',
  87. '-u',
  88. 'NONE',
  89. '--headless',
  90. },
  91. offset_encoding = 'utf-16',
  92. }, { attach = false }))
  93. end)
  94. fake_uri = 'file:///fake/uri'
  95. exec_lua(function()
  96. diagnostic_bufnr = vim.uri_to_bufnr(fake_uri)
  97. local lines = { '1st line of text', '2nd line of text', 'wow', 'cool', 'more', 'lines' }
  98. vim.fn.bufload(diagnostic_bufnr)
  99. vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines)
  100. vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
  101. end)
  102. end)
  103. after_each(function()
  104. clear()
  105. end)
  106. describe('vim.lsp.diagnostic.on_publish_diagnostics', function()
  107. it('correctly handles UTF-16 offsets', function()
  108. local line = 'All 💼 and no 🎉 makes Jack a dull 👦'
  109. local result = exec_lua(function()
  110. vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, -1, false, { line })
  111. vim.lsp.diagnostic.on_publish_diagnostics(nil, {
  112. uri = fake_uri,
  113. diagnostics = {
  114. _G.make_error('UTF-16 Diagnostic', 0, 7, 0, 8),
  115. },
  116. }, { client_id = client_id })
  117. local diags = vim.diagnostic.get(diagnostic_bufnr)
  118. vim.lsp.stop_client(client_id)
  119. vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
  120. return diags
  121. end)
  122. eq(1, #result)
  123. eq(
  124. exec_lua(function()
  125. return vim.str_byteindex(line, 'utf-16', 7)
  126. end),
  127. result[1].col
  128. )
  129. eq(
  130. exec_lua(function()
  131. return vim.str_byteindex(line, 'utf-16', 8)
  132. end),
  133. result[1].end_col
  134. )
  135. end)
  136. it('does not create buffer on empty diagnostics', function()
  137. -- No buffer is created without diagnostics
  138. eq(
  139. -1,
  140. exec_lua(function()
  141. vim.lsp.diagnostic.on_publish_diagnostics(nil, {
  142. uri = 'file:///fake/uri2',
  143. diagnostics = {},
  144. }, { client_id = client_id })
  145. return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2'))
  146. end)
  147. )
  148. -- Create buffer on diagnostics
  149. neq(
  150. -1,
  151. exec_lua(function()
  152. vim.lsp.diagnostic.on_publish_diagnostics(nil, {
  153. uri = 'file:///fake/uri2',
  154. diagnostics = {
  155. _G.make_error('Diagnostic', 0, 0, 0, 0),
  156. },
  157. }, { client_id = client_id })
  158. return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2'))
  159. end)
  160. )
  161. eq(
  162. 1,
  163. exec_lua(function()
  164. return #vim.diagnostic.get(_G.bufnr)
  165. end)
  166. )
  167. -- Clear diagnostics after buffer was created
  168. neq(
  169. -1,
  170. exec_lua(function()
  171. vim.lsp.diagnostic.on_publish_diagnostics(nil, {
  172. uri = 'file:///fake/uri2',
  173. diagnostics = {},
  174. }, { client_id = client_id })
  175. return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2'))
  176. end)
  177. )
  178. eq(
  179. 0,
  180. exec_lua(function()
  181. return #vim.diagnostic.get(_G.bufnr)
  182. end)
  183. )
  184. end)
  185. end)
  186. describe('vim.lsp.diagnostic.on_diagnostic', function()
  187. before_each(function()
  188. exec_lua(create_server_definition)
  189. exec_lua(function()
  190. _G.requests = 0
  191. _G.server = _G._create_server({
  192. capabilities = {
  193. diagnosticProvider = {},
  194. },
  195. handlers = {
  196. [vim.lsp.protocol.Methods.textDocument_diagnostic] = function()
  197. _G.requests = _G.requests + 1
  198. end,
  199. },
  200. })
  201. function _G.get_extmarks(bufnr, client_id0)
  202. local namespace = vim.lsp.diagnostic.get_namespace(client_id0, true)
  203. local ns = vim.diagnostic.get_namespace(namespace)
  204. local extmarks = {}
  205. if ns.user_data.virt_text_ns then
  206. for _, e in
  207. pairs(
  208. vim.api.nvim_buf_get_extmarks(
  209. bufnr,
  210. ns.user_data.virt_text_ns,
  211. 0,
  212. -1,
  213. { details = true }
  214. )
  215. )
  216. do
  217. table.insert(extmarks, e)
  218. end
  219. end
  220. if ns.user_data.underline_ns then
  221. for _, e in
  222. pairs(
  223. vim.api.nvim_buf_get_extmarks(
  224. bufnr,
  225. ns.user_data.underline_ns,
  226. 0,
  227. -1,
  228. { details = true }
  229. )
  230. )
  231. do
  232. table.insert(extmarks, e)
  233. end
  234. end
  235. return extmarks
  236. end
  237. client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
  238. end)
  239. end)
  240. it('adds diagnostics to vim.diagnostics', function()
  241. local diags = exec_lua(function()
  242. vim.lsp.diagnostic.on_diagnostic(nil, {
  243. kind = 'full',
  244. items = {
  245. _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
  246. },
  247. }, {
  248. params = {
  249. textDocument = { uri = fake_uri },
  250. },
  251. uri = fake_uri,
  252. client_id = client_id,
  253. }, {})
  254. return vim.diagnostic.get(diagnostic_bufnr)
  255. end)
  256. eq(1, #diags)
  257. eq('Pull Diagnostic', diags[1].message)
  258. end)
  259. it('severity defaults to error if missing', function()
  260. ---@type vim.Diagnostic[]
  261. local diagnostics = exec_lua(function()
  262. vim.lsp.diagnostic.on_diagnostic(nil, {
  263. kind = 'full',
  264. items = {
  265. {
  266. range = _G.make_range(4, 4, 4, 4),
  267. message = 'bad!',
  268. },
  269. },
  270. }, {
  271. params = {
  272. textDocument = { uri = fake_uri },
  273. },
  274. uri = fake_uri,
  275. client_id = client_id,
  276. }, {})
  277. return vim.diagnostic.get(diagnostic_bufnr)
  278. end)
  279. eq(1, #diagnostics)
  280. eq(1, diagnostics[1].severity)
  281. end)
  282. it('clears diagnostics when client detaches', function()
  283. exec_lua(function()
  284. vim.lsp.diagnostic.on_diagnostic(nil, {
  285. kind = 'full',
  286. items = {
  287. _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
  288. },
  289. }, {
  290. params = {
  291. textDocument = { uri = fake_uri },
  292. },
  293. uri = fake_uri,
  294. client_id = client_id,
  295. }, {})
  296. end)
  297. eq(
  298. 1,
  299. exec_lua(function()
  300. return #vim.diagnostic.get(diagnostic_bufnr)
  301. end)
  302. )
  303. exec_lua(function()
  304. vim.lsp.stop_client(client_id)
  305. end)
  306. eq(
  307. 0,
  308. exec_lua(function()
  309. return #vim.diagnostic.get(diagnostic_bufnr)
  310. end)
  311. )
  312. end)
  313. it('keeps diagnostics when one client detaches and others still are attached', function()
  314. local client_id2
  315. exec_lua(function()
  316. client_id2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server.cmd })
  317. vim.lsp.diagnostic.on_diagnostic(nil, {
  318. kind = 'full',
  319. items = {
  320. _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
  321. },
  322. }, {
  323. params = {
  324. textDocument = { uri = fake_uri },
  325. },
  326. uri = fake_uri,
  327. client_id = client_id,
  328. }, {})
  329. end)
  330. eq(
  331. 1,
  332. exec_lua(function()
  333. return #vim.diagnostic.get(diagnostic_bufnr)
  334. end)
  335. )
  336. exec_lua(function()
  337. vim.lsp.stop_client(client_id2)
  338. end)
  339. eq(
  340. 1,
  341. exec_lua(function()
  342. return #vim.diagnostic.get(diagnostic_bufnr)
  343. end)
  344. )
  345. end)
  346. it('handles server cancellation', function()
  347. eq(
  348. 1,
  349. exec_lua(function()
  350. vim.lsp.diagnostic.on_diagnostic({
  351. code = vim.lsp.protocol.ErrorCodes.ServerCancelled,
  352. -- Empty data defaults to retriggering request
  353. data = {},
  354. message = '',
  355. }, {}, {
  356. method = vim.lsp.protocol.Methods.textDocument_diagnostic,
  357. client_id = client_id,
  358. })
  359. return _G.requests
  360. end)
  361. )
  362. eq(
  363. 2,
  364. exec_lua(function()
  365. vim.lsp.diagnostic.on_diagnostic({
  366. code = vim.lsp.protocol.ErrorCodes.ServerCancelled,
  367. data = { retriggerRequest = true },
  368. message = '',
  369. }, {}, {
  370. method = vim.lsp.protocol.Methods.textDocument_diagnostic,
  371. client_id = client_id,
  372. })
  373. return _G.requests
  374. end)
  375. )
  376. eq(
  377. 2,
  378. exec_lua(function()
  379. vim.lsp.diagnostic.on_diagnostic({
  380. code = vim.lsp.protocol.ErrorCodes.ServerCancelled,
  381. data = { retriggerRequest = false },
  382. message = '',
  383. }, {}, {
  384. method = vim.lsp.protocol.Methods.textDocument_diagnostic,
  385. client_id = client_id,
  386. })
  387. return _G.requests
  388. end)
  389. )
  390. end)
  391. end)
  392. end)