diff options
Diffstat (limited to 'lua/mason-core/ui/init.lua')
| -rw-r--r-- | lua/mason-core/ui/init.lua | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/lua/mason-core/ui/init.lua b/lua/mason-core/ui/init.lua new file mode 100644 index 00000000..0b288b20 --- /dev/null +++ b/lua/mason-core/ui/init.lua @@ -0,0 +1,203 @@ +local _ = require "mason-core.functional" +local M = {} + +---@alias NodeType +---| '"NODE"' +---| '"CASCADING_STYLE"' +---| '"VIRTUAL_TEXT"' +---| '"DIAGNOSTICS"' +---| '"HL_TEXT"' +---| '"KEYBIND_HANDLER"' +---| '"STICKY_CURSOR"' + +---@alias INode Node | HlTextNode | CascadingStyleNode | VirtualTextNode | KeybindHandlerNode | DiagnosticsNode | StickyCursorNode + +---@param children INode[] +function M.Node(children) + ---@class Node + local node = { + type = "NODE", + children = children, + } + return node +end + +---@param lines_with_span_tuples string[][]|string[] +function M.HlTextNode(lines_with_span_tuples) + if type(lines_with_span_tuples[1]) == "string" then + -- this enables a convenience API for just rendering a single line (with just a single span) + lines_with_span_tuples = { { lines_with_span_tuples } } + end + ---@class HlTextNode + local node = { + type = "HL_TEXT", + lines = lines_with_span_tuples, + } + return node +end + +local create_unhighlighted_lines = _.map(function(line) + return { { line, "" } } +end) + +---@param lines string[] +function M.Text(lines) + return M.HlTextNode(create_unhighlighted_lines(lines)) +end + +---@alias CascadingStyle +---| '"INDENT"' +---| '"CENTERED"' + +---@param styles CascadingStyle[] +---@param children INode[] +function M.CascadingStyleNode(styles, children) + ---@class CascadingStyleNode + local node = { + type = "CASCADING_STYLE", + styles = styles, + children = children, + } + return node +end + +---@param virt_text string[][] @List of (text, highlight) tuples. +function M.VirtualTextNode(virt_text) + ---@class VirtualTextNode + local node = { + type = "VIRTUAL_TEXT", + virt_text = virt_text, + } + return node +end + +---@param diagnostic {message: string, severity: integer, source: string|nil} +function M.DiagnosticsNode(diagnostic) + ---@class DiagnosticsNode + local node = { + type = "DIAGNOSTICS", + diagnostic = diagnostic, + } + return node +end + +---@param condition boolean +---@param node INode | fun(): INode +---@param default_val any +function M.When(condition, node, default_val) + if condition then + if type(node) == "function" then + return node() + else + return node + end + end + return default_val or M.Node {} +end + +---@param key string @The keymap to register to. Example: "<CR>". +---@param effect string @The effect to call when keymap is triggered by the user. +---@param payload any @The payload to pass to the effect handler when triggered. +---@param is_global boolean|nil @Whether to register the keybind to apply on all lines in the buffer. +function M.Keybind(key, effect, payload, is_global) + ---@class KeybindHandlerNode + local node = { + type = "KEYBIND_HANDLER", + key = key, + effect = effect, + payload = payload, + is_global = is_global or false, + } + return node +end + +function M.EmptyLine() + return M.Text { "" } +end + +---@param rows string[][][] @A list of rows to include in the table. Each row consists of an array of (text, highlight) tuples (aka spans). +function M.Table(rows) + local col_maxwidth = {} + for i = 1, #rows do + local row = rows[i] + for j = 1, #row do + local col = row[j] + local content = col[1] + col_maxwidth[j] = math.max(vim.api.nvim_strwidth(content), col_maxwidth[j] or 0) + end + end + + for i = 1, #rows do + local row = rows[i] + for j = 1, #row do + local col = row[j] + local content = col[1] + col[1] = content .. string.rep(" ", col_maxwidth[j] - vim.api.nvim_strwidth(content) + 1) -- +1 for default minimum padding + end + end + + return M.HlTextNode(rows) +end + +---@param opts { id: string } +function M.StickyCursor(opts) + ---@class StickyCursorNode + local node = { + type = "STICKY_CURSOR", + id = opts.id, + } + return node +end + +---Makes it possible to create stateful animations by progressing from the start of a range to the end. +---This is done in "ticks", in accordance with the provided options. +---@param opts {range: integer[], delay_ms: integer, start_delay_ms: integer, iteration_delay_ms: integer} +function M.animation(opts) + local animation_fn = opts[1] + local start_tick, end_tick = opts.range[1], opts.range[2] + local is_animating = false + + local function start_animation() + if is_animating then + return + end + local tick, start + + tick = function(current_tick) + animation_fn(current_tick) + if current_tick < end_tick then + vim.defer_fn(function() + tick(current_tick + 1) + end, opts.delay_ms) + else + is_animating = false + if opts.iteration_delay_ms then + start(opts.iteration_delay_ms) + end + end + end + + start = function(delay_ms) + is_animating = true + if delay_ms then + vim.defer_fn(function() + tick(start_tick) + end, delay_ms) + else + tick(start_tick) + end + end + + start(opts.start_delay_ms) + + local function cancel() + is_animating = false + end + + return cancel + end + + return start_animation +end + +return M |
