mirror of
https://github.com/neovim/neovim.git
synced 2026-03-31 04:42:03 +00:00
feat(lsp): display codelens as virtual lines, not virtual text #36469
Problem: Code lenses currently display as virtual text on the same line and after the relevant item. While the spec does not say how lenses should be rendered, above the line is most typical. For longer lines, lenses rendered as virtual text can run off the side of the screen. Solution: Display lenses as virtual lines above the text. Closes https://github.com/neovim/neovim/issues/33923 Co-authored-by: Yi Ming <ofseed@foxmail.com>
This commit is contained in:
@@ -302,6 +302,7 @@ LSP
|
||||
• Support for `textDocument/semanticTokens/range`.
|
||||
• Support for `textDocument/codeLens` |lsp-codelens| has been reimplemented:
|
||||
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_codeLens
|
||||
• Code lenses now display as virtual lines
|
||||
• Support for `workspace/codeLens/refresh`:
|
||||
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeLens_refresh
|
||||
|
||||
|
||||
@@ -210,9 +210,10 @@ function Provider:on_win(toprow, botrow)
|
||||
for row = toprow, botrow do
|
||||
if self.row_version[row] ~= self.version then
|
||||
for client_id, state in pairs(self.client_state) do
|
||||
local bufnr = self.bufnr
|
||||
local namespace = state.namespace
|
||||
|
||||
api.nvim_buf_clear_namespace(self.bufnr, namespace, row, row + 1)
|
||||
api.nvim_buf_clear_namespace(bufnr, namespace, row, row + 1)
|
||||
|
||||
local lenses = state.row_lenses[row]
|
||||
if lenses then
|
||||
@@ -220,25 +221,37 @@ function Provider:on_win(toprow, botrow)
|
||||
return a.range.start.character < b.range.start.character
|
||||
end)
|
||||
|
||||
---@type [string, string][]
|
||||
local virt_text = {}
|
||||
---@type integer
|
||||
local indent = api.nvim_buf_call(bufnr, function()
|
||||
return vim.fn.indent(row + 1)
|
||||
end)
|
||||
|
||||
---@type [string, string|integer][][]
|
||||
local virt_lines = { { { string.rep(' ', indent), 'LspCodeLensSeparator' } } }
|
||||
local virt_text = virt_lines[1]
|
||||
for _, lens in ipairs(lenses) do
|
||||
-- A code lens is unresolved when no command is associated to it.
|
||||
if not lens.command then
|
||||
local client = assert(vim.lsp.get_client_by_id(client_id))
|
||||
local client = assert(vim.lsp.get_client_by_id(client_id)) ---@type vim.lsp.Client
|
||||
self:resolve(client, lens)
|
||||
else
|
||||
vim.list_extend(virt_text, {
|
||||
{ lens.command.title, 'LspCodeLens' },
|
||||
{ ' | ', 'LspCodeLensSeparator' },
|
||||
})
|
||||
virt_text[#virt_text + 1] = { lens.command.title, 'LspCodeLens' }
|
||||
virt_text[#virt_text + 1] = { ' | ', 'LspCodeLensSeparator' }
|
||||
end
|
||||
end
|
||||
-- Remove trailing separator.
|
||||
table.remove(virt_text)
|
||||
|
||||
api.nvim_buf_set_extmark(self.bufnr, namespace, row, 0, {
|
||||
virt_text = virt_text,
|
||||
if #virt_text > 1 then
|
||||
-- Remove trailing separator.
|
||||
virt_text[#virt_text] = nil
|
||||
else
|
||||
-- Use a placeholder to prevent flickering caused by layout shifts.
|
||||
virt_text[#virt_text + 1] = { '...', 'LspCodeLens' }
|
||||
end
|
||||
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, row, 0, {
|
||||
virt_lines = virt_lines,
|
||||
virt_lines_above = true,
|
||||
virt_lines_overflow = 'scroll',
|
||||
hl_mode = 'combine',
|
||||
})
|
||||
end
|
||||
@@ -246,6 +259,12 @@ function Provider:on_win(toprow, botrow)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if botrow == api.nvim_buf_line_count(self.bufnr) - 1 then
|
||||
for _, state in pairs(self.client_state) do
|
||||
api.nvim_buf_clear_namespace(self.bufnr, state.namespace, botrow, -1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local namespace = api.nvim_create_namespace('nvim.lsp.codelens')
|
||||
|
||||
@@ -16,6 +16,7 @@ local create_server_definition = t_lsp.create_server_definition
|
||||
|
||||
describe('vim.lsp.codelens', function()
|
||||
local text = dedent([[
|
||||
https://github.com/neovim/neovim/issues/16166
|
||||
struct S {
|
||||
a: i32,
|
||||
b: String,
|
||||
@@ -34,7 +35,9 @@ describe('vim.lsp.codelens', function()
|
||||
]])
|
||||
|
||||
local grid_with_lenses = dedent([[
|
||||
struct S { {1:1 implementation} |
|
||||
^https://github.com/neovim/neovim/issues/16166 |
|
||||
{1:1 implementation} |
|
||||
struct S { |
|
||||
a: i32, |
|
||||
b: String, |
|
||||
} |
|
||||
@@ -45,17 +48,18 @@ describe('vim.lsp.codelens', function()
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fn main() { {1:▶︎ Run } |
|
||||
{1:▶︎ Run } |
|
||||
fn main() { |
|
||||
let s = S::new(42, String::from("Hello, world!"))|
|
||||
; |
|
||||
println!("S.a: {}, S.b: {}", s.a, s.b); |
|
||||
} |
|
||||
^ |
|
||||
{1:~ }|*2
|
||||
|
|
||||
|
|
||||
]])
|
||||
|
||||
local grid_without_lenses = dedent([[
|
||||
^https://github.com/neovim/neovim/issues/16166 |
|
||||
struct S { |
|
||||
a: i32, |
|
||||
b: String, |
|
||||
@@ -72,7 +76,7 @@ describe('vim.lsp.codelens', function()
|
||||
; |
|
||||
println!("S.a: {}, S.b: {}", s.a, s.b); |
|
||||
} |
|
||||
^ |
|
||||
|
|
||||
{1:~ }|*2
|
||||
|
|
||||
]])
|
||||
@@ -87,7 +91,7 @@ describe('vim.lsp.codelens', function()
|
||||
clear_notrace()
|
||||
exec_lua(create_server_definition)
|
||||
|
||||
screen = Screen.new(nil, 20)
|
||||
screen = Screen.new(nil, 21)
|
||||
|
||||
client_id = exec_lua(function()
|
||||
_G.server = _G._create_server({
|
||||
@@ -105,7 +109,7 @@ describe('vim.lsp.codelens', function()
|
||||
impls = {
|
||||
position = {
|
||||
character = 7,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -114,11 +118,11 @@ describe('vim.lsp.codelens', function()
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 8,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 7,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -131,11 +135,11 @@ describe('vim.lsp.codelens', function()
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 7,
|
||||
line = 11,
|
||||
line = 12,
|
||||
},
|
||||
start = {
|
||||
character = 3,
|
||||
line = 11,
|
||||
line = 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -152,11 +156,11 @@ describe('vim.lsp.codelens', function()
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 8,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 7,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -174,6 +178,7 @@ describe('vim.lsp.codelens', function()
|
||||
vim.lsp.codelens.enable()
|
||||
end)
|
||||
|
||||
feed('gg')
|
||||
screen:expect({ grid = grid_with_lenses })
|
||||
end)
|
||||
|
||||
@@ -211,11 +216,11 @@ describe('vim.lsp.codelens', function()
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 8,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 7,
|
||||
line = 0,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -231,11 +236,11 @@ describe('vim.lsp.codelens', function()
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 7,
|
||||
line = 11,
|
||||
line = 12,
|
||||
},
|
||||
start = {
|
||||
character = 3,
|
||||
line = 11,
|
||||
line = 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -244,10 +249,12 @@ describe('vim.lsp.codelens', function()
|
||||
end)
|
||||
|
||||
it('refreshes code lenses on request', function()
|
||||
feed('ggdd')
|
||||
feed('2Gdd')
|
||||
|
||||
screen:expect([[
|
||||
^a: i32, {1:1 implementation} |
|
||||
https://github.com/neovim/neovim/issues/16166 |
|
||||
{1:1 implementation} |
|
||||
^a: i32, |
|
||||
b: String, |
|
||||
} |
|
||||
|
|
||||
@@ -257,13 +264,14 @@ describe('vim.lsp.codelens', function()
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fn main() { {1:▶︎ Run } |
|
||||
{1:▶︎ Run } |
|
||||
fn main() { |
|
||||
let s = S::new(42, String::from("Hello, world!"))|
|
||||
; |
|
||||
println!("S.a: {}, S.b: {}", s.a, s.b); |
|
||||
} |
|
||||
|
|
||||
{1:~ }|*3
|
||||
{1:~ }|*1
|
||||
|
|
||||
]])
|
||||
exec_lua(function()
|
||||
@@ -274,7 +282,9 @@ describe('vim.lsp.codelens', function()
|
||||
)
|
||||
end)
|
||||
screen:expect([[
|
||||
^a: i32, {1:1 implementation} |
|
||||
https://github.com/neovim/neovim/issues/16166 |
|
||||
{1: 1 implementation} |
|
||||
^a: i32, |
|
||||
b: String, |
|
||||
} |
|
||||
|
|
||||
@@ -285,16 +295,39 @@ describe('vim.lsp.codelens', function()
|
||||
} |
|
||||
|
|
||||
fn main() { |
|
||||
{1: ▶︎ Run } |
|
||||
let s = S::new(42, String::from("Hello, world!"))|
|
||||
; {1:▶︎ Run } |
|
||||
; |
|
||||
println!("S.a: {}, S.b: {}", s.a, s.b); |
|
||||
} |
|
||||
|
|
||||
{1:~ }|*3
|
||||
{1:~ }|*1
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('clears extmarks beyond the bottom of the buffer', function()
|
||||
feed('13G4dd')
|
||||
screen:expect([[
|
||||
https://github.com/neovim/neovim/issues/16166 |
|
||||
{1:1 implementation} |
|
||||
struct S { |
|
||||
a: i32, |
|
||||
b: String, |
|
||||
} |
|
||||
|
|
||||
impl S { |
|
||||
fn new(a: i32, b: String) -> Self { |
|
||||
S { a, b } |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
^ |
|
||||
{1:~ }|*6
|
||||
4 fewer lines |
|
||||
]])
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user