Skip to content

Commit

Permalink
feat: layout
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed Jul 10, 2024
1 parent 98ccc01 commit 9fbad7d
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 21 deletions.
139 changes: 139 additions & 0 deletions lua/which-key/layout.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
local M = {}

M.ELLIPSIS = ""

local dw = vim.fn.strdisplaywidth

---@alias wk.Size number|{min:number, max:number}

---@param size number
---@param opts? {parent?: number, min?: number, max?: number}
---@return number
function M.dim(size, opts)
opts = opts or {}

if opts.parent then
assert(type(opts.parent) == "number", "parent must be a number")
assert(opts.parent > 1, "parent must be greater than 1")
end

if math.abs(size) <= 1 then
assert(opts.parent, "parent is required for relative sizes")
size = math.floor(size * opts.parent + 0.5)
end

if size < 0 then
assert(opts.parent, "parent is required for relative sizes")
size = opts.parent + size
end

if opts.min then
local min = M.dim(opts.min, { parent = opts.parent })
size = math.max(size, min) ---@type number
end

if opts.max then
local max = M.dim(opts.max, { parent = opts.parent })
size = math.min(size, max) ---@type number
end

size = math.max(size, 0)
return size
end

---@class wk.Col
---@field key string
---@field hl? string
---@field width? number
---@field default? string
---@field align? "left"|"right"|"center"

---@class wk.Table.opts
---@field cols wk.Col[]
---@field rows table<string, string>[]

---@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 {width: number, spacing?: number}
function Table:layout(opts)
opts.spacing = opts.spacing or 1

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

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

for c, col in ipairs(self.cols) do
widths[c] = 0
for r, row in ipairs(self.rows) do
cells[r] = cells[r] or {}
local value = row[col.key] or col.default or ""
value = vim.fn.strtrans(value)
cells[r][c] = value
widths[c] = math.max(widths[c], dw(value))
end
end

local free = opts.width

for c, col in ipairs(self.cols) do
if not col.width then
free = free - widths[c]
end
if c ~= #self.cols then
free = free - opts.spacing
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], { parent = 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
value = vim.fn.strcharpart(value, 0, widths[c] - 1, true) .. M.ELLIPSIS
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
if c ~= #self.cols then
value = value .. (" "):rep(opts.spacing)
end
line[#line + 1] = { value = value, hl = col.hl }
end
ret[#ret + 1] = line
end
return ret
end

M.new = Table.new

return M
9 changes: 7 additions & 2 deletions lua/which-key/plugins/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ end
local PluginNode = {}

function PluginNode:__index(k)
if k == "children" then
if k == "cols" then
assert(self.plugin, "node must be a plugin node")
local plugin = M.plugins[self.plugin or ""]
assert(plugin, "plugin not found")
return plugin.cols
elseif k == "children" then
assert(self.plugin, "node must be a plugin node")

local plugin = M.plugins[self.plugin or ""]
assert(plugin, "plugin not found")
local ret = {} ---@type table<string, wk.Node>
Expand All @@ -68,6 +72,7 @@ function PluginNode:__index(k)
order = i,
value = item.value,
action = item.action,
data = item,
}
ret[item.key] = child
end
Expand Down
12 changes: 7 additions & 5 deletions lua/which-key/plugins/marks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ local labels = {
[">"] = "To end of last visual selection",
}

M.cols = {
{ key = "lnum", hl = "Number", align = "right" },
}

function M.expand()
local buf = vim.api.nvim_get_current_buf()
local items = {} ---@type wk.Plugin.item[]
Expand All @@ -38,19 +42,17 @@ function M.expand()

local line ---@type string?
if mark.pos[1] and mark.pos[1] ~= 0 then
line = vim.fn.getbufline(mark.pos[1], lnum)[1] --[[@as string?]]
line = vim.api.nvim_buf_get_lines(mark.pos[1], lnum - 1, lnum, false)[1]
end

local file = mark.file and vim.fn.fnamemodify(mark.file, ":p:~:.")

local value = string.format("%4d ", lnum)
value = value .. (line or file or "")

table.insert(items, {
key = key,
desc = labels[key] or file and ("file: " .. file) or "",
value = value,
value = vim.trim(line or file or ""),
highlights = { { 1, 5, "Number" } },
lnum = lnum,
})
end

Expand Down
1 change: 1 addition & 0 deletions lua/which-key/text.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function M:height()
return #self._lines
end

---@return number
function M:width()
local width = 0
for _, line in ipairs(self._lines) do
Expand Down
2 changes: 2 additions & 0 deletions lua/which-key/tree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ local Util = require("which-key.util")
---@field children? table<string, wk.Node>
---@field value? string
---@field action? fun()
---@field cols? wk.Col[]
---@field data? table<string, string>

---@class wk.Tree
---@field root wk.Node
Expand Down
1 change: 1 addition & 0 deletions lua/which-key/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

---@class wk.Plugin
---@field name string
---@field cols? wk.Col[]
---@field actions wk.Plugin.action[]
---@field expand fun():wk.Plugin.item[]
---@field setup fun(opts: table<string, any>)
51 changes: 37 additions & 14 deletions lua/which-key/view.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local Config = require("which-key.config")
local Layout = require("which-key.layout")
local State = require("which-key.state")
local Tree = require("which-key.tree")
local Util = require("which-key.util")
Expand All @@ -21,7 +22,7 @@ M.fields = {
return item.desc or "~"
end,
group = function(item)
return item.group and 0 or 1
return item.group and 1 or 0
end,
alphanum = function(item)
return item.key:find("^%w+$") and 0 or 1
Expand Down Expand Up @@ -143,27 +144,47 @@ function M.show()
desc = child_count .. " keymap" .. (child_count > 1 and "s" or "")
end
desc = M.replace("desc", desc or "")
return {
return setmetatable({
node = node,
key = M.replace("key", node.key),
desc = child_count > 0 and Config.ui.icons.group .. desc or desc,
group = child_count > 0,
}
value = node.value,
}, { __index = node.data or {} })
end, children)

M.sort(items, Config.ui.sort)

local width = 0
for _, node in ipairs(items) do
width = math.max(width, dw(node.key))
end
---@type wk.Col[]
local cols = {
{ key = "key", hl = "WhichKey", align = "right" },
{ key = "sep", hl = "WhichKeySeparator", default = Config.ui.icons.separator },
}
vim.list_extend(cols, state.node.cols or {})
vim.list_extend(cols, {
{ key = "value", hl = "WhichKeyValue", width = 0.5 },
{ key = "desc", width = 1 },
})

local t = Layout.new({
cols = cols,
rows = items,
})

vim.api.nvim_win_call(M.win, function()
for r, row in ipairs(t:layout({ width = 120 })) do
local item = items[r]
for c, col in ipairs(row) do
local hl = col.hl
if cols[c].key == "desc" then
hl = item.group and "WhichKeyGroup" or "WhichKeyDesc"
end
text:append(col.value, hl)
end
text:nl()
end
end)

for _, item in ipairs(items) do
text:append(string.rep(" ", width - dw(item.key)) .. item.key, "WhichKey")
text:append(" " .. Config.ui.icons.separator .. " ", "WhichKeySeparator")
text:append(item.desc, item.group and "WhichKeyGroup" or "WhichKeyDesc")
text:nl()
end
local title = {
{ " " .. table.concat(state.node.path) .. " ", "FloatTitle" },
}
Expand All @@ -175,12 +196,14 @@ function M.show()
end

local height = text:height() - 1
local width = text:width()
vim.api.nvim_win_set_config(M.win, {
title = title,
relative = "editor",
height = height,
width = width,
row = vim.o.lines - height - 3,
col = vim.o.columns - 40,
col = vim.o.columns - width,
})
text:render(M.buf)
vim.cmd.redraw()
Expand Down
35 changes: 35 additions & 0 deletions tests/layout_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local layout = require("which-key.layout")

describe("dim", function()
local tests = {
{ 100, { parent = 200 }, 100 },
{ 0.2, { parent = 100 }, 20 },
{ -0.2, { parent = 100 }, 80 },
{ -20, { parent = 100 }, 80 },
{ 1, { parent = 100 }, 100 },
-- now some tests with min/max
{ 100, { parent = 200, min = 50 }, 100 },
{ 100, { parent = 200, max = 150 }, 100 },
{ 100, { parent = 200, min = 50, max = 150 }, 100 },
{ 100, { parent = 200, min = 150, max = 150 }, 150 },
-- now the combo
{ 0.2, { parent = 100, min = 20, max = 150 }, 20 },
{ 0.2, { parent = 100, min = 20, max = 50 }, 20 },
{ -0.5, { parent = 200 }, 100 },
{ 0.5, { parent = 200 }, 100 },
{ 0.5, { parent = 200, min = 150 }, 150 },
{ -0.5, { parent = 200, max = 50 }, 50 },
{ 300, { parent = 200, max = 250 }, 250 },
{ 300, { parent = 200, min = 250 }, 300 },
{ -100, { parent = 100, min = 20, max = 90 }, 20 },
{ -200, { parent = 100, min = -50, max = -50 }, 50 },
{ 0.2, { parent = 100, min = 0.5 }, 50 },
{ -200, { parent = 100 }, 0 },
}

for _, test in ipairs(tests) do
it("should return " .. test[3] .. " when given " .. test[1], function()
assert.are.equal(test[3], layout.dim(test[1], test[2]))
end)
end
end)

0 comments on commit 9fbad7d

Please sign in to comment.