update nvim

This commit is contained in:
kYasuda
2026-09-01 15:15:53 -07:00
parent 17086f43ec
commit 447b90e3c2
5 changed files with 704 additions and 16 deletions
+1
View File
@@ -8,3 +8,4 @@ WRN 2026-08-28T16:41:19.378 ?.2 server_start:199: Failed to start server:
WRN 2026-08-31T13:42:03.306 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0 WRN 2026-08-31T13:42:03.306 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-08-31T13:42:57.702 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0 WRN 2026-08-31T13:42:57.702 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-08-31T13:43:16.053 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0 WRN 2026-08-31T13:43:16.053 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-09-01T13:41:43.821 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
+2 -2
View File
@@ -28,7 +28,7 @@
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" }, "lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
"mcphub.nvim": { "branch": "main", "commit": "7cd5db330f41b7bae02b2d6202218a061c3ebc1f" }, "mcphub.nvim": { "branch": "main", "commit": "7cd5db330f41b7bae02b2d6202218a061c3ebc1f" },
"mini.diff": { "branch": "main", "commit": "626b8a5b93874c4d05ca25aedec56cfff0b378fb" }, "mini.diff": { "branch": "main", "commit": "626b8a5b93874c4d05ca25aedec56cfff0b378fb" },
"mini.nvim": { "branch": "main", "commit": "a311ed247d527db9592bb434356badc705363746" }, "mini.nvim": { "branch": "main", "commit": "9d01f392b33fb2ba36fbc87fc0bf4453e63ffb0a" },
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" }, "noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
"nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" }, "nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
"nvim-autopairs": { "branch": "master", "commit": "430522f95fe4fb7c511ec64f8c1a90cc6a66c05c" }, "nvim-autopairs": { "branch": "master", "commit": "430522f95fe4fb7c511ec64f8c1a90cc6a66c05c" },
@@ -36,7 +36,7 @@
"nvim-colorizer.lua": { "branch": "master", "commit": "a065833f35a3a7cc3ef137ac88b5381da2ba302e" }, "nvim-colorizer.lua": { "branch": "master", "commit": "a065833f35a3a7cc3ef137ac88b5381da2ba302e" },
"nvim-html-css": { "branch": "main", "commit": "3f246f6166e75fb8afc1866ce8f1e4cb8d95757a" }, "nvim-html-css": { "branch": "main", "commit": "3f246f6166e75fb8afc1866ce8f1e4cb8d95757a" },
"nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" }, "nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" },
"nvim-lspconfig": { "branch": "master", "commit": "16286347bdba1333c7d124d9de9fe6630731b2b2" }, "nvim-lspconfig": { "branch": "master", "commit": "ee1e369181a9e64904cdd7b739e98c70778cfc71" },
"nvim-nio": { "branch": "master", "commit": "edcc181a875301dd21840189aa2f2f9ad69fc172" }, "nvim-nio": { "branch": "master", "commit": "edcc181a875301dd21840189aa2f2f9ad69fc172" },
"nvim-treesitter": { "branch": "master", "commit": "cf12346a3414fa1b06af75c79faebe7f76df080a" }, "nvim-treesitter": { "branch": "master", "commit": "cf12346a3414fa1b06af75c79faebe7f76df080a" },
"nvim-treesitter-context": { "branch": "master", "commit": "f3061339b8eaf9fda873600bc425b8d2d8502533" }, "nvim-treesitter-context": { "branch": "master", "commit": "f3061339b8eaf9fda873600bc425b8d2d8502533" },
+9 -11
View File
@@ -336,19 +336,19 @@ return {
-- log_level = "TRACE", -- log_level = "TRACE",
}, },
extensions = { extensions = {
-- mcphub = { mcphub = {
-- callback = "mcphub.extensions.codecompanion", callback = "mcphub.extensions.codecompanion",
-- opts = { opts = {
-- show_result_in_chat = true, -- Show the mcp tool result in the chat buffer show_result_in_chat = true, -- Show the mcp tool result in the chat buffer
-- make_vars = true, -- make chat #variables from MCP server resources make_vars = true, -- make chat #variables from MCP server resources
-- make_slash_commands = true, -- make /slash_commands from MCP server prompts make_slash_commands = true, -- make /slash_commands from MCP server prompts
-- }, },
-- }, },
}, },
memory = { memory = {
opts = { opts = {
chat = { chat = {
enabled = true, enabled = false,
}, },
}, },
}, },
@@ -365,8 +365,6 @@ return {
"AGENT.md", "AGENT.md",
"AGENTS.md", "AGENTS.md",
{ path = "CLAUDE.md", parser = "claude" }, { path = "CLAUDE.md", parser = "claude" },
{ path = "CLAUDE.local.md", parser = "claude" },
{ path = "~/.claude/CLAUDE.md", parser = "claude" },
}, },
is_preset = true, is_preset = true,
}, },
+38 -3
View File
@@ -1,14 +1,49 @@
local mcphub_spinner_frames = { "", "", "", "", "", "", "", "", "", "" }
local function mcphub_status()
if not vim.g.loaded_mcphub then
return "󰐻 -"
end
local status = vim.g.mcphub_status or "stopped"
if status == "stopped" then
return "󰐻 -"
end
if vim.g.mcphub_executing or status == "starting" or status == "restarting" then
local frame = math.floor(vim.uv.now() / 100) % #mcphub_spinner_frames + 1
return "󰐻 " .. mcphub_spinner_frames[frame]
end
return "󰐻 " .. (vim.g.mcphub_servers_count or 0)
end
local function mcphub_color()
if not vim.g.loaded_mcphub then
return { fg = "#6c7086" }
end
local status = vim.g.mcphub_status or "stopped"
if status == "ready" or status == "restarted" then
return { fg = "#50fa7b" }
elseif status == "starting" or status == "restarting" then
return { fg = "#ffb86c" }
end
return { fg = "#ff5555" }
end
return { return {
"nvim-lualine/lualine.nvim", "nvim-lualine/lualine.nvim",
dependencies = { dependencies = {
"AndreM222/copilot-lualine", "AndreM222/copilot-lualine",
"nvim-tree/nvim-web-devicons", "nvim-tree/nvim-web-devicons",
"ravitemer/mcphub.nvim",
}, },
config = function() config = function()
require("lualine").setup({ require("lualine").setup({
options = { options = {
theme = "catppuccin", -- theme = "catppuccin",
theme = "auto",
component_separators = { left = "", right = "" }, component_separators = { left = "", right = "" },
section_separators = { left = "", right = "" }, section_separators = { left = "", right = "" },
}, },
@@ -18,7 +53,7 @@ return {
lualine_c = { "filename" }, lualine_c = { "filename" },
lualine_x = { lualine_x = {
"searchcount", "searchcount",
require("mcphub.extensions.lualine"), { mcphub_status, color = mcphub_color },
{ {
"copilot", "copilot",
symbols = { symbols = {
+654
View File
@@ -0,0 +1,654 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Neovim Config Field Manual</title>
<script>document.documentElement.classList.add("js");</script>
<style>
:root {
--base: #24273a;
--mantle: #1e2030;
--crust: #181926;
--surface: #363a4f;
--surface-high: #494d64;
--text: #cad3f5;
--subtext: #a5adcb;
--muted: #6e738d;
--green: #a6da95;
--teal: #8bd5ca;
--yellow: #eed49f;
--peach: #f5a97f;
--red: #ed8796;
--blue: #8aadf4;
--mauve: #c6a0f6;
--line: color-mix(in srgb, var(--surface-high) 72%, transparent);
--mono: "JetBrains Mono", "Cascadia Code", "SFMono-Regular", monospace;
--serif: "Iowan Old Style", "Baskerville", "Palatino Linotype", serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
color: var(--text);
background:
radial-gradient(circle at 78% 12%, rgb(139 213 202 / 9%), transparent 26rem),
linear-gradient(90deg, rgb(255 255 255 / 2%) 1px, transparent 1px),
linear-gradient(rgb(255 255 255 / 2%) 1px, transparent 1px),
var(--crust);
background-size: auto, 44px 44px, 44px 44px, auto;
font-family: var(--serif);
font-size: 17px;
line-height: 1.65;
}
a { color: inherit; }
button { font: inherit; }
::selection { color: var(--crust); background: var(--green); }
:focus-visible {
outline: 2px solid var(--yellow);
outline-offset: 4px;
}
.reading-progress {
position: fixed;
top: 0;
left: 0;
z-index: 20;
width: 0;
height: 3px;
background: linear-gradient(90deg, var(--green), var(--teal));
box-shadow: 0 0 18px var(--teal);
}
.layout {
display: grid;
grid-template-columns: 18rem minmax(0, 1fr);
min-height: 100vh;
}
.rail {
position: sticky;
top: 0;
height: 100vh;
padding: 2rem 1.5rem;
border-right: 1px solid var(--line);
background: rgb(24 25 38 / 84%);
backdrop-filter: blur(14px);
}
.brand {
display: grid;
grid-template-columns: 2.5rem 1fr;
gap: 0.8rem;
align-items: center;
margin-bottom: 3rem;
}
.brand-mark {
display: grid;
place-items: center;
width: 2.5rem;
aspect-ratio: 1;
color: var(--crust);
background: var(--green);
font: 800 1.35rem/1 var(--mono);
clip-path: polygon(16% 0, 100% 0, 84% 100%, 0 100%);
}
.brand strong,
.brand small { display: block; font-family: var(--mono); }
.brand strong { color: var(--green); font-size: 0.82rem; letter-spacing: 0.12em; text-transform: uppercase; }
.brand small { color: var(--muted); font-size: 0.68rem; }
.nav-label,
.eyebrow,
.kicker {
font: 700 0.68rem/1.2 var(--mono);
letter-spacing: 0.16em;
text-transform: uppercase;
}
.nav-label { color: var(--muted); }
.rail nav { display: grid; gap: 0.22rem; margin-top: 0.8rem; }
.rail nav a {
position: relative;
padding: 0.55rem 0.75rem 0.55rem 1.2rem;
color: var(--subtext);
font: 500 0.76rem/1.3 var(--mono);
text-decoration: none;
transition: color 160ms ease, transform 160ms ease;
}
.rail nav a::before {
content: "";
position: absolute;
left: 0;
top: 50%;
width: 0.45rem;
height: 1px;
background: var(--surface-high);
transition: width 160ms ease, background 160ms ease;
}
.rail nav a:hover,
.rail nav a.active { color: var(--green); transform: translateX(0.18rem); }
.rail nav a.active::before { width: 0.8rem; background: var(--green); }
.rail-foot {
position: absolute;
left: 1.5rem;
right: 1.5rem;
bottom: 1.7rem;
padding-top: 1rem;
border-top: 1px solid var(--line);
color: var(--muted);
font: 0.68rem/1.55 var(--mono);
}
main { min-width: 0; overflow: hidden; }
.hero,
.section { padding-inline: clamp(2rem, 7vw, 7rem); }
.hero {
position: relative;
display: grid;
align-content: center;
min-height: 92vh;
padding-block: 6rem;
border-bottom: 1px solid var(--line);
}
.hero::after {
content: "NVIM";
position: absolute;
right: -0.08em;
bottom: -0.24em;
z-index: -1;
color: rgb(166 218 149 / 4%);
font: 900 clamp(8rem, 22vw, 21rem)/1 var(--mono);
letter-spacing: -0.12em;
pointer-events: none;
}
.eyebrow { color: var(--teal); }
h1 {
max-width: 12ch;
margin: 1.2rem 0 1.4rem;
font: 800 clamp(3.2rem, 8vw, 7.8rem)/0.91 var(--mono);
letter-spacing: -0.07em;
}
h1 .outline { color: transparent; -webkit-text-stroke: 1px var(--green); }
.lede { max-width: 43rem; margin: 0; color: var(--subtext); font-size: clamp(1.1rem, 2vw, 1.4rem); }
.hero-meta {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-top: 2.5rem;
}
.chip {
padding: 0.42rem 0.7rem;
border: 1px solid var(--line);
color: var(--subtext);
background: rgb(36 39 58 / 70%);
font: 0.68rem/1 var(--mono);
letter-spacing: 0.04em;
}
.chip.good { color: var(--green); border-color: rgb(166 218 149 / 40%); }
.section {
padding-block: clamp(5rem, 10vw, 9rem);
border-bottom: 1px solid var(--line);
}
.section-head {
display: grid;
grid-template-columns: minmax(0, 0.45fr) minmax(20rem, 1fr);
gap: clamp(2rem, 6vw, 7rem);
margin-bottom: 4rem;
}
.section-number { color: var(--muted); font: 0.72rem var(--mono); }
h2 { margin: 0.55rem 0 0; font: 750 clamp(2rem, 4.5vw, 4.6rem)/1 var(--mono); letter-spacing: -0.055em; }
.section-head p { align-self: end; max-width: 43rem; margin: 0; color: var(--subtext); font-size: 1.08rem; }
.boot-flow {
display: grid;
grid-template-columns: repeat(7, minmax(8rem, 1fr));
overflow-x: auto;
padding: 1.5rem 0 0.6rem;
scrollbar-color: var(--surface-high) transparent;
}
.boot-step {
position: relative;
min-height: 13rem;
padding: 1.1rem 1rem;
border-top: 1px solid var(--surface-high);
border-bottom: 1px solid var(--surface-high);
border-left: 1px solid var(--surface-high);
background: linear-gradient(180deg, rgb(54 58 79 / 52%), rgb(30 32 48 / 62%));
}
.boot-step:last-child { border-right: 1px solid var(--surface-high); }
.boot-step::after {
content: "";
position: absolute;
right: -0.5rem;
top: 50%;
z-index: 2;
color: var(--green);
font: 1.4rem var(--mono);
transform: translateY(-50%);
}
.boot-step:last-child::after { display: none; }
.boot-index { color: var(--green); font: 0.72rem var(--mono); }
.boot-step h3 { margin: 2.4rem 0 0.8rem; font: 700 0.82rem/1.3 var(--mono); }
.boot-step p { margin: 0; color: var(--muted); font: 0.72rem/1.55 var(--mono); }
.ownership {
display: grid;
grid-template-columns: 0.8fr 1.2fr;
border: 1px solid var(--line);
background: rgb(30 32 48 / 58%);
}
.owner-list { border-right: 1px solid var(--line); }
.owner {
display: grid;
grid-template-columns: 2.2rem 1fr;
gap: 0.8rem;
padding: 1.1rem 1.2rem;
border-bottom: 1px solid var(--line);
cursor: pointer;
transition: background 150ms ease;
}
.owner:last-child { border-bottom: 0; }
.owner:hover,
.owner.active { background: rgb(139 213 202 / 8%); }
.owner-key { color: var(--teal); font: 700 0.72rem var(--mono); }
.owner strong { display: block; font: 700 0.8rem var(--mono); }
.owner span { color: var(--muted); font: 0.7rem var(--mono); }
.owner-detail { display: grid; align-content: center; min-height: 26rem; padding: clamp(2rem, 5vw, 5rem); }
.owner-detail > div { display: none; }
.owner-detail > div.active { display: block; animation: detail-in 240ms ease both; }
.owner-detail .kicker { color: var(--yellow); }
.owner-detail h3 { margin: 0.8rem 0 1rem; font: 700 clamp(1.6rem, 3vw, 3rem)/1.05 var(--mono); }
.owner-detail p { max-width: 38rem; color: var(--subtext); }
.split {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
background: var(--line);
border: 1px solid var(--line);
}
.panel { padding: clamp(1.5rem, 4vw, 3.5rem); background: var(--mantle); }
.panel .kicker { color: var(--peach); }
.panel h3 { margin: 1rem 0; font: 700 1.35rem var(--mono); }
.panel p { color: var(--subtext); }
code,
pre { font-family: var(--mono); }
code { color: var(--teal); font-size: 0.86em; }
pre {
position: relative;
overflow: auto;
margin: 1.5rem 0 0;
padding: 1.3rem;
border: 1px solid var(--surface);
background: var(--crust);
color: var(--text);
font-size: 0.76rem;
line-height: 1.7;
tab-size: 2;
}
.lua-key { color: var(--mauve); }
.lua-str { color: var(--green); }
.lua-note { color: var(--muted); }
.mapping-rule {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 1.2rem;
align-items: stretch;
}
.rule-box { padding: 2rem; border: 1px solid var(--line); background: rgb(36 39 58 / 76%); }
.rule-box h3 { margin: 0 0 1rem; color: var(--green); font: 700 1rem var(--mono); }
.rule-box p { margin-bottom: 0; color: var(--subtext); }
.rule-arrow { display: grid; place-items: center; color: var(--muted); font: 1.6rem var(--mono); }
.removed {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--line);
border: 1px solid var(--line);
}
.removed article { min-height: 13rem; padding: 1.5rem; background: var(--mantle); }
.removed del { color: var(--red); font: 700 0.75rem var(--mono); text-decoration-thickness: 2px; }
.removed strong { display: block; margin-top: 2rem; color: var(--green); font: 0.84rem var(--mono); }
.removed p { color: var(--muted); font-size: 0.9rem; }
.checks { display: grid; gap: 0.8rem; max-width: 64rem; }
.check {
display: grid;
grid-template-columns: 3rem 1fr auto;
gap: 1rem;
align-items: center;
padding: 1rem;
border: 1px solid var(--line);
background: var(--mantle);
}
.check-state { color: var(--green); font: 700 0.75rem var(--mono); }
.check code { overflow-wrap: anywhere; }
.copy {
padding: 0.5rem 0.7rem;
border: 1px solid var(--surface-high);
color: var(--subtext);
background: transparent;
font: 0.65rem var(--mono);
cursor: pointer;
}
.copy:hover { color: var(--crust); border-color: var(--green); background: var(--green); }
.callout {
margin-top: 3rem;
padding: 1.4rem 1.5rem;
border-left: 3px solid var(--yellow);
background: rgb(238 212 159 / 7%);
color: var(--subtext);
}
.callout strong { color: var(--yellow); font-family: var(--mono); }
footer {
display: flex;
justify-content: space-between;
gap: 2rem;
padding: 3rem clamp(2rem, 7vw, 7rem);
color: var(--muted);
font: 0.68rem var(--mono);
}
.js .reveal { opacity: 0; transform: translateY(1rem); }
.reveal.visible { animation: reveal 600ms cubic-bezier(0.2, 0.8, 0.2, 1) both; }
@keyframes reveal { to { opacity: 1; transform: none; } }
@keyframes detail-in { from { opacity: 0; transform: translateY(0.5rem); } to { opacity: 1; transform: none; } }
@media (max-width: 900px) {
.layout { display: block; }
.rail { position: relative; height: auto; padding: 1rem; border-right: 0; border-bottom: 1px solid var(--line); }
.brand { margin: 0; }
.rail .nav-label,
.rail nav,
.rail-foot { display: none; }
.hero { min-height: 76vh; }
.section-head,
.ownership,
.split { grid-template-columns: 1fr; }
.owner-list { border-right: 0; border-bottom: 1px solid var(--line); }
.mapping-rule { grid-template-columns: 1fr; }
.rule-arrow { transform: rotate(90deg); }
.removed { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
.hero,
.section { padding-inline: 1.2rem; }
h1 { font-size: 3.3rem; }
.section-head { grid-template-columns: 1fr; }
.removed { grid-template-columns: 1fr; }
.check { grid-template-columns: 2.5rem 1fr; }
.copy { grid-column: 2; justify-self: start; }
footer { flex-direction: column; padding-inline: 1.2rem; }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
.reveal { opacity: 1; transform: none; }
}
</style>
</head>
<body>
<div class="reading-progress" aria-hidden="true"></div>
<div class="layout">
<aside class="rail">
<div class="brand">
<div class="brand-mark">N</div>
<div><strong>Field manual</strong><small>~/.config/nvim</small></div>
</div>
<div class="nav-label">Index</div>
<nav aria-label="Page sections">
<a href="#boot">01 / Boot sequence</a>
<a href="#ownership">02 / Tool ownership</a>
<a href="#keymaps">03 / Keymap model</a>
<a href="#plugins">04 / Plugin loading</a>
<a href="#cleanup">05 / What left</a>
<a href="#checks">06 / Maintenance loop</a>
</nav>
<div class="rail-foot">Macchiato palette<br>Lua modules + Lazy specs<br>Generated 2026.08</div>
</aside>
<main>
<header class="hero">
<div class="eyebrow">Neovim configuration / annotated system map</div>
<h1>Small core.<br><span class="outline">Deep tools.</span></h1>
<p class="lede">A practical map of how this Lua configuration boots, where behavior belongs, and which plugin owns each editing concern.</p>
<div class="hero-meta">
<span class="chip good">58 declared plugins</span>
<span class="chip">Lazy-loaded mappings</span>
<span class="chip">No LSP format fallback</span>
<span class="chip">0 Luacheck warnings</span>
</div>
</header>
<section class="section" id="boot">
<div class="section-head reveal">
<div><span class="section-number">01</span><h2>Boot sequence</h2></div>
<p><code>init.lua</code> is intentionally boring. It establishes Lazy first, then layers presentation, mappings, editor events, highlights, and one specialized LSP helper.</p>
</div>
<div class="boot-flow reveal" aria-label="Neovim startup sequence">
<article class="boot-step"><span class="boot-index">01</span><h3>core.lazy</h3><p>Bootstraps Lazy and loads core options before plugins.</p></article>
<article class="boot-step"><span class="boot-index">02</span><h3>plugins/*</h3><p>Lazy discovers specs and installs handlers for events, commands, and keys.</p></article>
<article class="boot-step"><span class="boot-index">03</span><h3>colorscheme</h3><p>Catppuccin Macchiato supplies the visual baseline.</p></article>
<article class="boot-step"><span class="boot-index">04</span><h3>core.keymaps</h3><p>Core editing, LSP, commands, and group labels register.</p></article>
<article class="boot-step"><span class="boot-index">05</span><h3>autocmds</h3><p>Editor-level reactions attach once through named augroups.</p></article>
<article class="boot-step"><span class="boot-index">06</span><h3>highlights</h3><p>Small local highlight overrides apply after the theme.</p></article>
<article class="boot-step"><span class="boot-index">07</span><h3>Hyprland LSP</h3><p>The file-pattern-specific helper starts only where relevant.</p></article>
</div>
</section>
<section class="section" id="ownership">
<div class="section-head reveal">
<div><span class="section-number">02</span><h2>One owner per concern</h2></div>
<p>Maintenance gets easier when two plugins do not compete for the same surface. Select a row to inspect the boundary.</p>
</div>
<div class="ownership reveal">
<div class="owner-list" role="tablist" aria-label="Tool ownership" aria-orientation="vertical">
<div class="owner active" id="owner-format" role="tab" tabindex="0" aria-selected="true" aria-controls="detail-format" data-owner="format"><span class="owner-key">F</span><div><strong>Conform</strong><span>formatting</span></div></div>
<div class="owner" id="owner-lint" role="tab" tabindex="-1" aria-selected="false" aria-controls="detail-lint" data-owner="lint"><span class="owner-key">L</span><div><strong>nvim-lint</strong><span>diagnostics on save</span></div></div>
<div class="owner" id="owner-complete" role="tab" tabindex="-1" aria-selected="false" aria-controls="detail-complete" data-owner="complete"><span class="owner-key">C</span><div><strong>nvim-cmp + LuaSnip</strong><span>completion</span></div></div>
<div class="owner" id="owner-notify" role="tab" tabindex="-1" aria-selected="false" aria-controls="detail-notify" data-owner="notify"><span class="owner-key">N</span><div><strong>Snacks + Fidget + Noice</strong><span>three distinct UI channels</span></div></div>
<div class="owner" id="owner-files" role="tab" tabindex="-1" aria-selected="false" aria-controls="detail-files" data-owner="files"><span class="owner-key">E</span><div><strong>Snacks Explorer</strong><span>file browsing</span></div></div>
<div class="owner" id="owner-terminal" role="tab" tabindex="-1" aria-selected="false" aria-controls="detail-terminal" data-owner="terminal"><span class="owner-key">T</span><div><strong>ToggleTerm</strong><span>terminal sessions</span></div></div>
</div>
<div class="owner-detail">
<div class="active" id="detail-format" role="tabpanel" aria-labelledby="owner-format" data-detail="format"><span class="kicker">Deterministic output</span><h3>Formatters only.</h3><p>Conform runs explicit tools per filetype. Python prefers Ruff, with isort and Black as the fallback toolchain. LSP formatting is deliberately set to <code>never</code>, so a newly attached server cannot silently change save behavior.</p></div>
<div id="detail-lint" role="tabpanel" aria-labelledby="owner-lint" data-detail="lint"><span class="kicker">Diagnostics after write</span><h3>Lint without mutation.</h3><p>nvim-lint runs once on <code>BufWritePost</code>. Ruff supplies Python diagnostics. Pydoclint and Codespell join only when their executables exist, avoiding noisy missing-tool warnings.</p></div>
<div id="detail-complete" role="tabpanel" aria-labelledby="owner-complete" data-detail="complete"><span class="kicker">Insert-mode intelligence</span><h3>Completion needs no shim.</h3><p>nvim-cmp gathers sources and LuaSnip expands snippets. None-ls was not part of this path, so removing it does not remove completion.</p></div>
<div id="detail-notify" role="tabpanel" aria-labelledby="owner-notify" data-detail="notify"><span class="kicker">Clear channel boundaries</span><h3>Three surfaces, zero contention.</h3><p>Snacks owns general notifications. Fidget owns LSP progress. Noice owns the command line and message history, with its notification forwarding and LSP progress disabled.</p></div>
<div id="detail-files" role="tabpanel" aria-labelledby="owner-files" data-detail="files"><span class="kicker">One tree</span><h3>Explorer replaces two browsers.</h3><p><code>&lt;leader&gt;fb</code> and <code>&lt;leader&gt;nt</code> both open Snacks Explorer, preserving muscle memory while removing nvim-tree and Telescope file-browser.</p></div>
<div id="detail-terminal" role="tabpanel" aria-labelledby="owner-terminal" data-detail="terminal"><span class="kicker">Session-aware shells</span><h3>Named tools stay alive.</h3><p>ToggleTerm owns splits, tabs, floats, and named sessions for btop, IPython, iotop, Lazydocker, nvtop, and rmpc. Snacks Terminal remains explicitly disabled.</p></div>
</div>
</div>
</section>
<section class="section" id="keymaps">
<div class="section-head reveal">
<div><span class="section-number">03</span><h2>Keymaps follow ownership</h2></div>
<p>The configuration uses two mapping paths. The deciding question is whether the mapping requires a plugin.</p>
</div>
<div class="mapping-rule reveal">
<div class="rule-box"><h3>Core behavior</h3><p>Editing motions, workspace operations, LSP calls, and local helper commands use <code>vim.keymap.set</code> inside <code>lua/core/keymaps/</code>.</p></div>
<div class="rule-arrow" aria-hidden="true"></div>
<div class="rule-box"><h3>Plugin behavior</h3><p>Telescope, Snacks, CodeCompanion, images, Diffview, and terminals declare mappings in their Lazy <code>keys</code> tables. The mapping becomes the load trigger.</p></div>
</div>
<div class="split reveal" style="margin-top: 1px">
<article class="panel"><span class="kicker">Core example</span><h3>Direct and unsurprising</h3><pre><span class="lua-key">map</span>(<span class="lua-str">"n"</span>, <span class="lua-str">"&lt;leader&gt;ca"</span>, vim.lsp.buf.code_action, {
desc = <span class="lua-str">"Code action"</span>,
})</pre></article>
<article class="panel"><span class="kicker">Plugin example</span><h3>Mapping as load boundary</h3><pre>keys = {
{ <span class="lua-str">"&lt;leader&gt;fb"</span>, <span class="lua-key">function</span>()
Snacks.explorer()
<span class="lua-key">end</span>, desc = <span class="lua-str">"File browser"</span> },
}</pre></article>
</div>
<div class="callout reveal"><strong>which-key has one job:</strong> it labels prefix groups. It no longer converts mapping tables or acts as a second mapping registry.</div>
</section>
<section class="section" id="plugins">
<div class="section-head reveal">
<div><span class="section-number">04</span><h2>Lazy is the seam</h2></div>
<p>Every file in <code>lua/plugins/</code> returns one plugin spec. The spec keeps the trigger, dependencies, mappings, options, and exceptional setup in one navigable place.</p>
</div>
<div class="split reveal">
<article class="panel"><span class="kicker">Prefer data</span><h3><code>opts</code> for ordinary setup</h3><p>When a plugin follows <code>require(module).setup(opts)</code>, its configuration stays declarative. Lazy can merge it and call setup at the correct time.</p><pre><span class="lua-key">return</span> {
<span class="lua-str">"stevearc/conform.nvim"</span>,
opts = { <span class="lua-note">-- formatter policy</span> },
}</pre></article>
<article class="panel"><span class="kicker">Use code selectively</span><h3><code>config</code> for real orchestration</h3><p>ToggleTerm needs named terminal objects and a terminal-buffer autocmd. That lifecycle is real behavior, so a focused <code>config</code> function is justified.</p><pre>config = <span class="lua-key">function</span>(_, opts)
require(<span class="lua-str">"toggleterm"</span>).setup(opts)
<span class="lua-note">-- create named sessions once</span>
<span class="lua-key">end</span></pre></article>
</div>
<div class="callout reveal"><strong>Avoid eager requires:</strong> do not call plugin modules while Lua is merely building a spec table. Wrap callback references in a function so Lazy can finish registering the plugin before it loads.</div>
</section>
<section class="section" id="cleanup">
<div class="section-head reveal">
<div><span class="section-number">05</span><h2>Complexity removed</h2></div>
<p>The cleanup favors replacement over compatibility layers. Old aliases remain where useful, but dead modules and duplicate owners do not.</p>
</div>
<div class="removed reveal">
<article><del>nvim-tree</del><strong>Snacks Explorer</strong><p>One browser, two preserved entry mappings.</p></article>
<article><del>Telescope file-browser</del><strong>Telescope search</strong><p>Search stays focused on pickers and discovery.</p></article>
<article><del>nvim-notify</del><strong>Snacks Notifier</strong><p>General messages move to the plugin already loaded at startup.</p></article>
<article><del>none-ls</del><strong>Conform + nvim-lint</strong><p>Formatting, linting, and completion now have separate owners.</p></article>
<article><del>Opencode</del><strong>CodeCompanion</strong><p>One in-editor AI workflow remains, alongside Copilot completion.</p></article>
<article><del>converter modules</del><strong>Native mapping APIs</strong><p>Less indirection between a key and the behavior it invokes.</p></article>
</div>
</section>
<section class="section" id="checks">
<div class="section-head reveal">
<div><span class="section-number">06</span><h2>Maintenance loop</h2></div>
<p>Run these from <code>~/.config/nvim</code>. Each check covers a different failure class: style drift, static mistakes, or startup integration.</p>
</div>
<div class="checks reveal">
<div class="check"><span class="check-state">01</span><code>stylua --check .</code><button class="copy" data-copy="stylua --check .">copy</button></div>
<div class="check"><span class="check-state">02</span><code>luacheck .</code><button class="copy" data-copy="luacheck .">copy</button></div>
<div class="check"><span class="check-state">03</span><code>jq empty lazy-lock.json</code><button class="copy" data-copy="jq empty lazy-lock.json">copy</button></div>
<div class="check"><span class="check-state">04</span><code>nvim --headless -i NONE -u ./init.lua '+qa'</code><button class="copy" data-copy="nvim --headless -i NONE -u ./init.lua '+qa'">copy</button></div>
</div>
<div class="callout reveal"><strong>Tool availability:</strong> optional linters should be conditional. The configuration registers Pydoclint and Codespell only when the corresponding executable is installed.</div>
</section>
<footer><span>Neovim Config Field Manual</span><span>Small core / explicit ownership / load on demand</span></footer>
</main>
</div>
<script>
const progress = document.querySelector(".reading-progress");
const sections = [...document.querySelectorAll("main section")];
const navLinks = [...document.querySelectorAll(".rail nav a")];
function updateScrollState() {
const max = document.documentElement.scrollHeight - innerHeight;
progress.style.width = `${max > 0 ? (scrollY / max) * 100 : 0}%`;
let current = sections[0]?.id;
for (const section of sections) {
if (section.getBoundingClientRect().top < innerHeight * 0.38) current = section.id;
}
for (const link of navLinks) link.classList.toggle("active", link.hash === `#${current}`);
}
addEventListener("scroll", updateScrollState, { passive: true });
updateScrollState();
const revealElements = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window) {
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
}
}, { threshold: 0.12 });
revealElements.forEach((element) => observer.observe(element));
} else {
revealElements.forEach((element) => element.classList.add("visible"));
}
function selectOwner(owner) {
const name = owner.dataset.owner;
document.querySelectorAll(".owner").forEach((item) => {
const selected = item === owner;
item.classList.toggle("active", selected);
item.setAttribute("aria-selected", String(selected));
item.tabIndex = selected ? 0 : -1;
});
document.querySelectorAll("[data-detail]").forEach((detail) => detail.classList.toggle("active", detail.dataset.detail === name));
}
const owners = [...document.querySelectorAll(".owner")];
owners.forEach((owner, index) => {
owner.addEventListener("click", () => selectOwner(owner));
owner.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectOwner(owner);
}
if (["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft", "Home", "End"].includes(event.key)) {
event.preventDefault();
const nextIndex = event.key === "Home" ? 0
: event.key === "End" ? owners.length - 1
: ["ArrowDown", "ArrowRight"].includes(event.key) ? (index + 1) % owners.length
: (index - 1 + owners.length) % owners.length;
selectOwner(owners[nextIndex]);
owners[nextIndex].focus();
}
});
});
async function copyText(text) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return;
} catch {}
}
const field = document.createElement("textarea");
field.value = text;
field.setAttribute("readonly", "");
field.style.position = "fixed";
field.style.opacity = "0";
document.body.append(field);
field.select();
const copied = document.execCommand("copy");
field.remove();
if (!copied) throw new Error("Copy is unavailable");
}
document.querySelectorAll(".copy").forEach((button) => {
button.setAttribute("aria-live", "polite");
button.addEventListener("click", async () => {
try {
await copyText(button.dataset.copy);
button.textContent = "copied";
} catch {
button.textContent = "copy failed";
}
setTimeout(() => { button.textContent = "copy"; }, 1200);
});
});
</script>
</body>
</html>