feat: more style controls in config

This commit is contained in:
2026-04-05 10:10:28 +03:00
parent 6a5f8fb22a
commit f20343f312
7 changed files with 234 additions and 21 deletions

View File

@@ -51,6 +51,41 @@ M.defaults = {
multiline = {
height = 5,
},
--- Visual styling
style = {
--- Chevron glyphs shown on the right side of `select` inputs to indicate
--- the dropdown state. Override either to taste (e.g. `"v"`/`"^"` for
--- ASCII, or extra spacing for wider icons).
chevron = {
--- Glyph shown when the dropdown is closed.
closed = "",
--- Glyph shown when the dropdown is open.
open = "",
},
--- Highlight groups applied on every `form:show()`. Each entry is passed
--- directly to `vim.api.nvim_set_hl(0, name, spec)`, so any option that
--- `nvim_set_hl` accepts (`fg`, `bg`, `link`, `bold`, `italic`,
--- `default`, ...) is valid. User overrides fully replace the default
--- spec for the matching group (they are NOT deep-merged field by field).
highlights = {
-- Parent form window
InputFormNormal = { link = "NormalFloat", default = true },
InputFormBorder = { link = "FloatBorder", default = true },
InputFormTitle = { link = "FloatTitle", default = true },
InputFormHelp = { fg = "Cyan", default = true },
-- Individual input fields
InputFormField = { link = "NormalFloat", default = true },
InputFormFieldBorder = { link = "FloatBorder", default = true },
InputFormFieldTitle = { link = "FloatTitle", default = true },
-- Error state for individual input fields
InputFormFieldError = { fg = "Red", default = true },
InputFormFieldErrorBorder = { fg = "Red", default = true },
InputFormFieldErrorTitle = { fg = "Red", default = true },
-- Select dropdown list
InputFormDropdown = { link = "NormalFloat", default = true },
InputFormDropdownActive = { link = "PmenuSel", default = true },
},
},
}
M.options = vim.deepcopy(M.defaults)
@@ -60,7 +95,16 @@ M.options = vim.deepcopy(M.defaults)
---@param user_opts table|nil
---@return table
function M.setup(user_opts)
M.options = utils.merge(vim.deepcopy(M.defaults), user_opts or {})
local merged = utils.merge(vim.deepcopy(M.defaults), user_opts or {})
-- Highlight specs must be replaced per-group, not deep-merged, so a user
-- override like `{ fg = "#ff5555" }` doesn't inherit the default's
-- `default = true` flag (which would let a colorscheme clobber it).
if user_opts and user_opts.style and user_opts.style.highlights then
for name, spec in pairs(user_opts.style.highlights) do
merged.style.highlights[name] = spec
end
end
M.options = merged
return M.options
end

View File

@@ -122,6 +122,10 @@ function M:show()
-- form buffers. Runs once per nvim session.
utils.register_ui_exclusions()
-- Apply configured highlight groups (user-configurable via
-- `setup({ style = { highlights = { ... } } })`).
self:_apply_highlights()
local layout = self:_compute_layout()
self._layout = layout
@@ -161,9 +165,6 @@ function M:show()
win_opts.footer_pos = "center"
end
end
-- Default highlight for the footer (help text): cyan, overridable by the user.
pcall(vim.api.nvim_set_hl, 0, "InputFormHelp", { fg = "Cyan", default = true })
self._parent_win = vim.api.nvim_open_win(self._parent_buf, false, win_opts)
vim.wo[self._parent_win].winblend = config.options.window.winblend
vim.wo[self._parent_win].winhl = table.concat({
@@ -191,16 +192,21 @@ function M:show()
self:_install_validation(input)
end
-- Default highlight groups for validation error state. `default = true`
-- means user overrides take precedence.
pcall(vim.api.nvim_set_hl, 0, "InputFormFieldError", { fg = "Red", default = true })
pcall(vim.api.nvim_set_hl, 0, "InputFormFieldErrorBorder", { fg = "Red", default = true })
pcall(vim.api.nvim_set_hl, 0, "InputFormFieldErrorTitle", { fg = "Red", default = true })
self:_focus(1)
return self
end
--- Apply all configured highlight groups. Called from `show()` so live
--- `setup({ style = { highlights = ... } })` edits take effect on the next
--- form open.
function M:_apply_highlights()
local style = config.options.style or {}
local hls = style.highlights or {}
for name, spec in pairs(hls) do
pcall(vim.api.nvim_set_hl, 0, name, spec)
end
end
--- Hide the form (close windows) but keep state so `:show()` can reopen it.
function M:hide()
if not self._visible then

View File

@@ -48,12 +48,22 @@ local function label_for(options, id)
return ""
end
local CHEVRON_CLOSED = ""
local CHEVRON_OPEN = ""
-- Fallbacks in case the config module has been mutated to a malformed state.
local DEFAULT_CHEVRON_CLOSED = ""
local DEFAULT_CHEVRON_OPEN = ""
local function chevron_for(open)
local style = config.options.style or {}
local chev = style.chevron or {}
if open then
return chev.open or DEFAULT_CHEVRON_OPEN
end
return chev.closed or DEFAULT_CHEVRON_CLOSED
end
local function format_display(options, id, width, open)
local label = label_for(options, id)
local chevron = open and CHEVRON_OPEN or CHEVRON_CLOSED
local chevron = chevron_for(open)
if not width or width <= 0 then
return label .. chevron
end