Skip to content

Commit

Permalink
feat: inlay hints (#38)
Browse files Browse the repository at this point in the history
Co-authored-by: Will Hopkins <willothyh@gmail.com>
  • Loading branch information
tris203 and willothy authored Jul 31, 2024
1 parent 2a566f0 commit 6772d3a
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 38 deletions.
1 change: 1 addition & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"describe",
"it",
"before_each",
"after_each",
]
}
10 changes: 10 additions & 0 deletions lua/precognition/compat.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local M = {}

function M.inlay_hints_enabled(t)
if vim.lsp and vim.lsp.inlay_hint and vim.lsp.inlay_hint.is_enabled then
return vim.lsp.inlay_hint.is_enabled(t)
end
return false
end

return M
26 changes: 22 additions & 4 deletions lua/precognition/init.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local compat = require("precognition.compat")

local M = {}

---@class Precognition.HintOpts
Expand Down Expand Up @@ -109,17 +111,18 @@ local gutter_group = "precognition_gutter"

---@param marks Precognition.VirtLine
---@param line_len integer
---@param extra_padding Precognition.ExtraPadding
---@param extra_padding Precognition.ExtraPadding[]
---@return table
local function build_virt_line(marks, line_len, extra_padding)
local utils = require("precognition.utils")
if not marks then
return {}
end
if line_len == 0 then
return {}
end
local virt_line = {}
local line_table = require("precognition.utils").create_pad_array(line_len, " ")
local line_table = utils.create_pad_array(line_len, " ")

for mark, loc in pairs(marks) do
local hint = config.hints[mark].text or mark
Expand Down Expand Up @@ -224,8 +227,9 @@ local function apply_gutter_hints(gutter_hints, bufnr)
end

local function display_marks()
local utils = require("precognition.utils")
local bufnr = vim.api.nvim_get_current_buf()
if require("precognition.utils").is_blacklisted_buffer(bufnr) then
if utils.is_blacklisted_buffer(bufnr) then
return
end
local cursorline = vim.fn.line(".")
Expand Down Expand Up @@ -263,9 +267,23 @@ local function display_marks()
Zero = 1,
}

if compat.inlay_hints_enabled({ bufnr = 0 }) then
local inlays_hints = vim.lsp.inlay_hint.get({
bufnr = 0,
range = {
start = { line = cursorline - 1, character = 0 },
["end"] = { line = cursorline - 1, character = line_len - 1 },
},
})

for _, hint in ipairs(inlays_hints) do
local length, ws_offset = utils.calc_ws_offset(hint, tab_width, vim.api.nvim_get_current_line())
table.insert(extra_padding, { start = ws_offset, length = length })
end
end
--multicharacter padding

require("precognition.utils").add_multibyte_padding(cur_line, extra_padding, line_len)
utils.add_multibyte_padding(cur_line, extra_padding, line_len)

local virt_line = build_virt_line(virtual_line_marks, line_len, extra_padding)

Expand Down
16 changes: 16 additions & 0 deletions lua/precognition/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ function M.create_pad_array(len, str)
return pad_array
end

---calculates the white space offset of a partial string
---@param hint vim.lsp.inlay_hint.get.ret
---@param tab_width integer
---@param current_line string
---@return integer
---@return integer
function M.calc_ws_offset(hint, tab_width, current_line)
-- + 1 here because of trailing padding
local length = #hint.inlay_hint.label[1].value + 1
local start = hint.inlay_hint.position.character
local prefix = vim.fn.strcharpart(current_line, 0, start)
local expanded = string.gsub(prefix, "\t", string.rep(" ", tab_width))
local ws_offset = vim.fn.strcharlen(expanded)
return length, ws_offset
end

---Add extra padding for multi byte character characters
---@param cur_line string
---@param extra_padding Precognition.ExtraPadding[]
Expand Down
42 changes: 9 additions & 33 deletions tests/precognition/e2e_spec.lua
Original file line number Diff line number Diff line change
@@ -1,32 +1,8 @@
local precognition = require("precognition")
local tu = require("tests.precognition.utils.utils")
---@diagnostic disable-next-line: undefined-field
local eq = assert.are.same

local function get_gutter_extmarks(buffer)
local gutter_extmarks = {}
for _, extmark in
pairs(vim.api.nvim_buf_get_extmarks(buffer, -1, 0, -1, {
details = true,
}))
do
if extmark[4] and extmark[4].sign_name and extmark[4].sign_name:match(precognition.gutter_group) then
table.insert(gutter_extmarks, extmark)
end
end
return gutter_extmarks
end

local function hex2dec(hex)
hex = hex:gsub("#", "")
local r = tonumber("0x" .. hex:sub(1, 2))
local g = tonumber("0x" .. hex:sub(3, 4))
local b = tonumber("0x" .. hex:sub(5, 6))

local dec = (r * 256 ^ 2) + (g * 256) + b

return dec
end

describe("e2e tests", function()
before_each(function()
precognition.setup({})
Expand Down Expand Up @@ -65,7 +41,7 @@ describe("e2e tests", function()
details = true,
})

local gutter_extmarks = get_gutter_extmarks(buffer)
local gutter_extmarks = tu.get_gutter_extmarks(buffer)

for _, extmark in pairs(gutter_extmarks) do
if extmark[4].sign_text == "G " then
Expand Down Expand Up @@ -104,7 +80,7 @@ describe("e2e tests", function()
vim.api.nvim_win_set_cursor(0, { 2, 1 })
precognition.on_cursor_moved()

gutter_extmarks = get_gutter_extmarks(buffer)
gutter_extmarks = tu.get_gutter_extmarks(buffer)

extmarks = vim.api.nvim_buf_get_extmark_by_id(buffer, precognition.ns, precognition.extmark, {
details = true,
Expand All @@ -129,7 +105,7 @@ describe("e2e tests", function()

vim.api.nvim_win_set_cursor(0, { 4, 1 })
precognition.on_cursor_moved()
gutter_extmarks = get_gutter_extmarks(buffer)
gutter_extmarks = tu.get_gutter_extmarks(buffer)

for _, extmark in pairs(gutter_extmarks) do
if extmark[4].sign_text == "G " then
Expand Down Expand Up @@ -165,7 +141,7 @@ describe("e2e tests", function()
details = true,
})

local gutter_extmarks = get_gutter_extmarks(buffer)
local gutter_extmarks = tu.get_gutter_extmarks(buffer)

for _, extmark in pairs(gutter_extmarks) do
if extmark[4].sign_text == "G " then
Expand Down Expand Up @@ -199,7 +175,7 @@ describe("e2e tests", function()
local background = "#00ff00"
local foreground = "#ff0000"
local customColor = { foreground = foreground, background = background }
local customMark = { fg = hex2dec(foreground), bg = hex2dec(background) }
local customMark = { fg = tu.hex2dec(foreground), bg = tu.hex2dec(background) }
precognition.setup({ highlightColor = customColor })
local buffer = vim.api.nvim_create_buf(true, false)
vim.api.nvim_set_current_buf(buffer)
Expand All @@ -218,7 +194,7 @@ describe("e2e tests", function()
details = true,
})

local gutter_extmarks = get_gutter_extmarks(buffer)
local gutter_extmarks = tu.get_gutter_extmarks(buffer)

for _, extmark in pairs(gutter_extmarks) do
if extmark[4].sign_text == "G " then
Expand Down Expand Up @@ -275,7 +251,7 @@ describe("Gutter Priority", function()

precognition.on_cursor_moved()

local gutter_extmarks = get_gutter_extmarks(testBuf)
local gutter_extmarks = tu.get_gutter_extmarks(testBuf)

for _, extmark in pairs(gutter_extmarks) do
eq(true, extmark[4].sign_text ~= "G ")
Expand Down Expand Up @@ -304,7 +280,7 @@ describe("Gutter Priority", function()

precognition.on_cursor_moved()

local gutter_extmarks = get_gutter_extmarks(testBuf)
local gutter_extmarks = tu.get_gutter_extmarks(testBuf)

eq(1, vim.tbl_count(gutter_extmarks))
eq("gg", gutter_extmarks[1][4].sign_text)
Expand Down
74 changes: 74 additions & 0 deletions tests/precognition/inlay_hints_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
local server = require("tests.precognition.utils.lsp").server
local compat = require("tests.precognition.utils.compat")
local precognition = require("precognition")
---@diagnostic disable-next-line: undefined-field
local eq = assert.are.same
---@diagnostic disable-next-line: undefined-field
local neq = assert.are_not.same
local buf

local function wait(condition, msg)
vim.wait(100, condition)
local result = condition()
neq(false, result, msg)
neq(nil, result, msg)
end

describe("lsp based tests", function()
before_each(function()
require("tests.precognition.utils.lsp").Reset()
buf = vim.api.nvim_create_buf(true, false)
local srv = vim.lsp.start_client({ cmd = server })
if srv then
vim.lsp.buf_attach_client(buf, srv)
end
end)

it("initialize lsp", function()
eq(2, #require("tests.precognition.utils.lsp").messages)
eq("initialize", require("tests.precognition.utils.lsp").messages[1].method)
eq("initialized", require("tests.precognition.utils.lsp").messages[2].method)
end)

it("can enable inlay hints", function()
vim.lsp.inlay_hint.enable(true, { bufnr = buf })

eq(3, #require("tests.precognition.utils.lsp").messages)
eq("textDocument/inlayHint", require("tests.precognition.utils.lsp").messages[3].method)
end)

it("inlay hint shifts the line", function()
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "here is a string" })
vim.api.nvim_set_current_buf(buf)
vim.api.nvim_win_set_cursor(0, { 1, 1 })

precognition.on_cursor_moved()

local extmarks = vim.api.nvim_buf_get_extmark_by_id(buf, precognition.ns, precognition.extmark, {
details = true,
})

eq("b e w $", extmarks[3].virt_lines[1][1][1])

vim.lsp.inlay_hint.enable(true, { bufnr = buf })
-- NOTE:The test LSP replies with an inlay hint, that suggest "foo" as line 1, position 4
-- This means that the inlay hint is shifted by 3 chars

precognition.on_cursor_moved()

extmarks = vim.api.nvim_buf_get_extmark_by_id(buf, precognition.ns, precognition.extmark, {
details = true,
})

eq("b e w $", extmarks[3].virt_lines[1][1][1])
end)

after_each(function()
vim.lsp.inlay_hint.enable(false, { bufnr = buf })
vim.api.nvim_buf_delete(buf, { force = true })
vim.lsp.stop_client(compat.get_active_lsp_clients())
wait(function()
return vim.tbl_count(compat.get_active_lsp_clients()) == 0
end, "clients must stop")
end)
end)
5 changes: 5 additions & 0 deletions tests/precognition/utils/compat.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local M = {}

M.get_active_lsp_clients = vim.lsp.get_clients() or vim.lsp.get_active_clients()

return M
53 changes: 53 additions & 0 deletions tests/precognition/utils/lsp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
local M = {}

M.messages = {}

function M.server()
local closing = false
local srv = {}

function srv.request(method, params, handler)
table.insert(M.messages, { method = method, params = params })
if method == "initialize" then
handler(nil, {
capabilities = {
inlayHintProvider = true,
},
})
elseif method == "shutdown" then
handler(nil, nil)
elseif method == "textDocument/inlayHint" then
handler(nil, {
{
position = { line = 0, character = 3 },
label = { { value = "foo" } },
},
})
else
assert(false, "Unhandled method: " .. method)
end
end

function srv.notify(method, params)
table.insert(M.messages, { method = method, params = params })
if method == "exit" then
closing = true
end
end

function srv.is_closing()
return closing
end

function srv.terminate()
closing = true
end

return srv
end

function M.Reset()
M.messages = {}
end

return M
30 changes: 30 additions & 0 deletions tests/precognition/utils/utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
local precognition = require("precognition")

local M = {}

function M.get_gutter_extmarks(buffer)
local gutter_extmarks = {}
for _, extmark in
pairs(vim.api.nvim_buf_get_extmarks(buffer, -1, 0, -1, {
details = true,
}))
do
if extmark[4] and extmark[4].sign_name and extmark[4].sign_name:match(precognition.gutter_group) then
table.insert(gutter_extmarks, extmark)
end
end
return gutter_extmarks
end

function M.hex2dec(hex)
hex = hex:gsub("#", "")
local r = tonumber("0x" .. hex:sub(1, 2))
local g = tonumber("0x" .. hex:sub(3, 4))
local b = tonumber("0x" .. hex:sub(5, 6))

local dec = (r * 256 ^ 2) + (g * 256) + b

return dec
end

return M
1 change: 0 additions & 1 deletion tests/precognition/virtline_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ describe("Build Virtual Line", function()

it("example virtual line with whitespace padding", function()
local line = " abc def"
-- abc def
local cursorcol = 5
local tab_width = vim.bo.expandtab and vim.bo.shiftwidth or vim.bo.tabstop
local cur_line = line:gsub("\t", string.rep(" ", tab_width))
Expand Down

0 comments on commit 6772d3a

Please sign in to comment.