File: layout.lua

package info (click to toggle)
neovim-which-key 3.17.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 500 kB
  • sloc: sh: 21; makefile: 2
file content (147 lines) | stat: -rw-r--r-- 4,034 bytes parent folder | download
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
local Config = require("which-key.config")
local M = {}

local dw = vim.fn.strdisplaywidth

--- When `size` is a number, it is returned as is (fixed dize).
--- Otherwise, it is a percentage of `parent` (relative size).
--- If `size` is negative, it is subtracted from `parent`.
--- If `size` is a table, it is a range of values.
---@alias wk.Dim number|{min:number, max:number}

---@param size number
---@param parent number
---@param ... wk.Dim
---@return number
function M.dim(size, parent, ...)
  size = math.abs(size) < 1 and parent * size or size
  size = size < 0 and parent + size or size
  for _, dim in ipairs({ ... } --[[ @as wk.Dim[] ]]) do
    if type(dim) == "number" then
      size = M.dim(dim, parent)
    else
      local min = dim.min and M.dim(dim.min, parent) or 0
      local max = dim.max and M.dim(dim.max, parent) or parent
      size = math.max(min, math.min(max, size))
    end
  end
  return math.floor(math.max(0, math.min(parent, size)) + 0.5)
end

---@class wk.Table: wk.Table.opts
local Table = {}
Table.__index = Table

---@param opts wk.Table.opts
function Table.new(opts)
  local self = setmetatable({}, Table)
  self.cols = opts.cols
  self.rows = opts.rows
  return self
end

---@param opts? {spacing?: number}
---@return string[][], number[], number
function Table:cells(opts)
  opts = opts or {}
  opts.spacing = opts.spacing or 1

  local widths = {} ---@type number[] actual column widths

  local cells = {} ---@type string[][]

  local total = 0
  for c, col in ipairs(self.cols) do
    widths[c] = 0
    local all_ws = true
    for r, row in ipairs(self.rows) do
      cells[r] = cells[r] or {}
      local value = row[col.key] or col.default or ""
      value = tostring(value)
      value = value:gsub("%s*$", "")
      value = value:gsub("\n", Config.icons.keys.NL)
      value = vim.fn.strtrans(value)
      if value:find("%S") then
        all_ws = false
      end
      if col.padding then
        value = (" "):rep(col.padding[1] or 0) .. value .. (" "):rep(col.padding[2] or 0)
      end
      if c ~= #self.cols then
        value = value .. (" "):rep(opts.spacing)
      end
      cells[r][c] = value
      widths[c] = math.max(widths[c], dw(value))
    end
    if all_ws then
      widths[c] = 0
      for _, cell in pairs(cells) do
        cell[c] = ""
      end
    end
    total = total + widths[c]
  end

  return cells, widths, total
end

---@param opts {width: number, spacing?: number}
function Table:layout(opts)
  local cells, widths = self:cells(opts)

  local free = opts.width

  for c, col in ipairs(self.cols) do
    if not col.width then
      free = free - widths[c]
    end
  end
  free = math.max(free, 0)

  for c, col in ipairs(self.cols) do
    if col.width then
      widths[c] = M.dim(widths[c], free, { max = col.width })
      free = free - widths[c]
    end
  end

  ---@type {value: string, hl?:string}[][]
  local ret = {}

  for _, row in ipairs(cells) do
    ---@type {value: string, hl?:string}[]
    local line = {}
    for c, col in ipairs(self.cols) do
      local value = row[c]
      local width = dw(value)
      if width > widths[c] then
        local old = value
        value = ""
        for i = 0, vim.fn.strchars(old) do
          value = value .. vim.fn.strcharpart(old, i, 1)
          if dw(value) >= widths[c] - 1 - (opts.spacing or 1) then
            break
          end
        end
        value = value .. Config.icons.ellipsis .. string.rep(" ", opts.spacing or 1)
      else
        local align = col.align or "left"
        if align == "left" then
          value = value .. (" "):rep(widths[c] - width)
        elseif align == "right" then
          value = (" "):rep(widths[c] - width) .. value
        elseif align == "center" then
          local pad = (widths[c] - width) / 2
          value = (" "):rep(math.floor(pad)) .. value .. (" "):rep(math.ceil(pad))
        end
      end
      line[#line + 1] = { value = value, hl = col.hl }
    end
    ret[#ret + 1] = line
  end
  return ret
end

M.new = Table.new

return M