mirror of
https://github.com/folke/snacks.nvim
synced 2025-07-07 21:25:11 +00:00
403 lines
9.3 KiB
Lua
403 lines
9.3 KiB
Lua
---@class snacks.toggle
|
|
---@overload fun(... :snacks.toggle.Opts): snacks.toggle.Class
|
|
local M = setmetatable({}, {
|
|
__call = function(M, ...)
|
|
return M.new(...)
|
|
end,
|
|
})
|
|
|
|
M.meta = {
|
|
desc = "Toggle keymaps integrated with which-key icons / colors",
|
|
}
|
|
|
|
---@class snacks.toggle.Config
|
|
---@field icon? string|{ enabled: string, disabled: string }
|
|
---@field color? string|{ enabled: string, disabled: string }
|
|
---@field wk_desc? string|{ enabled: string, disabled: string }
|
|
---@field map? fun(mode: string|string[], lhs: string, rhs: string|fun(), opts?: vim.keymap.set.Opts)
|
|
---@field which_key? boolean
|
|
---@field notify? boolean
|
|
local defaults = {
|
|
map = vim.keymap.set, -- keymap.set function to use
|
|
which_key = true, -- integrate with which-key to show enabled/disabled icons and colors
|
|
notify = true, -- show a notification when toggling
|
|
-- icons for enabled/disabled states
|
|
icon = {
|
|
enabled = " ",
|
|
disabled = " ",
|
|
},
|
|
-- colors for enabled/disabled states
|
|
color = {
|
|
enabled = "green",
|
|
disabled = "yellow",
|
|
},
|
|
wk_desc = {
|
|
enabled = "Disable ",
|
|
disabled = "Enable ",
|
|
},
|
|
}
|
|
|
|
---@type table<string, snacks.toggle.Class>
|
|
M.toggles = {}
|
|
|
|
---@class snacks.toggle.Opts: snacks.toggle.Config
|
|
---@field id? string
|
|
---@field name string
|
|
---@field get fun():boolean
|
|
---@field set fun(state:boolean)
|
|
|
|
---@class snacks.toggle.Class
|
|
---@field opts snacks.toggle.Opts
|
|
local Toggle = {}
|
|
Toggle.__index = Toggle
|
|
|
|
---@param ... snacks.toggle.Opts
|
|
function M.new(...)
|
|
---@type snacks.toggle.Class
|
|
local self = setmetatable({}, Toggle)
|
|
self.opts = Snacks.config.get("toggle", defaults, ...) --[[@as snacks.toggle.Opts]]
|
|
local id = self.opts.id or self.opts.name:lower():gsub("%W+", "_"):gsub("_+$", ""):gsub("^_+", "")
|
|
self.opts.id = id
|
|
M.toggles[id] = self
|
|
return self
|
|
end
|
|
|
|
---@param id string
|
|
---@return snacks.toggle.Class?
|
|
function M.get(id)
|
|
if not M.toggles[id] and M[id] then
|
|
M[id]()
|
|
end
|
|
return M.toggles[id]
|
|
end
|
|
|
|
function Toggle:get()
|
|
local ok, ret = pcall(self.opts.get)
|
|
if not ok then
|
|
Snacks.notify.error({
|
|
"Failed to get state for `" .. self.opts.name .. "`:\n",
|
|
ret --[[@as string]],
|
|
}, { title = self.opts.name, once = true })
|
|
return false
|
|
end
|
|
return ret
|
|
end
|
|
|
|
---@param state boolean
|
|
function Toggle:set(state)
|
|
local ok, err = pcall(self.opts.set, state) ---@type boolean, string?
|
|
if not ok then
|
|
Snacks.notify.error({
|
|
"Failed to set state for `" .. self.opts.name .. "`:\n",
|
|
err --[[@as string]],
|
|
}, { title = self.opts.name, once = true })
|
|
end
|
|
end
|
|
|
|
function Toggle:toggle()
|
|
local state = not self:get()
|
|
self:set(state)
|
|
if self.opts.notify then
|
|
Snacks.notify(
|
|
(state and "Enabled" or "Disabled") .. " **" .. self.opts.name .. "**",
|
|
{ title = self.opts.name, level = state and vim.log.levels.INFO or vim.log.levels.WARN }
|
|
)
|
|
end
|
|
end
|
|
|
|
---@param keys string
|
|
---@param opts? vim.keymap.set.Opts | { mode: string|string[]}
|
|
function Toggle:map(keys, opts)
|
|
opts = opts or {}
|
|
local mode = opts.mode or "n"
|
|
opts.mode = nil
|
|
opts.desc = opts.desc or ("Toggle " .. self.opts.name)
|
|
self.opts.map(mode, keys, function()
|
|
self:toggle()
|
|
end, opts)
|
|
if self.opts.which_key then
|
|
Snacks.util.on_module("which-key", function()
|
|
self:_wk(keys, mode)
|
|
end)
|
|
end
|
|
return self
|
|
end
|
|
|
|
function Toggle:_wk(keys, mode)
|
|
require("which-key").add({
|
|
{
|
|
keys,
|
|
mode = mode,
|
|
real = true,
|
|
icon = function()
|
|
local key = self:get() and "enabled" or "disabled"
|
|
return {
|
|
icon = type(self.opts.icon) == "string" and self.opts.icon or self.opts.icon[key],
|
|
color = type(self.opts.color) == "string" and self.opts.color or self.opts.color[key],
|
|
}
|
|
end,
|
|
desc = function()
|
|
local key = self:get() and "enabled" or "disabled"
|
|
return (type(self.opts.wk_desc) == "string" and self.opts.wk_desc or self.opts.wk_desc[key]) .. self.opts.name
|
|
end,
|
|
},
|
|
})
|
|
end
|
|
|
|
---@param option string
|
|
---@param opts? snacks.toggle.Config | {on?: unknown, off?: unknown, global?: boolean}
|
|
function M.option(option, opts)
|
|
opts = opts or {}
|
|
local on = opts.on == nil and true or opts.on
|
|
local off = opts.off ~= nil and opts.off or false
|
|
return M.new({
|
|
id = option,
|
|
name = option,
|
|
get = function()
|
|
if opts.global then
|
|
return vim.opt[option]:get() == on
|
|
end
|
|
return vim.opt_local[option]:get() == on
|
|
end,
|
|
set = function(state)
|
|
if opts.global then
|
|
vim.opt[option] = state and on or off
|
|
return
|
|
end
|
|
vim.opt_local[option] = state and on or off
|
|
end,
|
|
}, opts)
|
|
end
|
|
|
|
---@param opts? snacks.toggle.Config
|
|
function M.treesitter(opts)
|
|
return M.new({
|
|
id = "treesitter",
|
|
name = "Treesitter Highlight",
|
|
get = function()
|
|
return vim.b.ts_highlight
|
|
end,
|
|
set = function(state)
|
|
vim.treesitter[state and "start" or "stop"]()
|
|
end,
|
|
}, opts)
|
|
end
|
|
|
|
---@param opts? snacks.toggle.Config
|
|
function M.line_number(opts)
|
|
local number, relativenumber = true, true
|
|
return M.new({
|
|
id = "line_number",
|
|
name = "Line Numbers",
|
|
get = function()
|
|
return vim.opt_local.number:get() or vim.opt_local.relativenumber:get()
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
vim.opt_local.number, vim.opt_local.relativenumber = number, relativenumber
|
|
else
|
|
number, relativenumber = vim.opt_local.number:get(), vim.opt_local.relativenumber:get()
|
|
vim.opt_local.number, vim.opt_local.relativenumber = false, false
|
|
end
|
|
end,
|
|
}, opts)
|
|
end
|
|
|
|
---@param opts? snacks.toggle.Config
|
|
function M.inlay_hints(opts)
|
|
return M.new({
|
|
id = "inlay_hints",
|
|
name = "Inlay Hints",
|
|
get = function()
|
|
return vim.lsp.inlay_hint.is_enabled({ bufnr = 0 })
|
|
end,
|
|
set = function(state)
|
|
vim.lsp.inlay_hint.enable(state, { bufnr = 0 })
|
|
end,
|
|
}, opts)
|
|
end
|
|
|
|
---@param opts? snacks.toggle.Config
|
|
function M.diagnostics(opts)
|
|
return M.new({
|
|
id = "diagnostics",
|
|
name = "Diagnostics",
|
|
get = function()
|
|
local enabled = false
|
|
if vim.diagnostic.is_enabled then
|
|
enabled = vim.diagnostic.is_enabled()
|
|
elseif vim.diagnostic.is_disabled then
|
|
enabled = not vim.diagnostic.is_disabled()
|
|
end
|
|
return enabled
|
|
end,
|
|
set = function(state)
|
|
if vim.fn.has("nvim-0.10") == 0 then
|
|
if state then
|
|
pcall(vim.diagnostic.enable)
|
|
else
|
|
pcall(vim.diagnostic.disable)
|
|
end
|
|
else
|
|
vim.diagnostic.enable(state)
|
|
end
|
|
end,
|
|
}, opts)
|
|
end
|
|
|
|
---@private
|
|
function M.health()
|
|
local ok = pcall(require, "which-key")
|
|
Snacks.health[ok and "ok" or "warn"](("{which-key} is %s"):format(ok and "installed" or "not installed"))
|
|
end
|
|
|
|
function M.profiler()
|
|
return M.new({
|
|
id = "profiler",
|
|
name = "Profiler",
|
|
get = function()
|
|
return Snacks.profiler.running()
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.profiler.start()
|
|
else
|
|
Snacks.profiler.stop()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.profiler_highlights()
|
|
return M.new({
|
|
id = "profiler_highlights",
|
|
name = "Profiler Highlights",
|
|
get = function()
|
|
return Snacks.profiler.ui.enabled
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.profiler.ui.show()
|
|
else
|
|
Snacks.profiler.ui.hide()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.indent()
|
|
return M.new({
|
|
id = "indent",
|
|
name = "Indent Guides",
|
|
get = function()
|
|
return Snacks.indent.enabled
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.indent.enable()
|
|
else
|
|
Snacks.indent.disable()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.dim()
|
|
return M.new({
|
|
id = "dim",
|
|
name = "Dimming",
|
|
get = function()
|
|
return Snacks.dim.enabled
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.dim.enable()
|
|
else
|
|
Snacks.dim.disable()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.words()
|
|
return M.new({
|
|
id = "words",
|
|
name = "LSP Words",
|
|
get = function()
|
|
return Snacks.words.enabled
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.words.enable()
|
|
else
|
|
Snacks.words.disable()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.scroll()
|
|
return M.new({
|
|
id = "scroll",
|
|
name = "Smooth Scroll",
|
|
get = function()
|
|
return Snacks.scroll.enabled
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.scroll.enable()
|
|
else
|
|
Snacks.scroll.disable()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.zen()
|
|
return M.new({
|
|
id = "zen",
|
|
name = "Zen Mode",
|
|
get = function()
|
|
return Snacks.zen.win and Snacks.zen.win:valid() or false
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.zen()
|
|
elseif Snacks.zen.win then
|
|
Snacks.zen.win:close()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.zoom()
|
|
return M.new({
|
|
id = "zoom",
|
|
name = "Zoom Mode",
|
|
get = function()
|
|
return Snacks.zen.win and Snacks.zen.win:valid() or false
|
|
end,
|
|
set = function(state)
|
|
if state then
|
|
Snacks.zen.zoom()
|
|
elseif Snacks.zen.win then
|
|
Snacks.zen.win:close()
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function M.animate()
|
|
return M.new({
|
|
id = "animate",
|
|
name = "Animations",
|
|
get = function()
|
|
return vim.g.snacks_animate ~= false
|
|
end,
|
|
set = function(state)
|
|
vim.g.snacks_animate = state
|
|
end,
|
|
})
|
|
end
|
|
|
|
return M
|