Hyprland-dotfiles/nvim/lua/modules/utils/init.lua
2024-07-21 02:51:17 -04:00

371 lines
11 KiB
Lua

local M = {}
---@class palette
---@field rosewater string
---@field flamingo string
---@field mauve string
---@field pink string
---@field red string
---@field maroon string
---@field peach string
---@field yellow string
---@field green string
---@field sapphire string
---@field blue string
---@field sky string
---@field teal string
---@field lavender string
---@field text string
---@field subtext1 string
---@field subtext0 string
---@field overlay2 string
---@field overlay1 string
---@field overlay0 string
---@field surface2 string
---@field surface1 string
---@field surface0 string
---@field base string
---@field mantle string
---@field crust string
---@field none "NONE"
---@type nil|palette
local palette = nil
-- Indicates if autocmd for refreshing the builtin palette has already been registered
---@type boolean
local _has_autocmd = false
---Initialize the palette
---@return palette
local function init_palette()
-- Reinitialize the palette on event `ColorScheme`
if not _has_autocmd then
_has_autocmd = true
vim.api.nvim_create_autocmd("ColorScheme", {
group = vim.api.nvim_create_augroup("__builtin_palette", { clear = true }),
pattern = "*",
callback = function()
palette = nil
init_palette()
-- Also refresh hard-coded hl groups
M.gen_alpha_hl()
M.gen_lspkind_hl()
pcall(vim.cmd.AlphaRedraw)
end,
})
end
if not palette then
palette = vim.g.colors_name:find("catppuccin") and require("catppuccin.palettes").get_palette()
or {
rosewater = "#DC8A78",
flamingo = "#DD7878",
mauve = "#CBA6F7",
pink = "#F5C2E7",
red = "#E95678",
maroon = "#B33076",
peach = "#FF8700",
yellow = "#F7BB3B",
green = "#AFD700",
sapphire = "#36D0E0",
blue = "#61AFEF",
sky = "#04A5E5",
teal = "#B5E8E0",
lavender = "#7287FD",
text = "#F2F2BF",
subtext1 = "#BAC2DE",
subtext0 = "#A6ADC8",
overlay2 = "#C3BAC6",
overlay1 = "#988BA2",
overlay0 = "#6E6B6B",
surface2 = "#6E6C7E",
surface1 = "#575268",
surface0 = "#302D41",
base = "#1D1536",
mantle = "#1C1C19",
crust = "#161320",
}
palette = vim.tbl_extend("force", { none = "NONE" }, palette, require("core.settings").palette_overwrite)
end
return palette
end
---@param c string @The color in hexadecimal.
local function hex_to_rgb(c)
c = string.lower(c)
return { tonumber(c:sub(2, 3), 16), tonumber(c:sub(4, 5), 16), tonumber(c:sub(6, 7), 16) }
end
-- NOTE: If the active colorscheme isn't `catppuccin`, this function won't overwrite existing definitions
---Sets a global highlight group.
---@param name string @Highlight group name, e.g. "ErrorMsg"
---@param foreground string @The foreground color
---@param background? string @The background color
---@param italic? boolean
local function set_global_hl(name, foreground, background, italic)
vim.api.nvim_set_hl(0, name, {
fg = foreground,
bg = background,
italic = italic == true,
default = not vim.g.colors_name:find("catppuccin"),
})
end
---Blend foreground with background
---@param foreground string @The foreground color
---@param background string @The background color to blend with
---@param alpha number|string @Number between 0 and 1 for blending amount.
function M.blend(foreground, background, alpha)
alpha = type(alpha) == "string" and (tonumber(alpha, 16) / 0xff) or alpha
local bg = hex_to_rgb(background)
local fg = hex_to_rgb(foreground)
local blend_channel = function(i)
local ret = (alpha * fg[i] + ((1 - alpha) * bg[i]))
return math.floor(math.min(math.max(0, ret), 255) + 0.5)
end
return string.format("#%02x%02x%02x", blend_channel(1), blend_channel(2), blend_channel(3))
end
---Get RGB highlight by highlight group
---@param hl_group string @Highlight group name
---@param use_bg boolean @Returns background or not
---@param fallback_hl? string @Fallback value if the hl group is not defined
---@return string
function M.hl_to_rgb(hl_group, use_bg, fallback_hl)
local hex = fallback_hl or "#000000"
local hlexists = pcall(vim.api.nvim_get_hl, 0, { name = hl_group, link = false })
if hlexists then
local result = vim.api.nvim_get_hl(0, { name = hl_group, link = false })
if use_bg then
hex = result.bg and string.format("#%06x", result.bg) or "NONE"
else
hex = result.fg and string.format("#%06x", result.fg) or "NONE"
end
end
return hex
end
---Extend a highlight group
---@param name string @Target highlight group name
---@param def table @Attributes to be extended
function M.extend_hl(name, def)
local hlexists = pcall(vim.api.nvim_get_hl, 0, { name = name, link = false })
if not hlexists then
-- Do nothing if highlight group not found
return
end
local current_def = vim.api.nvim_get_hl(0, { name = name, link = false })
local combined_def = vim.tbl_deep_extend("force", current_def, def)
vim.api.nvim_set_hl(0, name, combined_def)
end
---Generate universal highlight groups
---@param overwrite palette? @The color to be overwritten | highest priority
---@return palette
function M.get_palette(overwrite)
if not overwrite then
return vim.deepcopy(init_palette())
else
return vim.tbl_extend("force", init_palette(), overwrite)
end
end
-- Generate highlight groups for lspsaga. Existing attributes will NOT be overwritten
function M.gen_lspkind_hl()
local colors = M.get_palette()
local dat = {
Class = colors.yellow,
Constant = colors.peach,
Constructor = colors.sapphire,
Enum = colors.yellow,
EnumMember = colors.teal,
Event = colors.yellow,
Field = colors.teal,
File = colors.rosewater,
Function = colors.blue,
Interface = colors.yellow,
Key = colors.red,
Method = colors.blue,
Module = colors.blue,
Namespace = colors.blue,
Number = colors.peach,
Operator = colors.sky,
Package = colors.blue,
Property = colors.teal,
Struct = colors.yellow,
TypeParameter = colors.blue,
Variable = colors.peach,
Array = colors.peach,
Boolean = colors.peach,
Null = colors.yellow,
Object = colors.yellow,
String = colors.green,
TypeAlias = colors.green,
Parameter = colors.blue,
StaticMethod = colors.peach,
Text = colors.green,
Snippet = colors.mauve,
Folder = colors.blue,
Unit = colors.green,
Value = colors.peach,
}
for kind, color in pairs(dat) do
set_global_hl("LspKind" .. kind, color)
end
end
-- Generate highlight groups for alpha. Existing attributes will NOT be overwritten
function M.gen_alpha_hl()
local colors = M.get_palette()
set_global_hl("AlphaHeader", colors.blue)
set_global_hl("AlphaButtons", colors.green)
set_global_hl("AlphaShortcut", colors.pink, nil, true)
set_global_hl("AlphaFooter", colors.yellow)
end
-- Generate blend_color for neodim.
function M.gen_neodim_blend_attr()
local trans_bg = require("core.settings").transparent_background
local appearance = require("core.settings").background
if trans_bg and appearance == "dark" then
return "#000000"
elseif trans_bg and appearance == "light" then
return "#FFFFFF"
else
return M.hl_to_rgb("Normal", true)
end
end
---Convert number (0/1) to boolean
---@param value number @The value to check
---@return boolean|nil @Returns nil if failed
function M.tobool(value)
if value == 0 then
return false
elseif value == 1 then
return true
else
vim.notify(
"Attempting to convert data of type '" .. type(value) .. "' [other than 0 or 1] to boolean",
vim.log.levels.ERROR,
{ title = "[utils] Runtime Error" }
)
return nil
end
end
--- Function to recursively merge src into dst
--- Unlike vim.tbl_deep_extend(), this function extends if the original value is a list
---@paramm dst table @Table which will be modified and appended to
---@paramm src table @Table from which values will be inserted
---@return table @Modified table
local function tbl_recursive_merge(dst, src)
for key, value in pairs(src) do
if type(dst[key]) == "table" and type(value) == "function" then
dst[key] = value(dst[key])
elseif type(dst[key]) == "table" and vim.islist(dst[key]) and key ~= "dashboard_image" then
vim.list_extend(dst[key], value)
elseif type(dst[key]) == "table" and type(value) == "table" and not vim.islist(dst[key]) then
tbl_recursive_merge(dst[key], value)
else
dst[key] = value
end
end
return dst
end
-- Function to extend existing core configs (settings, events, etc.)
---@param config table @The default config to be merged with
---@param user_config string @The module name used to require user config
---@return table @Extended config
function M.extend_config(config, user_config)
local ok, extras = pcall(require, user_config)
if ok and type(extras) == "table" then
config = tbl_recursive_merge(config, extras)
end
return config
end
---@param plugin_name string @Module name of the plugin (used to setup itself)
---@param opts nil|table @The default config to be merged with
---@param vim_plugin? boolean @If this plugin is written in vimscript or not
---@param setup_callback? function @Add new callback if the plugin needs unusual setup function
function M.load_plugin(plugin_name, opts, vim_plugin, setup_callback)
vim_plugin = vim_plugin or false
-- Get the file name of the default config
local fname = debug.getinfo(2, "S").source:match("[^@/\\]*.lua$")
local ok, user_config = pcall(require, "user.configs." .. fname:sub(0, #fname - 4))
if ok and vim_plugin then
if user_config == false then
-- Return early if the user explicitly requires disabling plugin setup
return
elseif type(user_config) == "function" then
-- OK, setup as instructed by the user
user_config()
else
vim.notify(
string.format(
"<%s> is not a typical Lua plugin, please return a function with\nthe corresponding options defined instead (usually via `vim.g.*`)",
plugin_name
),
vim.log.levels.ERROR,
{ title = "[utils] Runtime Error (User Config)" }
)
end
elseif not vim_plugin then
if user_config == false then
-- Return early if the user explicitly requires disabling plugin setup
return
else
setup_callback = setup_callback or require(plugin_name).setup
-- User config exists?
if ok then
-- Extend base config if the returned user config is a table
if type(user_config) == "table" then
opts = tbl_recursive_merge(opts, user_config)
setup_callback(opts)
-- Replace base config if the returned user config is a function
elseif type(user_config) == "function" then
local user_opts = user_config(opts)
if type(user_opts) == "table" then
setup_callback(user_opts)
end
else
vim.notify(
string.format(
[[
Please return a `table` if you want to override some of the default options OR a
`function` returning a `table` if you want to replace the default options completely.
We received a `%s` for plugin <%s>.]],
type(user_config),
plugin_name
),
vim.log.levels.ERROR,
{ title = "[utils] Runtime Error (User Config)" }
)
end
else
-- Nothing provided... Fallback as default setup of the plugin
setup_callback(opts)
end
end
end
end
return M