automate/020_neovim-lazyvim_plugins.sh

899 lines
26 KiB
Bash

#!/usr/bin/env bash
DEST=${1:-/etc/skel}
LAZY_DEST=${DEST}/.config/nvim/lua/plugins
LAZY_UTILS_DEST=${DEST}/.config/nvim/lua/utils
LAZY_SYNTAX=${DEST}/.config/nvim/after/queries
mkdir -p "$LAZY_DEST" "$LAZY_UTILS_DEST"
# Yazi tui filemanager
# For extra configuration options see:
# https://github.com/mikavilpas/yazi.nvim
# https://github.com/mikavilpas/yazi.nvim#%EF%B8%8F%EF%B8%8F-advanced-configuration
conf_print_yazi() {
cat <<EOF
return {
---@type LazySpec
{
"mikavilpas/yazi.nvim",
version = "*", -- use the latest stable version
event = "VeryLazy",
dependencies = {
{ "nvim-lua/plenary.nvim", lazy = true },
},
keys = {
-- 👇 in this section, choose your own keymappings!
{
"<leader>-",
mode = { "n", "v" },
"<cmd>Yazi<cr>",
desc = "Open yazi at the current file",
},
{
-- Open in the current working directory
"<leader>cw",
"<cmd>Yazi cwd<cr>",
desc = "Open the file manager in nvim's working directory",
},
{
"<c-up>",
"<cmd>Yazi toggle<cr>",
desc = "Resume the last yazi session",
},
},
---@type YaziConfig | {}
opts = {
-- if you want to open yazi instead of netrw, see below for more info
open_for_directories = false,
keymaps = {
show_help = "<f1>",
},
},
-- 👇 if you use \$(open_for_directories=true), this is recommended
init = function()
-- mark netrw as loaded so it's not loaded at all.
--
-- More details: https://github.com/mikavilpas/yazi.nvim/issues/802
vim.g.loaded_netrwPlugin = 1
end,
}
}
EOF
}
conf_print_yazi | tee "${LAZY_DEST}/yazi.lua"
conf_print_time-machine() {
cat <<EOF
return {
"y3owk1n/time-machine.nvim",
cmd = {
"TimeMachineToggle",
"TimeMachinePurgeBuffer",
"TimeMachinePurgeAll",
"TimeMachineLogShow",
"TimeMachineLogClear",
},
---@type TimeMachine.Config
opts = {},
keys = {
{
"<leader>t",
"",
desc = "Time Machine",
},
{
"<leader>tt",
"<cmd>TimeMachineToggle<cr>",
desc = "[Time Machine] Toggle Tree",
},
{
"<leader>tx",
"<cmd>TimeMachinePurgeCurrent<cr>",
desc = "[Time Machine] Purge current",
},
{
"<leader>tX",
"<cmd>TimeMachinePurgeAll<cr>",
desc = "[Time Machine] Purge all",
},
{
"<leader>tl",
"<cmd>TimeMachineLogShow<cr>",
desc = "[Time Machine] Show log",
},
},
}
EOF
}
conf_print_time-machine | tee "${LAZY_DEST}/time-machine.lua"
# Use triple backtick for code blocks rather than tab
# set code-block-style: or MD046: om ~/.markdownlint-cli2.yaml
conf_print_markdownlint_yaml() {
cat <<EOF
config:
code-block-style:
style: "fenced"
EOF
}
conf_print_markdownlint_yaml | tee "${DEST}/.markdownlint-cli2.yaml"
conf_print_markdownlint() {
cat <<EOF
return {
{
"mfussenegger/nvim-lint",
opts = {
linters = {
["markdownlint-cli2"] = {
args = { "--config", vim.env.HOME .. "/.markdownlint-cli2.yaml", "--" },
},
},
},
},
}
EOF
}
conf_print_markdownlint | tee "${LAZY_DEST}/lint.lua"
# mise-en-plave
# https://mise.jdx.dev/mise-cookbook/neovim.html
#
config_print_mise_syntax() {
cat <<EOF
return {
"nvim-treesitter/nvim-treesitter",
init = function()
require("vim.treesitter.query").add_predicate("is-mise?", function(_, _, bufnr, _)
local filepath = vim.api.nvim_buf_get_name(tonumber(bufnr) or 0)
local filename = vim.fn.fnamemodify(filepath, ":t")
return string.match(filename, ".*mise.*%.toml$") ~= nil
end, { force = true, all = false })
end,
}
EOF
}
config_print_mise_syntax | tee "${LAZY_DEST}/mise-syntax.lua"
# define Tree-sitter query files that extend or customize syntax highlighting and
# code injection for TOML files
mkdir -p "${LAZY_SYNTAX}/toml"
# languages using # comments
mkdir -p "${LAZY_SYNTAX}/bash"
mkdir -p "${LAZY_SYNTAX}/python"
# languages using // comments
mkdir -p "${LAZY_SYNTAX}/go"
mkdir -p "${LAZY_SYNTAX}/rust"
# Other
mkdir -p "${LAZY_SYNTAX}/markdown"
mkdir -p "${LAZY_SYNTAX}/html"
mkdir -p "${LAZY_SYNTAX}/yaml"
conf_print_toml_injections() {
cat <<'EOF'
; extends
; 1. Multiline shebang /usr/bin/env [ALL TOML]
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content @injection.language
(#match? @injection.language "^'''\\n*#!(/\\w+)+/env\\s+\\w+")
(#gsub! @injection.language "^.*#!/.*/env\\s+([^\\s]+).*" "%1")
(#offset! @injection.content 0 3 0 -3)
)
; 2. Multiline shebang direct path
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content @injection.language
(#match? @injection.language "^'''\\n*#!(/\\w+)+\\s*\\n")
(#gsub! @injection.language "^.*#!/.*/([^/\\s]+).*" "%1")
(#offset! @injection.content 0 3 0 -3)
)
; 3. Multiline no shebang → bash
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content
(#match? @injection.content "^'''\\n")
(#not-match? @injection.content "^'''\\n*#!")
(#offset! @injection.content 0 3 0 -3)
(#set! injection.language "bash")
)
; 4. Single line → bash
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content
(#not-match? @injection.content "^'''")
(#offset! @injection.content 0 1 0 -1)
(#set! injection.language "bash")
)
; --- Mise-specific ONLY ---
(#is-mise?)
; depends_on task references
(pair
(bare_key) @key (#eq? @key "depends_on")
(array (string) @reference.task))
; [tasks] table identification
(table
(bare_key) @key (#eq? @key "tasks"))
; Task definitions under tasks tables
(pair (bare_key) @task.definition)
EOF
}
conf_print_toml_injections | tee "${LAZY_SYNTAX}/toml/injections.scm"
conf_print_bash_injections() {
cat <<EOF
; extends
; ============================================================================
; #MISE comments - TOML injection
; ============================================================================
; This injection captures comment lines starting with "#MISE " or "#[MISE]" or
; "# [MISE]" and treats them as TOML code blocks for syntax highlighting.
;
; #MISE format
; The (#offset!) directive skips the "#MISE " prefix (6 characters) from the source
((comment) @injection.content
(#lua-match? @injection.content "^#MISE ")
(#offset! @injection.content 0 6 0 1)
(#set! injection.language "toml"))
; #[MISE] format
((comment) @injection.content
(#lua-match? @injection.content "^#%[MISE%] ")
(#offset! @injection.content 0 8 0 1)
(#set! injection.language "toml"))
; # [MISE] format
((comment) @injection.content
(#lua-match? @injection.content "^# %[MISE%] ")
(#offset! @injection.content 0 9 0 1)
(#set! injection.language "toml"))
; ============================================================================
; #USAGE comments - KDL injection
; ============================================================================
; This injection captures consecutive comment lines starting with "#USAGE " or
; "#[USAGE]" or "# [USAGE]" and treats them as a single KDL code block for
; syntax highlighting.
;
; #USAGE format
((comment) @injection.content
(#lua-match? @injection.content "^#USAGE ")
; Extend the range one byte to the right, to include the trailing newline.
; see https://github.com/neovim/neovim/discussions/36669#discussioncomment-15054154
(#offset! @injection.content 0 7 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
; #[USAGE] format
((comment) @injection.content
(#lua-match? @injection.content "^#%[USAGE%] ")
(#offset! @injection.content 0 9 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
; # [USAGE] format
((comment) @injection.content
(#lua-match? @injection.content "^# %[USAGE%] ")
(#offset! @injection.content 0 10 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
; NOTE: on neovim >= 0.12, you can use the multi node pattern instead of
; combining injections:
;
; ((comment)+ @injection.content
; (#lua-match? @injection.content "^#USAGE ")
; (#offset! @injection.content 0 7 0 1)
; (#set! injection.language "kdl"))
;
; this is the preferred way as combined injections have multiple
; limitations:
; https://github.com/neovim/neovim/issues/32635
EOF
}
conf_print_bash_injections | tee "${LAZY_SYNTAX}/bash/injections.scm"
conf_print_bash_injections | tee "${LAZY_SYNTAX}/python/injections.scm"
conf_print_go_injections() {
cat <<EOF
((comment) @injection.content
(#lua-match? @injection.content "^//MISE ")
(#offset! @injection.content 0 7 0 1)
(#set! injection.language "toml"))
((comment) @injection.content
(#lua-match? @injection.content "^//%[MISE%] ")
(#offset! @injection.content 0 9 0 1)
(#set! injection.language "toml"))
((comment) @injection.content
(#lua-match? @injection.content "^// %[MISE%] ")
(#offset! @injection.content 0 10 0 1)
(#set! injection.language "toml"))
((comment) @injection.content
(#lua-match? @injection.content "^//USAGE ")
(#offset! @injection.content 0 8 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
((comment) @injection.content
(#lua-match? @injection.content "^//%[USAGE%] ")
(#offset! @injection.content 0 10 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
((comment) @injection.content
(#lua-match? @injection.content "^// %[USAGE%] ")
(#offset! @injection.content 0 11 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
EOF
}
conf_print_go_injections | tee "${LAZY_SYNTAX}/go/injections.scm"
conf_print_go_injections | tee "${LAZY_SYNTAX}/rust/injections.scm"
# To only apply the highlighting on mise files instead of all toml files, the
# is-mise? predicate is used. THe following will consider any toml file
# containing mise in its name as a mise file.
# .mise.toml
# mise.toml
# ~/.config/mise/config.toml
# ~/.config/mise/conf.d/*.toml
# ./mise/config.toml
# ./.mise/config.toml
# ./mise/something.toml
conf_print_predicates() {
cat <<'EOF'
return {
"nvim-treesitter/nvim-treesitter",
init = function()
require("vim.treesitter.query").add_predicate("is-mise?", function(match, _, bufnr, _)
local filepath = vim.api.nvim_buf_get_name(bufnr)
-- 1. Must be a .toml file (case-insensitive on Windows is automatic via Neovim)
if not filepath:match("%.toml$") then
return false
end
local filename = vim.fn.fnamemodify(filepath, ":t")
-- 2. Filename contains "mise" (covers .mise.toml, mise.toml, mise.local.toml, mise.dev.toml, etc.)
if filename:find("mise") then
return true
end
-- 3. Path contains "/mise/" or "\mise\" as a directory component
-- Catches ~/.config/mise/config.toml, .mise/config.toml, mise/conf.d/*.toml, etc.
-- The [\/\\] pattern handles both Unix and Windows separators
if filepath:find("[/\\]mise[/\\]") then
return true
end
return false
end, { force = true })
end,
}
EOF
}
conf_print_predicates | tee "${LAZY_DEST}/mise-treesitter.lua"
# otter https://github.com/jmbuhr/otter.nvim
# https://github.com/jmbuhr/LazyVim/blob/main/lua/lazyvim/plugins/extras/lsp/otter.lua
# provides lsp features, including code completion, for code embedded in other documents
conf_print_mise_otter_lsp() {
cat <<'EOF'
return {
"jmbuhr/otter.nvim",
dependencies = { "nvim-treesitter/nvim-treesitter" },
config = function()
vim.api.nvim_create_autocmd("FileType", {
pattern = "toml",
callback = function()
local full_path = vim.fn.expand("%:p")
-- Matches 'mise' anywhere in the path to activate Otter
if string.match(full_path, "mise") then
require("otter").activate({"bash", "sh", "python", "lua"})
end
end,
})
end,
}
EOF
}
conf_print_mise_otter_lsp | tee "${LAZY_DEST}/mise-otter.lua"
# define Tree-sitter query files that extend or customize syntax highlighting and
# code injection for TOML files
mkdir -p "${LAZY_SYNTAX}/toml"
# languages using # comments
mkdir -p "${LAZY_SYNTAX}/bash"
mkdir -p "${LAZY_SYNTAX}/python"
# languages using // comments
mkdir -p "${LAZY_SYNTAX}/go"
mkdir -p "${LAZY_SYNTAX}/rust"
# Other
mkdir -p "${LAZY_SYNTAX}/markdown"
mkdir -p "${LAZY_SYNTAX}/html"
mkdir -p "${LAZY_SYNTAX}/yaml"
# Mise only injections captured above in the the mixed toml_injections()
# conf_print_toml_injections() {
# cat <<EOF
# ; extends
#
# (pair
# (bare_key) @key (#eq? @key "run")
# (string) @injection.content @injection.language
#
# (#is-mise?)
# (#match? @injection.language "^['\"]{3}\n*#!(/\\w+)+/env\\s+\\w+") ; multiline shebang using env
# (#gsub! @injection.language "^.*#!/.*/env%s+([^%s]+).*" "%1") ; extract lang
# (#offset! @injection.content 0 3 0 -3) ; rm quotes
# )
#
# (pair
# (bare_key) @key (#eq? @key "run")
# (string) @injection.content @injection.language
#
# (#is-mise?)
# (#match? @injection.language "^['\"]{3}\n*#!(/\\w+)+\s*\n") ; multiline shebang
# (#gsub! @injection.language "^.*#!/.*/([^/%s]+).*" "%1") ; extract lang
# (#offset! @injection.content 0 3 0 -3) ; rm quotes
# )
#
# (pair
# (bare_key) @key (#eq? @key "run")
# (string) @injection.content
#
# (#is-mise?)
# (#match? @injection.content "^['\"]{3}\n*.*") ; multiline
# (#not-match? @injection.content "^['\"]{3}\n*#!") ; no shebang
# (#offset! @injection.content 0 3 0 -3) ; rm quotes
# (#set! injection.language "bash") ; default to bash
# )
#
# (pair
# (bare_key) @key (#eq? @key "run")
# (string) @injection.content
#
# (#is-mise?)
# (#not-match? @injection.content "^['\"]{3}") ; not multiline
# (#offset! @injection.content 0 1 0 -1) ; rm quotes
# (#set! injection.language "bash") ; default to bash
# )
# EOF
# }
# conf_print_toml_injections | tee "${LAZY_SYNTAX}/toml/injections.scm"
conf_print_kitty_scrollback() {
cat <<'EOF'
return {
'mikesmithgh/kitty-scrollback.nvim',
enabled = true,
lazy = true,
cmd = {
'KittyScrollbackGenerateKittens',
'KittyScrollbackCheckHealth',
'KittyScrollbackGenerateCommandLineEditing',
},
event = { 'User KittyScrollbackLaunch' },
config = function()
require('kitty-scrollback').setup({
-- ────────────────────────────────────────────────
-- Global defaults applied to all configurations
-- ────────────────────────────────────────────────
status_window = {
enabled = true,
style_simple = false, -- use nice Nerd Font icons
autoclose = true, -- auto close after loaded
show_timer = true, -- shows loading time (great for debugging slow startups)
icons = {
kitty = '󰄛', -- or  /  etc.
heart = '󰣐',
nvim = '',
},
},
paste_window = {
filetype = nil, -- auto-detect shell (bash/zsh/fish)
hide_footer = false,
yank_register = '', -- default register
yank_register_enabled = true,
winblend = 0, -- no transparency (clean look)
},
-- Useful callbacks & extras
callbacks = {
after_ready = nil, -- can be overridden per config
},
-- ────────────────────────────────────────────────
-- Named configurations you can call with --config <name>
-- ────────────────────────────────────────────────
-- Default (what you get with just kitty_scrollback_nvim)
default = {},
-- Opens scrollback and immediately starts backward search (?)
search = {
callbacks = {
after_ready = function()
vim.api.nvim_feedkeys('?', 'n', false)
end,
},
},
-- Only shows the last command output (very clean for quick checks)
last_cmd = {
kitty_get_text = {
extent = 'last_non_empty_output', -- or 'output' / 'screen'
},
status_window = {
enabled = false, -- cleaner without status if only last cmd
},
},
})
end,
}
EOF
}
conf_print_kitty_scrollback | tee "${LAZY_DEST}/kitty-scrollback.lua"
conf_print_diagram() {
cat <<'EOF'
return {
"3rd/diagram.nvim",
dependencies = {
{ "3rd/image.nvim", opts = {} }, -- you'd probably want to configure image.nvim manually instead of doing this
},
opts = { -- you can just pass {}, defaults below
events = {
render_buffer = { "InsertLeave", "BufWinEnter", "TextChanged" },
clear_buffer = {"BufLeave"},
},
renderer_options = {
mermaid = {
background = nil, -- nil | "transparent" | "white" | "#hex"
theme = nil, -- nil | "default" | "dark" | "forest" | "neutral"
scale = 1, -- nil | 1 (default) | 2 | 3 | ...
width = nil, -- nil | 800 | 400 | ...
height = nil, -- nil | 600 | 300 | ...
cli_args = nil, -- nil | { "--no-sandbox" } | { "-p", "/path/to/puppeteer" } | ...
},
plantuml = {
charset = nil,
cli_args = nil, -- nil | { "-Djava.awt.headless=true" } | ...
},
d2 = {
theme_id = nil,
dark_theme_id = nil,
scale = nil,
layout = nil,
sketch = nil,
cli_args = nil, -- nil | { "--pad", "0" } | ...
},
gnuplot = {
size = nil, -- nil | "800,600" | ...
font = nil, -- nil | "Arial,12" | ...
theme = nil, -- nil | "light" | "dark" | custom theme string
cli_args = nil, -- nil | { "-p" } | { "-c", "config.plt" } | ...
},
},
}
}
EOF
}
conf_print_diagram | tee "${LAZY_DEST}/diagram.lua"
# frizbee fuzzy finder replaces telescope (native lua) with rust library
# relying on SIMD which older hardware doesn't have, very clever if you do.
conf_print_fzf_lua_no_frizbee() {
cat <<'EOF'
return {
{
"ibhagwan/fzf-lua",
opts = function(_, opts)
-- Force the standard fzf algorithm (much lighter on CPU)
opts.fzf_opts = opts.fzf_opts or {}
opts.fzf_opts["--algo"] = "v1" -- 'v1' is the original, fast algorithm
-- Optional: Disable the scrollbar if you're on a very old fzf version
-- as it has been known to cause crashes on older binaries.
-- opts.fzf_opts["--no-scrollbar"] = true
end,
},
}
EOF
}
conf_print_fzf_lua_no_frizbee | tee "${LAZY_DEST}/fzf_lua_no_frizbee.lua"
conf_print_blink_no_frizbee() {
cat <<'EOF'
return {
{
"saghen/blink.cmp",
opts = {
fuzzy = {
-- 'frizbee' is the new default; 'fuzzy' is the old reliable
implementation = "lua",
},
},
},
}
EOF
}
conf_print_blink_no_frizbee | tee "${LAZY_DEST}/blink_no_frizbee.lua"
#
conf_print_local_parser_path() {
cat <<EOF
return {
{
dir = "/home/default/.local/share/nvim/site", -- Points to the local path
lazy = false,
priority = 1000, -- Load this early
config = function()
vim.opt.runtimepath:append("/home/default/.local/share/nvim/site")
end,
}
}
EOF
}
conf_print_local_parser_path | tee "${LAZY_DEST}/local_parser_path.lua"
############################
# Hacking Below this Point #
############################
#
# Conform.nvim formatters, append new formatters here.
# Use the official bash formatter on sh files using conform.nvim
# Understands heredocs <<-EOF tab indent/removal
# gofumpt strict formatter for go
#
# conf_print_conform_formatters() {
# cat << 'EOF'
# return {
# "stevearc/conform.nvim",
# opts = {
# formatters_by_ft = {
# sh = { "shfmt" }, bash = { "shfmt" }, zsh = { "shfmt" },
# go = { "goimports", "gofumpt" },
# },
# formatters = {
# shfmt = {
# prepend_args = { "-ln", "bash", "-i", "0", "-ci", "-sr", "-bn", "-kp" },
# },
# },
# -- these are top-level opts
# format = {
# async = false, -- synchronous for visual selection
# timeout_ms = 500,
# lsp_format = "fallback",
# },
# log_level = vim.log.levels.debug,
# format_on_save = function(bufnr)
# if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then return end
# return { timeout_ms = 500, lsp_format = "fallback" }
# end,
# },
# }
# EOF
# }
# conf_print_conform_formatters | tee "${LAZY_DEST}/conform-formatters.lua"
#
# conf_print_conform_conform() {
# cat <<- 'EOF'
# vim.keymap.set("n", "<leader>uf", function()
# local is_disabled = vim.g.disable_autoformat or vim.b.disable_autoformat
# if is_disabled then
# -- Re-enable logic (global + buffer)
# vim.b.disable_autoformat = false
# vim.g.disable_autoformat = false
# vim.notify("Autoformat enabled", vim.log.levels.INFO)
# else
# vim.b.disable_autoformat = true
# vim.notify("Autoformat disabled for buffer", vim.log.levels.WARN)
# end
# end, { desc = "Toggle autoformat" })
#
# return {
# "stevearc/conform.nvim",
# opts = function(_, opts)
# -- Merge formatters (preserves LazyVim defaults)
# opts.formatters_by_ft = vim.tbl_extend("force", opts.formatters_by_ft or {}, {
# sh = { "shfmt" },
# go = { "goimports", "gofumpt" },
# })
#
# -- shfmt with your exact flags (heredoc-friendly)
# opts.formatters = vim.tbl_extend("force", opts.formatters or {}, {
# shfmt = {
# prepend_args = { "-ln", "bash", "-i", "0", "-ci", "-sr", "-bn", "-kp" },
# },
# })
#
# -- Format-on-save with guard clause (global/buffer priority)
# opts.format_on_save = function(bufnr)
# if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
# return
# end
# return {
# timeout_ms = 500,
# lsp_format = "fallback",
# }
# end
# end,
# }
# EOF
# }
# conf_print_conform_conform | tee "${PLUGINS_HOME}/conform.lua"
# conf_print_conform_keymaps() {
# cat << 'EOF'
# conf_print_conform_keymaps() {
# cat << 'EOF'
# return {
# "stevearc/conform.nvim",
# keys = {
# {
# "<leader>fs",
# function()
# require("conform").format({ async = false, lsp_fallback = true, timeout_ms = 500 })
# end,
# mode = { "n", "v" },
# desc = "Format selection or buffer",
# },
# {
# "<leader>tf",
# function()
# if vim.g.disable_autoformat or vim.b.disable_autoformat then
# vim.b.disable_autoformat = false
# vim.g.disable_autoformat = false
# print("Autoformat enabled")
# else
# vim.g.disable_autoformat = true
# print("Autoformat disabled")
# end
# end,
# mode = "n",
# desc = "Toggle Autoformat on Save",
# },
# },
# }
# EOF
# }
# conf_print_conform_keymaps | tee "${LAZY_DEST}/conform-keymaps.lua"
conf_print_conform_lualine() {
cat <<-'EOF'
return {
"nvim-lualine/lualine.nvim",
opts = function(_, opts)
table.insert(opts.sections.lualine_x, {
function()
if vim.g.disable_autoformat or vim.b.disable_autoformat then
return "󰉐 OFF"
end
return nil
end,
color = { fg = "#ff9e64", gui = "bold" },
})
end,
}
EOF
}
conf_print_conform_lualine | tee "${LAZY_DEST}/lualine.lua"
# # A quick audit of the key systems tied to your formatting workflow and status UI.
# # Call with:
# # :lua require("utils.checklist").run()
#
# conf_print_conform_checklist() {
# cat << EOF
# -- ~/.config/nvim/lua/utils/checklist.lua
# -- Neovim Configuration Health Check (Formatting & UI Systems)
#
# local M = {}
#
# local status = {
# header = "🧭 Neovim Config Checklist",
# items = {},
# }
#
# -- Utility function
# local function add_result(name, ok, detail)
# table.insert(status.items, {
# name = name,
# ok = ok,
# detail = detail or "",
# })
# end
#
# local function check_plugin(name)
# local ok, plugin = pcall(require, name)
# add_result(name, ok, ok and "Loaded ✅" or "Missing ❌")
# return ok, plugin
# end
#
# function M.run()
# status.items = {}
#
# -- Basic checks
# add_result("Neovim version", vim.fn.has("nvim-0.9") == 1, vim.version().string)
#
# -- Plugin checks
# local c_ok = check_plugin("conform")
# local l_ok = check_plugin("lualine")
# local w_ok = check_plugin("which-key")
#
# -- Conform.nvim sanity
# if c_ok then
# local conform = require("conform")
# add_result(
# "Conform config",
# type(conform.formatters_by_ft) == "table",
# "Formatter map loaded: " .. tostring(conform.formatters_by_ft and "yes" or "no")
# )
# end
#
# -- Autoformat toggles
# local global_toggle = vim.g.disable_autoformat
# local buf_toggle = vim.b.disable_autoformat
# add_result("Global autoformat state", global_toggle ~= true, tostring(global_toggle or false))
# add_result("Buffer autoformat state", buf_toggle ~= true, tostring(buf_toggle or false))
#
# -- Keymap validation
# local keymaps = vim.api.nvim_get_keymap("n")
# local uf_found = false
# for _, km in ipairs(keymaps) do
# if km.lhs == "<leader>uf" then
# uf_found = true
# add_result("<leader>uf Keymap", true, km.rhs)
# break
# end
# end
# if not uf_found then
# add_result("<leader>uf Keymap", false, "Not found ❌")
# end
#
# -- Lualine check
# if l_ok then
# local lualine_ok = pcall(require("lualine").setup)
# add_result("Lualine setup callable", lualine_ok, lualine_ok and "OK" or "Fail to load")
# end
#
# -- Which-key registry
# if w_ok then
# local wk = require("which-key")
# local has_wk = wk.register ~= nil
#
# -- Print results
# vim.notify(status.header, vim.log.levels.INFO, { title = "Checklist" })
# for _, item in ipairs(status.items) do
# local level = item.ok and vim.log.levels.INFO or vim.log.levels.WARN
# local icon = item.ok and "✅" or "❌"
# vim.notify(string.format("%s %s — %s", icon, item.name, item.detail), level, { title = "Checklist" })
# end
# end
#
# return M
# EOF
# }
# conf_print_conform_checklist | tee "${LAZY_UTILS_DEST}/checklist.lua"