Skip to content

Commit

Permalink
fix: doc scrollbar render (#724)
Browse files Browse the repository at this point in the history
* fix(scrollbar): update doc scrollbar after scroll doc

* fix(scrollbar): fix doc scrollbar position after scroll

* fix(scrollbar): make sure scrollbar thumb wouldn't over the border

* perf(scrollbar): trigger doc update by fix emitter closure

* doc: update scrollbar line calculating comment

* feat: rework scrollbar updates

---------

Co-authored-by: Liam Dyer <[email protected]>
  • Loading branch information
tim3nd and Saghen authored Dec 23, 2024
1 parent b6c7762 commit 8f71ccb
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 48 deletions.
28 changes: 16 additions & 12 deletions lua/blink/cmp/completion/windows/documentation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ local docs = {
scrollbar = win_config.scrollbar,
wrap = true,
filetype = 'blink-cmp-documentation',
scrolloff = 0,
}),
last_context_id = nil,
auto_show_timer = vim.uv.new_timer(),
}

menu.position_update_emitter:on(docs.update_position)
menu.position_update_emitter:on(function() docs.update_position() end)
menu.close_emitter:on(function()
docs.win:close()
docs.auto_show_timer:stop()
Expand Down Expand Up @@ -96,33 +97,38 @@ function docs.show_item(context, item)

if menu.win:get_win() then
docs.win:open()
vim.api.nvim_win_set_cursor(docs.win:get_win(), { 1, 0 }) -- reset scroll
docs.win:set_cursor({ 1, 0 }) -- reset scroll
docs.update_position()
end
end)
:catch(function(err) vim.notify(err, vim.log.levels.ERROR, { title = 'blink.cmp' }) end)
end

-- TODO: compensate for wrapped lines
function docs.scroll_up(amount)
local winnr = docs.win:get_win()
local top_line = math.max(1, vim.fn.line('w0', winnr) - 1)
if winnr == nil then return end

local top_line = math.max(1, vim.fn.line('w0', winnr))
local desired_line = math.max(1, top_line - amount)

vim.api.nvim_win_set_cursor(docs.win:get_win(), { desired_line, 0 })
docs.win:set_cursor({ desired_line, 0 })
end

-- TODO: compensate for wrapped lines
function docs.scroll_down(amount)
local winnr = docs.win:get_win()
if winnr == nil then return end

local line_count = vim.api.nvim_buf_line_count(docs.win:get_buf())
local bottom_line = math.max(1, vim.fn.line('w$', winnr) + 1)
local bottom_line = math.max(1, vim.fn.line('w$', winnr))
local desired_line = math.min(line_count, bottom_line + amount)

vim.api.nvim_win_set_cursor(docs.win:get_win(), { desired_line, 0 })
docs.win:set_cursor({ desired_line, 0 })
end

function docs.update_position()
if not docs.win:is_open() or not menu.win:is_open() then return end
local winnr = docs.win:get_win()

docs.win:update_size()

Expand Down Expand Up @@ -163,16 +169,16 @@ function docs.update_position()
end

-- set width and height based on available space
vim.api.nvim_win_set_height(docs.win:get_win(), pos.height)
vim.api.nvim_win_set_width(docs.win:get_win(), pos.width)
docs.win:set_height(pos.height)
docs.win:set_width(pos.width)

-- set position based on provided direction

local height = docs.win:get_height()
local width = docs.win:get_width()

local function set_config(opts)
vim.api.nvim_win_set_config(winnr, { relative = 'win', win = menu_winnr, row = opts.row, col = opts.col })
docs.win:set_win_config({ relative = 'win', win = menu_winnr, row = opts.row, col = opts.col })
end
if pos.direction == 'n' then
if menu_win_is_up then
Expand Down Expand Up @@ -214,8 +220,6 @@ function docs.update_position()
set_config({ row = -menu_border_size.top, col = -width - menu_border_size.left })
end
end

if docs.win.scrollbar then docs.win.scrollbar:update(winnr) end
end

return docs
30 changes: 4 additions & 26 deletions lua/blink/cmp/completion/windows/menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,12 @@ function menu.close()

menu.win:close()
menu.close_emitter:emit()
menu.redraw_if_needed()
end

function menu.set_selected_item_idx(idx)
menu.win:set_option_value('cursorline', idx ~= nil)
menu.selected_item_idx = idx
if menu.win:is_open() then
vim.api.nvim_win_set_cursor(menu.win:get_win(), { idx or 1, 0 })
menu.redraw_if_needed()
end
if menu.win:is_open() then menu.win:set_cursor({ idx or 1, 0 }) end
end

--- TODO: Don't switch directions if the context is the same
Expand All @@ -98,7 +94,6 @@ function menu.update_position()

local win = menu.win
if not win:is_open() then return end
local winnr = win:get_win()

win:update_size()

Expand All @@ -119,37 +114,20 @@ function menu.update_position()

if vim.api.nvim_get_mode().mode == 'c' then
local cmdline_position = config.cmdline_position()
vim.api.nvim_win_set_config(winnr, {
win:set_win_config({
relative = 'editor',
row = cmdline_position[1] + row,
col = math.max(cmdline_position[2] + context.bounds.start_col - start_col, 0),
})
else
local cursor_col = context.get_cursor()[2]
local col = context.bounds.start_col - cursor_col - (context.bounds.length == 0 and 0 or 1) - border_size.left
vim.api.nvim_win_set_config(winnr, { relative = 'cursor', row = row, col = col - start_col })
win:set_win_config({ relative = 'cursor', row = row, col = col - start_col })
end

vim.api.nvim_win_set_height(winnr, pos.height)
if win.scrollbar then win.scrollbar:update(winnr) end
win:set_height(pos.height)

menu.position_update_emitter:emit()
menu.redraw_if_needed()
end

local redraw_queued = false
--- In cmdline mode, the window won't be redrawn automatically so we redraw ourselves on schedule
function menu.redraw_if_needed()
if vim.api.nvim_get_mode().mode ~= 'c' or menu.win:get_win() == nil then return end
if redraw_queued then return end

-- We redraw on schedule to avoid the cmdline disappearing during redraw
-- and to batch multiple redraws together
redraw_queued = true
vim.schedule(function()
redraw_queued = false
vim.api.nvim__redraw({ win = menu.win:get_win(), flush = true })
end)
end

return menu
2 changes: 1 addition & 1 deletion lua/blink/cmp/config/completion/documentation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ local documentation = {
desired_min_height = 10,
border = 'padded',
winblend = 0,
winhighlight = 'Normal:BlinkCmpDoc,FloatBorder:BlinkCmpDocBorder',
winhighlight = 'Normal:BlinkCmpDoc,FloatBorder:BlinkCmpDocBorder,EndOfBuffer:BlinkCmpDoc',
scrollbar = true,
direction_priority = {
menu_north = { 'e', 'w', 'n', 's' },
Expand Down
65 changes: 65 additions & 0 deletions lua/blink/cmp/lib/window/init.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
-- TODO: The scrollbar and redrawing logic should be done by wrapping the functions that would
-- trigger a redraw or update the window

--- @class blink.cmp.WindowOptions
--- @field min_width? number
--- @field max_width? number
Expand All @@ -16,6 +19,7 @@
--- @field buf? number
--- @field config blink.cmp.WindowOptions
--- @field scrollbar? blink.cmp.Scrollbar
--- @field redraw_queued boolean
---
--- @field new fun(config: blink.cmp.WindowOptions): blink.cmp.Window
--- @field get_buf fun(self: blink.cmp.Window): number
Expand All @@ -31,8 +35,13 @@
--- @field get_content_width fun(self: blink.cmp.Window): number
--- @field get_width fun(self: blink.cmp.Window): number
--- @field get_cursor_screen_position fun(): { distance_from_top: number, distance_from_bottom: number }
--- @field set_cursor fun(self: blink.cmp.Window, cursor: number[])
--- @field set_height fun(self: blink.cmp.Window, height: number)
--- @field set_width fun(self: blink.cmp.Window, width: number)
--- @field set_win_config fun(self: blink.cmp.Window, config: table)
--- @field get_vertical_direction_and_height fun(self: blink.cmp.Window, direction_priority: ("n" | "s")[]): { height: number, direction: 'n' | 's' }?
--- @field get_direction_with_window_constraints fun(self: blink.cmp.Window, anchor_win: blink.cmp.Window, direction_priority: ("n" | "s" | "e" | "w")[], desired_min_size?: { width: number, height: number }): { width: number, height: number, direction: 'n' | 's' | 'e' | 'w' }?
--- @field redraw_if_needed fun(self: blink.cmp.Window)

--- @type blink.cmp.Window
--- @diagnostic disable-next-line: missing-fields
Expand All @@ -57,6 +66,7 @@ function win.new(config)
scrollbar = config.scrollbar,
filetype = config.filetype,
}
self.redraw_queued = false

if self.config.scrollbar then
self.scrollbar = require('blink.cmp.lib.window.scrollbar').new({
Expand Down Expand Up @@ -111,6 +121,7 @@ function win:open()
vim.api.nvim_set_option_value('filetype', self.config.filetype, { buf = self.buf })

if self.scrollbar then self.scrollbar:update(self.id) end
self:redraw_if_needed()
end

function win:set_option_value(option, value)
Expand All @@ -124,6 +135,7 @@ function win:close()
self.id = nil
end
if self.scrollbar then self.scrollbar:update() end
self:redraw_if_needed()
end

--- Updates the size of the window to match the max width and height of the content/config
Expand Down Expand Up @@ -253,6 +265,46 @@ function win.get_cursor_screen_position()
}
end

function win:set_cursor(cursor)
local winnr = self:get_win()
assert(winnr ~= nil, 'Window must be open to set cursor')

vim.api.nvim_win_set_cursor(winnr, cursor)

if self.scrollbar then self.scrollbar:update(winnr) end
self:redraw_if_needed()
end

function win:set_height(height)
local winnr = self:get_win()
assert(winnr ~= nil, 'Window must be open to set height')

vim.api.nvim_win_set_height(winnr, height)

if self.scrollbar then self.scrollbar:update(winnr) end
self:redraw_if_needed()
end

function win:set_width(width)
local winnr = self:get_win()
assert(winnr ~= nil, 'Window must be open to set width')

vim.api.nvim_win_set_width(winnr, width)

if self.scrollbar then self.scrollbar:update(winnr) end
self:redraw_if_needed()
end

function win:set_win_config(config)
local winnr = self:get_win()
assert(winnr ~= nil, 'Window must be open to set window config')

vim.api.nvim_win_set_config(winnr, config)

if self.scrollbar then self.scrollbar:update(winnr) end
self:redraw_if_needed()
end

--- Gets the direction with the most space available, prioritizing the directions in the order of the
--- direction_priority list
function win:get_vertical_direction_and_height(direction_priority)
Expand Down Expand Up @@ -374,4 +426,17 @@ function win:get_direction_with_window_constraints(anchor_win, direction_priorit
}
end

--- In cmdline mode, the window won't be redrawn automatically so we redraw ourselves on schedule
function win:redraw_if_needed()
if self.redraw_queued or vim.api.nvim_get_mode().mode ~= 'c' or self:get_win() == nil then return end

-- We redraw on schedule to avoid the cmdline disappearing during redraw
-- and to batch multiple redraws together
self.redraw_queued = true
vim.schedule(function()
self.redraw_queued = false
vim.api.nvim__redraw({ win = self:get_win(), flush = true })
end)
end

return win
26 changes: 24 additions & 2 deletions lua/blink/cmp/lib/window/scrollbar/geometry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ local function get_col_offset(border)
return 0
end

--- Gets the starting line, handling line wrapping if enabled
--- @param target_win number
--- @param width number
--- @return number
local get_content_start_line = function(target_win, width)
local start_line = math.max(1, vim.fn.line('w0', target_win))
if not vim.wo[target_win].wrap then return start_line end

local bufnr = vim.api.nvim_win_get_buf(target_win)
local wrapped_start_line = 1
for _, text in ipairs(vim.api.nvim_buf_get_lines(bufnr, 0, start_line - 1, false)) do
-- nvim_buf_get_lines sometimes returns a blob. see hrsh7th/nvim-cmp#2050
if vim.fn.type(text) == vim.v.t_blob then text = vim.fn.string(text) end
wrapped_start_line = wrapped_start_line + math.max(1, math.ceil(vim.fn.strdisplaywidth(text) / width))
end
return wrapped_start_line
end

--- @param target_win number
--- @return { should_hide: boolean, thumb: blink.cmp.ScrollbarGeometry, gutter: blink.cmp.ScrollbarGeometry }
function M.get_geometry(target_win)
Expand All @@ -47,10 +65,14 @@ function M.get_geometry(target_win)
local zindex = config.zindex

local buf_height = get_win_buf_height(target_win)
local start_line = math.max(1, vim.fn.line('w0', target_win))
local pct = (start_line - 1) / (buf_height - height)
local thumb_height = math.max(1, math.floor(height * height / buf_height + 0.5) - 1)

local start_line = get_content_start_line(target_win, width or 1)

local pct = (start_line - 1) / (buf_height - height)
local thumb_offset = math.floor((pct * (height - thumb_height)) + 0.5)
thumb_height = thumb_offset + thumb_height > height and height - thumb_offset or thumb_height
thumb_height = math.max(1, thumb_height)

local common_geometry = {
width = 1,
Expand Down
5 changes: 0 additions & 5 deletions lua/blink/cmp/lib/window/scrollbar/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@

--- @class blink.cmp.Scrollbar
--- @field win blink.cmp.ScrollbarWin
--- @field autocmd? number
--- @field target_win? number
---
--- @field new fun(opts: blink.cmp.ScrollbarConfig): blink.cmp.Scrollbar
--- @field is_visible fun(self: blink.cmp.Scrollbar): boolean
--- @field is_mounted fun(self: blink.cmp.Scrollbar): boolean
--- @field update fun(self: blink.cmp.Scrollbar, target_win: number | nil)

--- @type blink.cmp.Scrollbar
Expand All @@ -27,8 +24,6 @@ end

function scrollbar:is_visible() return self.win:is_visible() end

function scrollbar:is_mounted() return self.autocmd ~= nil end

function scrollbar:update(target_win)
if target_win == nil or not vim.api.nvim_win_is_valid(target_win) then return self.win:hide() end

Expand Down
3 changes: 3 additions & 0 deletions lua/blink/cmp/lib/window/scrollbar/win.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
--- @field hide_gutter fun(self: blink.cmp.ScrollbarWin)
--- @field hide fun(self: blink.cmp.ScrollbarWin)
--- @field _make_win fun(self: blink.cmp.ScrollbarWin, geometry: blink.cmp.ScrollbarGeometry, hl_group: string): number
--- @field redraw_if_needed fun(self: blink.cmp.ScrollbarWin)

--- @type blink.cmp.ScrollbarWin
--- @diagnostic disable-next-line: missing-fields
local scrollbar_win = {}

function scrollbar_win.new(opts) return setmetatable(opts, { __index = scrollbar_win }) end
Expand Down
2 changes: 0 additions & 2 deletions lua/blink/cmp/signature/window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ function signature.update_position()
else
vim.api.nvim_win_set_config(winnr, { relative = 'cursor', row = pos.direction == 's' and 1 or -height, col = 0 })
end

if win.scrollbar then win.scrollbar:update(winnr) end
end

return signature

0 comments on commit 8f71ccb

Please sign in to comment.