utils_spec.lua (15042B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local feed = n.feed 6 local eq = t.eq 7 local exec_lua = n.exec_lua 8 local command, api = n.command, n.api 9 local pcall_err = t.pcall_err 10 11 describe('vim.lsp.util', function() 12 before_each(n.clear) 13 14 describe('stylize_markdown', function() 15 local stylize_markdown = function(content, opts) 16 return exec_lua(function() 17 local bufnr = vim.uri_to_bufnr('file:///fake/uri') 18 vim.fn.bufload(bufnr) 19 return vim.lsp.util.stylize_markdown(bufnr, content, opts) 20 end) 21 end 22 23 it('code fences', function() 24 local lines = { 25 '```lua', 26 "local hello = 'world'", 27 '```', 28 } 29 local expected = { 30 "local hello = 'world'", 31 } 32 local opts = {} 33 eq(expected, stylize_markdown(lines, opts)) 34 end) 35 36 it('code fences with whitespace surrounded info string', function() 37 local lines = { 38 '``` lua ', 39 "local hello = 'world'", 40 '```', 41 } 42 local expected = { 43 "local hello = 'world'", 44 } 45 local opts = {} 46 eq(expected, stylize_markdown(lines, opts)) 47 end) 48 49 it('adds separator after code block', function() 50 local lines = { 51 '```lua', 52 "local hello = 'world'", 53 '```', 54 '', 55 'something', 56 } 57 local expected = { 58 "local hello = 'world'", 59 '─────────────────────', 60 'something', 61 } 62 local opts = { separator = true } 63 eq(expected, stylize_markdown(lines, opts)) 64 end) 65 66 it('replaces supported HTML entities', function() 67 local lines = { 68 '1 < 2', 69 '3 > 2', 70 '"quoted"', 71 ''apos'', 72 '   ', 73 '&', 74 } 75 local expected = { 76 '1 < 2', 77 '3 > 2', 78 '"quoted"', 79 "'apos'", 80 ' ', 81 '&', 82 } 83 local opts = {} 84 eq(expected, stylize_markdown(lines, opts)) 85 end) 86 end) 87 88 it('convert_input_to_markdown_lines', function() 89 local r = exec_lua(function() 90 local hover_data = { 91 kind = 'markdown', 92 value = '```lua\nfunction vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)\n -> boolean\n```\n\n---\n\n Activates buffer-update events. Example:\n\n\n\n ```lua\n events = {}\n vim.api.nvim_buf_attach(0, false, {\n on_lines = function(...)\n table.insert(events, {...})\n end,\n })\n ```\n\n\n @see `nvim_buf_detach()`\n @see `api-buffer-updates-lua`\n@*param* `buffer` — Buffer handle, or 0 for current buffer\n\n\n\n@*param* `send_buffer` — True if whole buffer.\n Else the first notification will be `nvim_buf_changedtick_event`.\n\n\n@*param* `opts` — Optional parameters.\n\n - on_lines: Lua callback. Args:\n - the string "lines"\n - buffer handle\n - b:changedtick\n@*return* — False if foo;\n\n otherwise True.\n\n@see foo\n@see bar\n\n', 93 } 94 return vim.lsp.util.convert_input_to_markdown_lines(hover_data) 95 end) 96 local expected = { 97 '```lua', 98 'function vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)', 99 ' -> boolean', 100 '```', 101 '', 102 '---', 103 '', 104 ' Activates buffer-update events. Example:', 105 '', 106 '', 107 '', 108 ' ```lua', 109 ' events = {}', 110 ' vim.api.nvim_buf_attach(0, false, {', 111 ' on_lines = function(...)', 112 ' table.insert(events, {...})', 113 ' end,', 114 ' })', 115 ' ```', 116 '', 117 '', 118 ' @see `nvim_buf_detach()`', 119 ' @see `api-buffer-updates-lua`', 120 '', 121 -- For each @param/@return: #30695 122 -- - Separate each by one empty line. 123 -- - Remove all other blank lines. 124 '@*param* `buffer` — Buffer handle, or 0 for current buffer', 125 '', 126 '@*param* `send_buffer` — True if whole buffer.', 127 ' Else the first notification will be `nvim_buf_changedtick_event`.', 128 '', 129 '@*param* `opts` — Optional parameters.', 130 ' - on_lines: Lua callback. Args:', 131 ' - the string "lines"', 132 ' - buffer handle', 133 ' - b:changedtick', 134 '', 135 '@*return* — False if foo;', 136 ' otherwise True.', 137 '@see foo', 138 '@see bar', 139 } 140 eq(expected, r) 141 end) 142 143 describe('_normalize_markdown', function() 144 it('collapses consecutive blank lines', function() 145 local result = exec_lua(function() 146 local lines = { 147 'foo', 148 '', 149 '', 150 '', 151 'bar', 152 '', 153 'baz', 154 } 155 return vim.lsp.util._normalize_markdown(lines) 156 end) 157 local expected = { 'foo', '', 'bar', '', 'baz' } 158 eq(expected, result) 159 end) 160 161 it('removes preceding and trailing empty lines', function() 162 local result = exec_lua(function() 163 local lines = { 164 '', 165 'foo', 166 'bar', 167 '', 168 '', 169 } 170 return vim.lsp.util._normalize_markdown(lines) 171 end) 172 local expected = { 'foo', 'bar' } 173 eq(expected, result) 174 end) 175 end) 176 177 describe('make_floating_popup_options', function() 178 local function assert_anchor(anchor_bias, expected_anchor) 179 local opts = exec_lua(function() 180 return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias }) 181 end) 182 183 eq(expected_anchor, string.sub(opts.anchor, 1, 1)) 184 end 185 186 before_each(function() 187 local _ = Screen.new(80, 80) 188 feed('79i<CR><Esc>') -- fill screen with empty lines 189 end) 190 191 describe('when on the first line it places window below', function() 192 before_each(function() 193 feed('gg') 194 end) 195 196 it('for anchor_bias = "auto"', function() 197 assert_anchor('auto', 'N') 198 end) 199 200 it('for anchor_bias = "above"', function() 201 assert_anchor('above', 'N') 202 end) 203 204 it('for anchor_bias = "below"', function() 205 assert_anchor('below', 'N') 206 end) 207 end) 208 209 describe('when on the last line it places window above', function() 210 before_each(function() 211 feed('G') 212 end) 213 214 it('for anchor_bias = "auto"', function() 215 assert_anchor('auto', 'S') 216 end) 217 218 it('for anchor_bias = "above"', function() 219 assert_anchor('above', 'S') 220 end) 221 222 it('for anchor_bias = "below"', function() 223 assert_anchor('below', 'S') 224 end) 225 end) 226 227 describe('with 20 lines above, 59 lines below', function() 228 before_each(function() 229 feed('gg20j') 230 end) 231 232 it('places window below for anchor_bias = "auto"', function() 233 assert_anchor('auto', 'N') 234 end) 235 236 it('places window above for anchor_bias = "above"', function() 237 assert_anchor('above', 'S') 238 end) 239 240 it('places window below for anchor_bias = "below"', function() 241 assert_anchor('below', 'N') 242 end) 243 end) 244 245 describe('with 59 lines above, 20 lines below', function() 246 before_each(function() 247 feed('G20k') 248 end) 249 250 it('places window above for anchor_bias = "auto"', function() 251 assert_anchor('auto', 'S') 252 end) 253 254 it('places window above for anchor_bias = "above"', function() 255 assert_anchor('above', 'S') 256 end) 257 258 it('places window below for anchor_bias = "below"', function() 259 assert_anchor('below', 'N') 260 end) 261 262 it('bordered window truncates dimensions correctly', function() 263 local opts = exec_lua(function() 264 return vim.lsp.util.make_floating_popup_options(100, 100, { border = 'single' }) 265 end) 266 267 eq(56, opts.height) 268 end) 269 270 it('title with winborder option #35179', function() 271 local opts = exec_lua(function() 272 vim.o.winborder = 'single' 273 return vim.lsp.util.make_floating_popup_options(100, 100, { title = 'Title' }) 274 end) 275 eq('Title', opts.title) 276 end) 277 end) 278 end) 279 280 describe('open_floating_preview', function() 281 before_each(function() 282 Screen.new(10, 10) 283 feed('9i<CR><Esc>G4k') 284 end) 285 286 local var_name = 'lsp_floating_preview' 287 local curbuf = api.nvim_get_current_buf() 288 289 it('clean bufvar after fclose', function() 290 exec_lua(function() 291 vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) 292 end) 293 eq(true, api.nvim_win_is_valid(api.nvim_buf_get_var(curbuf, var_name))) 294 command('fclose') 295 eq('Key not found: lsp_floating_preview', pcall_err(api.nvim_buf_get_var, curbuf, var_name)) 296 end) 297 298 it('clean bufvar after CursorMoved', function() 299 local result = exec_lua(function() 300 vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) 301 local winnr = vim.b[vim.api.nvim_get_current_buf()].lsp_floating_preview 302 local result = vim.api.nvim_win_is_valid(winnr) 303 vim.api.nvim_feedkeys(vim.keycode('G'), 'txn', false) 304 return result 305 end) 306 eq(true, result) 307 eq('Key not found: lsp_floating_preview', pcall_err(api.nvim_buf_get_var, curbuf, var_name)) 308 end) 309 end) 310 311 it('open_floating_preview zindex greater than current window', function() 312 local screen = Screen.new() 313 exec_lua(function() 314 vim.api.nvim_open_win(0, true, { 315 relative = 'editor', 316 border = 'single', 317 height = 11, 318 width = 51, 319 row = 2, 320 col = 2, 321 }) 322 vim.keymap.set('n', 'K', function() 323 vim.lsp.util.open_floating_preview({ 'foo' }, '', { border = 'single' }) 324 end, {}) 325 end) 326 feed('K') 327 screen:expect([[ 328 ┌───────────────────────────────────────────────────┐| 329 │{4:^ }│| 330 │┌───┐{11: }│| 331 ││{4:foo}│{11: }│| 332 │└───┘{11: }│| 333 │{11:~ }│|*7 334 └───────────────────────────────────────────────────┘| 335 | 336 ]]) 337 end) 338 339 it('open_floating_preview height reduced for concealed lines', function() 340 local screen = Screen.new() 341 screen:add_extra_attr_ids({ 342 [100] = { 343 background = Screen.colors.LightMagenta, 344 foreground = Screen.colors.Brown, 345 bold = true, 346 }, 347 [101] = { background = Screen.colors.LightMagenta, foreground = Screen.colors.Blue }, 348 [102] = { background = Screen.colors.LightMagenta, foreground = Screen.colors.DarkCyan }, 349 }) 350 exec_lua([[ 351 vim.g.syntax_on = false 352 vim.lsp.util.open_floating_preview({ '```lua', 'local foo', '```' }, 'markdown', { 353 border = 'single', 354 focus = false, 355 }) 356 ]]) 357 screen:expect([[ 358 ^ | 359 ┌─────────┐{1: }| 360 │{100:local}{101: }{102:foo}│{1: }| 361 └─────────┘{1: }| 362 {1:~ }|*9 363 | 364 ]]) 365 -- Entering window keeps lines concealed and doesn't end up below inner window size. 366 feed('<C-w>wG') 367 screen:expect([[ 368 | 369 ┌─────────┐{1: }| 370 │{101:^```}{4: }│{1: }| 371 └─────────┘{1: }| 372 {1:~ }|*9 373 | 374 ]]) 375 -- Correct height when float inherits 'conceallevel' >= 2 #32639 376 command('close | set conceallevel=2') 377 feed('<Ignore>') -- Prevent CursorMoved closing the next float immediately 378 exec_lua([[ 379 vim.lsp.util.open_floating_preview({ '```lua', 'local foo', '```' }, 'markdown', { 380 border = 'single', 381 focus = false, 382 }) 383 ]]) 384 screen:expect([[ 385 ^ | 386 ┌─────────┐{1: }| 387 │{100:local}{101: }{102:foo}│{1: }| 388 └─────────┘{1: }| 389 {1:~ }|*9 390 | 391 ]]) 392 -- This tests the valid winline code path (why doesn't the above?). 393 exec_lua([[ 394 vim.cmd.only() 395 vim.lsp.util.open_floating_preview({ 'foo', '```lua', 'local bar', '```' }, 'markdown', { 396 border = 'single', 397 focus = false, 398 }) 399 ]]) 400 feed('<C-W>wG') 401 screen:expect([[ 402 | 403 ┌─────────┐{1: }| 404 │{100:local}{101: }{102:bar}│{1: }| 405 │{101:^```}{4: }│{1: }| 406 └─────────┘{1: }| 407 {1:~ }|*8 408 | 409 ]]) 410 end) 411 412 it('open_floating_preview height does not exceed max_height', function() 413 local screen = Screen.new() 414 exec_lua([[ 415 vim.lsp.util.open_floating_preview(vim.fn.range(1, 10), 'markdown', { 416 border = 'single', 417 width = 5, 418 max_height = 5, 419 focus = false, 420 }) 421 ]]) 422 screen:expect([[ 423 ^ | 424 ┌─────┐{1: }| 425 │{4:1 }│{1: }| 426 │{4:2 }│{1: }| 427 │{4:3 }│{1: }| 428 │{4:4 }│{1: }| 429 │{4:5 }│{1: }| 430 └─────┘{1: }| 431 {1:~ }|*5 432 | 433 ]]) 434 end) 435 end)