summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/lib/window/scrollbar/geometry.lua
blob: ad481a05f102a1f7bba944839bbf224da89c8389 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
--- Helper for calculating placement of the scrollbar thumb and gutter

--- @class blink.cmp.ScrollbarGeometry
--- @field width number
--- @field height number
--- @field row number
--- @field col number
--- @field zindex number
--- @field relative string
--- @field win number

local M = {}

--- @param target_win number
--- @return number
local function get_win_buf_height(target_win)
  local buf = vim.api.nvim_win_get_buf(target_win)

  -- not wrapping, so just get the line count
  if not vim.wo[target_win].wrap then return vim.api.nvim_buf_line_count(buf) end

  local width = vim.api.nvim_win_get_width(target_win)
  local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
  local height = 0
  for _, l in ipairs(lines) do
    height = height + math.max(1, (math.ceil(vim.fn.strwidth(l) / width)))
  end
  return height
end

--- @param border string|string[]
--- @return number
local function get_col_offset(border)
  -- we only need an extra offset when working with a padded window
  if type(border) == 'table' and border[1] == ' ' and border[4] == ' ' and border[7] == ' ' and border[8] == ' ' then
    return 1
  end
  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)
  local config = vim.api.nvim_win_get_config(target_win)
  local width = config.width
  local height = config.height
  local zindex = config.zindex

  local buf_height = get_win_buf_height(target_win)
  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,
    row = thumb_offset,
    col = width + get_col_offset(config.border),
    relative = 'win',
    win = target_win,
  }

  return {
    should_hide = height >= buf_height,
    thumb = vim.tbl_deep_extend('force', common_geometry, { height = thumb_height, zindex = zindex + 2 }),
    gutter = vim.tbl_deep_extend('force', common_geometry, { row = 0, height = height, zindex = zindex + 1 }),
  }
end

return M