peek.lua 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. local M = {
  2. floating_buf = nil,
  3. floating_win = nil,
  4. prev_result = nil,
  5. }
  6. local function create_floating_file(location, opts)
  7. vim.validate {
  8. location = { location, "t" },
  9. opts = { opts, "t", true },
  10. }
  11. -- Set some defaults
  12. opts = opts or {}
  13. local close_events = opts.close_events or { "CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre" }
  14. -- location may be LocationLink or Location
  15. local uri = location.targetUri or location.uri
  16. if uri == nil then
  17. return
  18. end
  19. local bufnr = vim.uri_to_bufnr(uri)
  20. if not vim.api.nvim_buf_is_loaded(bufnr) then
  21. vim.fn.bufload(bufnr)
  22. end
  23. local range = location.targetRange or location.range
  24. local contents = vim.api.nvim_buf_get_lines(
  25. bufnr,
  26. range.start.line,
  27. math.min(
  28. range["end"].line + 1 + (opts.context or lvim.lsp.peek.max_height),
  29. range.start.line + (opts.max_height or lvim.lsp.peek.max_height)
  30. ),
  31. false
  32. )
  33. if next(contents) == nil then
  34. vim.notify("peek: Unable to get contents of the file!", vim.log.levels.WARN)
  35. return
  36. end
  37. local width, height = vim.lsp.util._make_floating_popup_size(contents, opts)
  38. local if_nil = vim.F.if_nil
  39. opts = vim.lsp.util.make_floating_popup_options(
  40. if_nil(width, lvim.lsp.peek.max_width),
  41. if_nil(height, lvim.lsp.peek.max_height),
  42. opts
  43. )
  44. -- Don't make it minimal as it is meant to be fully featured
  45. opts["style"] = nil
  46. vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
  47. local winnr = vim.api.nvim_open_win(bufnr, false, opts)
  48. vim.api.nvim_win_set_option(winnr, "winblend", 0)
  49. vim.api.nvim_win_set_cursor(winnr, { range.start.line + 1, range.start.character })
  50. vim.api.nvim_buf_set_var(bufnr, "lsp_floating_window", winnr)
  51. -- Set some autocmds to close the window
  52. vim.api.nvim_command(
  53. string.format("autocmd %s <buffer> ++once lua pcall(vim.api.nvim_win_close, %d, true)", unpack(close_events), winnr)
  54. )
  55. return bufnr, winnr
  56. end
  57. local function preview_location_callback(result)
  58. if result == nil or vim.tbl_isempty(result) then
  59. return nil
  60. end
  61. local opts = {
  62. border = "rounded",
  63. context = lvim.lsp.peek.context,
  64. }
  65. if vim.tbl_islist(result) then
  66. M.prev_result = result[1]
  67. M.floating_buf, M.floating_win = create_floating_file(result[1], opts)
  68. else
  69. M.prev_result = result
  70. M.floating_buf, M.floating_win = create_floating_file(result, opts)
  71. end
  72. end
  73. local function preview_location_callback_new_signature(_, result)
  74. return preview_location_callback(result)
  75. end
  76. function M.open_file()
  77. -- Get the file currently open in the floating window
  78. local filepath = vim.fn.expand "%:."
  79. if not filepath then
  80. vim.notify("peek: Unable to open the file!", vim.log.levels.ERROR)
  81. return
  82. end
  83. -- Close the floating window
  84. pcall(vim.api.nvim_win_close, M.floating_win, true)
  85. -- Edit the file
  86. vim.cmd("edit " .. filepath)
  87. local winnr = vim.api.nvim_get_current_win()
  88. -- Set the cursor at the right position
  89. M.set_cursor_to_prev_pos(winnr)
  90. end
  91. function M.set_cursor_to_prev_pos(winnr)
  92. -- Get position of the thing to peek at
  93. local location = M.prev_result
  94. local range = location.targetRange or location.range
  95. local cursor_pos = { range.start.line + 1, range.start.character }
  96. -- Set the winnr to the floating window if none was passed in
  97. winnr = winnr or M.floating_win
  98. -- Set the cursor at the correct position in the floating window
  99. vim.api.nvim_win_set_cursor(winnr, cursor_pos)
  100. end
  101. function M.Peek(what)
  102. -- If a window already exists, focus it at the right position!
  103. if vim.tbl_contains(vim.api.nvim_list_wins(), M.floating_win) then
  104. local success_1, _ = pcall(vim.api.nvim_set_current_win, M.floating_win)
  105. if not success_1 then
  106. vim.notify("peek: You cannot edit the current file in a preview!", vim.log.levels.ERROR)
  107. return
  108. end
  109. -- Set the cursor at the correct position in the floating window
  110. M.set_cursor_to_prev_pos()
  111. vim.api.nvim_buf_set_keymap(
  112. M.floating_buf,
  113. "n",
  114. "<CR>",
  115. ":lua require('lvim.lsp.peek').open_file()<CR>",
  116. { noremap = true, silent = true }
  117. )
  118. else
  119. -- Make a new request and then create the new window in the callback
  120. local params = vim.lsp.util.make_position_params()
  121. local preview_callback = preview_location_callback_new_signature
  122. local success, _ = pcall(vim.lsp.buf_request, 0, "textDocument/" .. what, params, preview_callback)
  123. if not success then
  124. vim.notify(
  125. 'peek: Error calling LSP method "textDocument/' .. what .. '". The current language lsp might not support it.',
  126. vim.log.levels.ERROR
  127. )
  128. end
  129. end
  130. end
  131. return M