Compare commits

...
15 Commits
Author SHA1 Message Date
sudacode 5883796d80 Merge remote-tracking branch 'refs/remotes/origin/master' 2026-09-11 00:44:56 -07:00
sudacode 212fe286a7 update 2026-09-11 00:44:31 -07:00
kYasuda b0524979fe Merge branch 'master' of github.com:ksyasuda/dotfiles 2026-09-01 15:16:00 -07:00
kYasuda 447b90e3c2 update nvim 2026-09-01 15:15:53 -07:00
sudacode 385b881972 Merge remote-tracking branch 'refs/remotes/origin/master' 2026-08-31 23:39:13 -07:00
sudacode 031fd15dfd update 2026-08-31 23:38:52 -07:00
kYasuda 17086f43ec update nvim 2026-08-31 13:48:36 -07:00
sudacode 67afa82778 Merge branch 'master' of github.com:ksyasuda/dotfiles 2026-08-22 16:37:18 -07:00
sudacode 20b38a79d3 udpate 2026-08-22 16:37:11 -07:00
sudacode a9d3d00be9 Merge remote-tracking branch 'refs/remotes/origin/master' 2026-08-22 16:36:37 -07:00
sudacode 1a0f0cdabb update 2026-08-22 16:36:15 -07:00
sudacode c62d9e9d0e udpate 2026-08-22 16:34:08 -07:00
sudacode 5346c3fa5e update 2026-08-18 23:02:56 -07:00
sudacode ba1324d10a update 2026-08-18 22:31:44 -07:00
sudacode 7d560759ca update 2026-08-18 19:12:17 -07:00
95 changed files with 2770 additions and 3682 deletions
+7
View File
@@ -0,0 +1,7 @@
---
name: bro
description: Restate the last message in plain human language, with no jargon.
disable-model-invocation: true
---
Restate your last message. Stop using jargon and speak coherently. State it more simply and concisely, like one human talking to another.
@@ -1,93 +1,184 @@
--- ---
name: claude-code-computer-delegate name: claude-code-computer-delegate
description: "Route Claude Code computer-use, browser-use, GUI automation, and workstation interaction tasks to Codex subagents only. Use when Claude Code needs another agent to inspect or operate a desktop app, browser, local UI, terminal-driven workflow, or repo task involving computer control. Always use GPT-5.6 Codex models only: gpt-5.6-terra for narrow/simple work and gpt-5.6-sol for complex/high-risk work. Never use gpt-5.6-luna, Luna aliases, Claude models, or non-GPT-5.6 Codex models." description: "Delegate desktop GUI and workstation control to a Codex subagent that acts as remote hands while Claude Code reads the screenshots. Use when a task needs to see or operate the local desktop: take a screenshot, check what is currently on screen, inspect or drive a native or Electron app window, read window/workspace/monitor state, launch a desktop program, or run a live-session workflow that a sandboxed shell cannot reach. Not for web pages (use the Chrome DevTools or Playwright MCP tools) and not for ordinary repo edits (use the delegate skill). Codex only: gpt-5.6-terra for narrow work, gpt-5.6-sol for complex or risky work; never gpt-5.6-luna, Luna aliases, or Claude models."
--- ---
# Claude Code Computer Delegate # Claude Code Computer Delegate
## Overview ## The one thing to understand first
Delegate computer-use work from Claude Code to Codex subagents. Route every task to `gpt-5.6-terra` or `gpt-5.6-sol`; reject Luna and every other model. **Codex is the hands. Claude Code is the eyes.**
## Hard Rules Verified on this machine: a `codex exec` subagent *cannot view image files*. Asked to
screenshot the desktop and describe it, it replies `CANNOT VIEW IMAGES`. So never ask
Codex "what does the screen look like" or "check whether the dialog appeared". It is
blind.
- Use provider `codex` only. The working division of labor:
- Use only `gpt-5.6-terra` or `gpt-5.6-sol`.
- Never use `gpt-5.6-luna`, `luna`, aliases, "latest", fallback models, Claude models, or pre-5.6 Codex models.
- If a requested model violates these rules, stop and report the conflict. Do not silently substitute.
- Do not delegate recursively. The Codex subagent must complete the task itself and report back.
- Prefer read-only delegation for inspection, diagnosis, screenshots, or review. Use write mode only for explicit implementation or file edits.
## Route Selection | Step | Who | How |
|---|---|---|
| Capture screen, drive windows, launch apps, run live-session commands | Codex subagent | `grim`, `hyprctl`, `wtype` |
| Interpret pixels: layout, colors, error text, "did it work" | Claude Code (you) | `Read` the PNG path Codex reports |
| Decide the next action | Claude Code (you) | Send a follow-up brief |
Choose the smallest adequate route: Codex reasons only over **text**: `hyprctl -j clients` JSON, command output, exit codes,
log files. Have it report those. Have it report *screenshot paths*, never screenshot
*descriptions*.
| Complexity | Use | Model | Effort | ## Use this skill when
|---|---|---|---|
| Narrow | Single screen, one command, simple browser/GUI check, small file inspection, bounded terminal task | `gpt-5.6-terra` | medium |
| Narrow but context-heavy | Same scope, but with long logs, many screenshots, or nuanced UI state | `gpt-5.6-terra` | high |
| Standard | Multi-step computer workflow, multi-file repo task, ordinary debugging, meaningful trade-offs | `gpt-5.6-sol` | medium |
| Deep | Architecture, security, concurrency, risky edits, broad ambiguity, high-impact user/system state | `gpt-5.6-sol` | high |
Default to `gpt-5.6-sol` when failure could change user data, spend money, publish, delete, overwrite, or affect credentials. Default to `gpt-5.6-terra` when the task is reversible, local, and easy to verify. - "What's on my screen right now", "take a screenshot", "look at my desktop"
- Inspecting or operating a native/Electron/GTK/Qt app window
- Reading window, workspace, or monitor layout state
- Launching or focusing a desktop program
- A command that must touch the live graphical session
## Preferred Invocation ## Do NOT use this skill when
When the local `delegate` broker is available, use it because it enforces the approved Codex model set and safe execution modes: - **Web page or web app work.** You have `chrome-devtools` and `playwright` MCP tools
in-process. They give you the DOM, console, network, and snapshots you can actually
see. Delegating browser work to a blind subagent is strictly worse. Use the MCP tools.
- **Plain repo work** (reading code, edits, reviews) with no GUI involved. Use the
`delegate` skill instead.
- The task is a single command you can just run in Bash yourself. Do that.
## Hard model policy
- Provider `codex` only. Model must be `gpt-5.6-terra` or `gpt-5.6-sol`.
- Never `gpt-5.6-luna`, `luna`, aliases, `latest`, fallbacks, Claude models, or pre-5.6
Codex models.
- If the user names a model outside this set, stop and report the conflict. Do not
silently substitute.
- The subagent must not delegate further.
| Complexity | Model | Effort |
|---|---|---|
| Single screenshot, one command, bounded lookup | `gpt-5.6-terra` | medium |
| Same scope, long logs or fiddly state | `gpt-5.6-terra` | high |
| Multi-step GUI workflow, ordinary debugging | `gpt-5.6-sol` | medium |
| Risky, ambiguous, or touching credentials/money/user data | `gpt-5.6-sol` | high |
Default to `sol` when a mistake could change user data, spend money, publish, delete, or
overwrite. Default to `terra` when the action is reversible and easy to verify.
## Sandbox: the part that used to silently fail
GUI access requires `--sandbox danger-full-access`. This is not optional and there is no
narrower mode that works.
Measured on this box (Wayland, Hyprland 0.56.2):
| Sandbox | `grim` screenshot | `hyprctl` |
|---|---|---|
| `read-only` | fails, `failed to create display` | fails, `Couldn't set socket timeout` |
| `workspace-write` | fails, `failed to create display` | fails |
| `workspace-write --add-dir /run/user/1000` | still fails | still fails |
| `danger-full-access` | works | works |
The sandbox passes `WAYLAND_DISPLAY` and `XDG_RUNTIME_DIR` through as environment
variables but blocks the compositor sockets themselves, so the failure looks like a
missing display rather than a permission error. `--add-dir` does not fix it.
Because `danger-full-access` removes the sandbox entirely, keep the blast radius in the
brief instead: name the exact commands allowed, and forbid everything else.
## Run it
```bash ```bash
python3 /Users/sudacode/.agents/skills/delegate/scripts/delegate.py \ cd "$PWD" && timeout 900 codex exec \
--provider codex \
--tier quick \
--mode read \
--repo "$PWD" <<'TASK'
<self-contained computer-use task>
TASK
```
Map tiers as follows:
- `quick` -> `gpt-5.6-terra`, medium
- `quick-context` -> `gpt-5.6-terra`, high
- `standard` -> `gpt-5.6-sol`, medium
- `deep` -> `gpt-5.6-sol`, high
Use `--mode write` only when the user explicitly wants implementation or edits.
## Direct Codex Fallback
If the broker is unavailable, call Codex directly with an approved model:
```bash
codex exec \
--ignore-user-config \ --ignore-user-config \
--model gpt-5.6-terra \ --model gpt-5.6-terra \
--config 'model_reasoning_effort="medium"' \ --config model_reasoning_effort="medium" \
--config 'approval_policy="never"' \ --config approval_policy="never" \
--sandbox read-only \ --sandbox danger-full-access \
--ephemeral \ --ephemeral \
--skip-git-repo-check \ --skip-git-repo-check \
--cd "$PWD" \ --output-last-message /tmp/codex-gui-1.md \
- --cd "$PWD" - <<'TASK'
<brief from the template below>
TASK
cat /tmp/codex-gui-1.md
``` ```
For write tasks, use `--sandbox workspace-write`; never grant broader access unless the user explicitly approves that exact operation. Notes that matter:
## Task Brief - Always feed the brief on stdin via a **quoted** heredoc (`<<'TASK'`) so the shell does
not expand anything in it. The trailing `-` is what tells Codex to read stdin.
- `--output-last-message` gives you the clean report; stdout also carries the reasoning
stream, which is what you want when a run fails.
- Give screenshots a path you can reach afterward. Prefer your scratchpad directory.
- A nonzero exit or empty report file is a failure even if stdout printed something.
Check both.
Write a complete brief; the Codex subagent starts with no conversation context. Include: ## Brief template
- Goal and definition of done. Codex starts with zero context.
- Exact app, browser tab, local URL, file path, command, or screen state involved.
- Constraints: forbidden actions, allowed edits, credentials/payment/publishing restrictions.
- Verification expected: screenshot, command output, tests, or concise report.
- Reporting format: summary, actions taken, evidence, files changed, verification, open questions.
Pass raw evidence such as errors, logs, screenshots paths, or diffs. Do not tell Codex the answer to find. ```
You are a subagent with direct access to the live graphical session.
## Safety Checks Environment: Wayland + Hyprland. Available: grim (screenshot), slurp (region),
wtype (typing), hyprctl (window control and JSON introspection), xdotool
(XWayland windows only), playwright, google-chrome-stable, firefox.
- Before launching: state selected model, tier, mode, and why. You CANNOT view images. Never describe the contents of a screenshot. Capture it,
- After completion: inspect the report and verify load-bearing claims before acting on them. report the absolute path, and let the caller look at it.
- For write mode: review `git diff` and run targeted tests/checks before reporting success.
- On failure: report selected model, exact command path used, and the error. Retry once only for transient failures, with the same allowed model family. Hard constraints:
- Run only these commands: <explicit list>
- Do not close, move, or resize the user's existing windows unless told to.
- Do not type into or click on windows the task does not name.
- Do not delegate further or invoke Codex/Claude recursively.
Report, in these sections:
- Summary: what you did.
- Screenshots: absolute path of each, and what each was meant to capture.
- Structural state: relevant `hyprctl -j clients` output or command stdout, verbatim.
- Commands run: each with its exit code.
- Open questions: anything you could not determine without vision.
Task:
<self-contained task>
```
Pass raw evidence. Do not tell Codex the answer you expect it to find.
## Desktop cookbook (verified available here)
Prefer structured text over pixels wherever possible, since that is the part Codex can
reason about.
```bash
grim /path/shot.png # whole screen
grim -g "$(slurp)" /path/region.png # region (interactive, needs a human)
hyprctl -j clients # every window: class, title, at[x,y], size[w,h], workspace
hyprctl -j activewindow # focused window
hyprctl -j monitors # geometry and scale
hyprctl notify -1 3000 "rgb(44ccff)" "message"
wtype 'text to type' # types into the focused window
```
`hyprctl -j clients` is the highest-value call: it returns exact window rectangles, so
Codex can position and identify windows without seeing anything.
## Known limits, state honestly
- **No synthetic mouse clicks.** `ydotool` is not installed and the user is not in the
`input` group, so `/dev/uinput` is not writable. There is no working click injection.
If a task needs a click, say so and ask the user, rather than having Codex flail.
- **Hyprland 0.56 changed the dispatch API** to a Lua form (`hl.dsp.window.close()`).
Old `hyprctl dispatch <name>` strings error out. Verify a dispatcher before relying on
it.
- `xdotool` only reaches XWayland clients, not native Wayland ones.
- **Screenshots are downscaled when you Read them.** A 3440x1440 capture is shown to you
at 2000x837. If you derive coordinates from the image, multiply by the stated factor
before handing them to anything.
## After the run
- Read the screenshot yourself before believing any claim about UI state.
- Treat the report as a claim. Spot-check load-bearing parts against the raw output.
- On failure, report the model, the exact command, and the error. Retry once only for
transient faults, on the same model family. If the error is `failed to create display`,
the sandbox flag was wrong, not the task.
@@ -1,4 +1,4 @@
interface: interface:
display_name: "Claude Code Computer Delegate" display_name: "Claude Code Computer Delegate"
short_description: "Route Claude Code computer-use tasks to Codex" short_description: "Codex acts on the desktop; Claude Code reads the screenshots"
default_prompt: "Use $claude-code-computer-delegate to route computer-use work from Claude Code to the right Codex model." default_prompt: "Use $claude-code-computer-delegate to drive the local desktop through a Codex subagent (gpt-5.6-terra or gpt-5.6-sol) and interpret the captured screenshots here."
@@ -0,0 +1,37 @@
# Deepening
How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**.
## Dependency categories
When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam.
### 1. In-process
Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed.
### 2. Local-substitutable
Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface.
### 3. Remote but owned (Ports & Adapters)
Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter.
Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."*
### 4. True external (Mock)
Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter.
## Seam discipline
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection.
- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them.
## Testing strategy: replace, don't layer
- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them.
- Write new tests at the deepened module's interface. The **interface is the test surface**.
- Tests assert on observable outcomes through the interface, not internal state.
- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface.
@@ -0,0 +1,44 @@
# Design It Twice
When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best.
Uses the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**, **leverage**.
## Process
### 1. Frame the problem space
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
- The constraints any new interface would need to satisfy
- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md))
- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete
Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel.
### 2. Spawn sub-agents
Spawn 3+ sub-agents in parallel. Each must produce a **radically different** interface for the deepened module.
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint:
- Agent 1: "Minimize the interface — aim for 13 entry points max. Maximise leverage per entry point."
- Agent 2: "Maximise flexibility — support many use cases and extension."
- Agent 3: "Optimise for the most common caller — make the default case trivial."
- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies."
Include both [SKILL.md](SKILL.md) vocabulary and CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language.
Each sub-agent outputs:
1. Interface (types, methods, params — plus invariants, ordering, error modes)
2. Usage example showing how callers use it
3. What the implementation hides behind the seam
4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md))
5. Trade-offs — where leverage is high, where it's thin
### 3. Present and compare
Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**.
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu.
+114
View File
@@ -0,0 +1,114 @@
---
name: codebase-design
description: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
---
# Codebase Design
Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone.
## Glossary
Use these terms exactly — don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point.
**Module** — anything with an interface and an implementation. Deliberately scale-agnostic: a function, class, package, or tier-spanning slice. _Avoid_: unit, component, service.
**Interface** — everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow — they refer only to the type-level surface).
**Implementation** — what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repo) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise.
**Depth** — leverage at the interface: the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation.
**Seam** _(Michael Feathers)_ — a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context).
**Adapter** — a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside).
**Leverage** — what callers get from depth: more capability per unit of interface they learn. One implementation pays back across N call sites and M tests.
**Locality** — what maintainers get from depth: change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere.
## Deep vs shallow
**Deep module** = small interface + lots of implementation:
```
┌─────────────────────┐
│ Small Interface │ ← Few methods, simple params
├─────────────────────┤
│ │
│ Deep Implementation│ ← Complex logic hidden
│ │
└─────────────────────┘
```
**Shallow module** = large interface + little implementation (avoid):
```
┌─────────────────────────────────┐
│ Large Interface │ ← Many methods, complex params
├─────────────────────────────────┤
│ Thin Implementation │ ← Just passes through
└─────────────────────────────────┘
```
When designing an interface, ask:
- Can I reduce the number of methods?
- Can I simplify the parameters?
- Can I hide more complexity inside?
## Principles
- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface.
- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape.
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it.
## Designing for testability
Good interfaces make testing natural:
1. **Accept dependencies, don't create them.**
```typescript
// Testable
function processOrder(order, paymentGateway) {}
// Hard to test
function processOrder(order) {
const gateway = new StripeGateway();
}
```
2. **Return results, don't produce side effects.**
```typescript
// Testable
function calculateDiscount(cart): Discount {}
// Hard to test
function applyDiscount(cart): void {
cart.total -= discount;
}
```
3. **Small surface area.** Fewer methods = fewer tests needed. Fewer params = simpler test setup.
## Relationships
- A **Module** has exactly one **Interface** (the surface it presents to callers and tests).
- **Depth** is a property of a **Module**, measured against its **Interface**.
- A **Seam** is where a **Module**'s **Interface** lives.
- An **Adapter** sits at a **Seam** and satisfies the **Interface**.
- **Depth** produces **Leverage** for callers and **Locality** for maintainers.
## Rejected framings
- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead.
- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow — interface here includes every fact a caller must know.
- **"Boundary"**: overloaded with DDD's bounded context. Say **seam** or **interface**.
## Going deeper
- **Deepening a cluster given its dependencies** — see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing.
- **Exploring alternative interfaces** — see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): spin up parallel sub-agents to design the interface several radically different ways, then compare on depth, locality, and seam placement.
@@ -0,0 +1,3 @@
interface:
display_name: "Codebase Design"
short_description: "Vocabulary for deep-module design"
+112 -30
View File
@@ -5,9 +5,13 @@ description: Delegate self-contained tasks to Claude or Codex subagents run as f
# Delegate # Delegate
Hand a self-contained task to a fresh Claude or Codex subagent and get back a Hand a self-contained task to a fresh `claude` or `codex` CLI process and get
structured report. Each invocation is one subagent; run several in parallel for back a structured report. Each invocation is one subagent; run several in
independent tasks. parallel for independent tasks.
You call the CLIs directly. The command shapes below are the default recipe,
not a fixed harness — adjust flags when the task needs it (extra directories,
different tools, a longer timeout), and say what you changed in your report.
## When to delegate ## When to delegate
@@ -61,45 +65,123 @@ The subagent starts with zero context. Write a self-contained task containing:
Pass raw evidence; do not tell the subagent the answer you expect. Pass raw evidence; do not tell the subagent the answer you expect.
Wrap the brief in this scaffold. Keep the constraint block matching the mode
and keep the report sections verbatim — the result-handling steps below assume
them.
```
You are a subagent completing a delegated task inside the current repository.
Work independently; the caller cannot answer questions mid-task. If the task is
ambiguous, choose the safest reasonable interpretation and note the choice in
your report.
<constraints for the chosen mode — see below>
- Do not delegate further or invoke Claude or Codex recursively.
End your reply with a report containing these sections:
- Summary: what you did or found, in a few sentences.
- Details: key evidence, decisions, or findings, with file:line references.
- Files changed: list each changed file, or "none".
- Verification: what you ran and the outcome, or "not verified" and why.
- Open questions: anything unresolved the caller must decide, or "none".
Task:
<self-contained task brief>
```
`read` constraints:
```
Hard constraints:
- You are in read-only mode. Do not modify, create, rename, or delete files.
- Do not commit, branch, push, or contact external systems that change state.
- Base claims on repository evidence. Cite file paths and line numbers.
```
`write` constraints:
```
Hard constraints:
- Work only inside the given repository directory.
- Do not commit, branch, push, or open pull requests unless the task says to.
- Do not delete or rename files the task does not cover; if something looks
wrong or unexpected, stop and report instead of guessing.
- Verify your work (build, tests, or a targeted check) when feasible.
```
## Run it ## Run it
Resolve `<skill-dir>` to the directory containing this `SKILL.md`. Send the Always send the prompt on stdin via a quoted heredoc (`<<'TASK'`) so nothing in
task on stdin; do not interpolate it into the shell command. the brief is expanded or re-parsed by the shell. Run the CLI from the target
repo with `cd <repo> && ...`.
Codex, read-only:
```bash ```bash
python3 <skill-dir>/scripts/delegate.py \ cd /path/to/repo && codex exec \
--provider codex \ --ignore-user-config \
--tier standard \ --model gpt-5.6-sol \
--mode read \ --config model_reasoning_effort="medium" \
--repo "$PWD" <<'TASK' --config approval_policy="never" \
<self-contained task brief> --sandbox read-only \
--ephemeral \
--skip-git-repo-check \
--output-last-message /tmp/delegate-codex-1.md \
--cd /path/to/repo - <<'TASK'
<scaffold + brief>
TASK
cat /tmp/delegate-codex-1.md
```
Codex, write mode: swap `--sandbox read-only` for `--sandbox workspace-write`.
`--output-last-message` writes just the final report; Codex's stdout also
carries its reasoning stream, which is useful when a run fails or you want to
see what it actually did. Add `--add-dir <dir>` for extra writable roots.
Claude, read-only:
```bash
cd /path/to/repo && claude --print \
--model claude-opus-4-8 \
--effort medium \
--disable-slash-commands \
--no-session-persistence \
--permission-mode plan \
--tools Read,Glob,Grep <<'TASK'
<scaffold + brief>
TASK TASK
``` ```
Flags: Claude, write mode: replace the last two lines with
- `--provider claude|codex` (required) ```bash
- `--tier quick|quick-context|standard|deep` (default `standard`) --permission-mode acceptEdits \
- `--mode read|write` (default `read`) --tools Read,Glob,Grep,Edit,Write,Bash \
- `--model <approved-model>` for an explicit allowed override --allowedTools Edit,Write,Bash
- `--repo <dir>` (default cwd) ```
- `--timeout <seconds>` (default 1800)
- `--dry-run` to inspect routing and command construction without running Claude prints the final message on stdout. Widen `--tools` when a task
genuinely needs more (for example `WebSearch` for external research, or
`Bash` in read mode for a `git diff` the subagent must see) — that is a
deliberate choice, so note it when you report back.
Give long runs a timeout that fits the task (the Bash tool's `timeout` is in
milliseconds; 1800000 is a reasonable ceiling for a `deep` tier run).
Parallel delegation: launch each invocation as a separate background shell Parallel delegation: launch each invocation as a separate background shell
command, then collect the outputs. Never point two `write`-mode subagents at command, write each Codex report to its own `--output-last-message` path, then
overlapping files; split by file/directory or run them sequentially. collect the outputs. Never point two `write`-mode subagents at overlapping
files; split by file/directory or run them sequentially.
## Handle the result ## Handle the result
The report ends with Summary / Details / Files changed / Verification / Open - Treat the report as a subagent's claim, not ground truth: spot-check
questions sections. Then: load-bearing findings, and for `write` mode review the diff (`git diff`) and
re-run verification before building on it.
- Treat it as a subagent's claim, not ground truth: spot-check load-bearing
findings, and for `write` mode review the diff (`git diff`) and re-run
verification before building on it.
- Relay the outcome to the user in your own words; credit which provider/model - Relay the outcome to the user in your own words; credit which provider/model
produced it when it matters. produced it when it matters.
- On failure, report the provider, model, and exact error. Retry once with the - On failure, report the provider, model, and exact error. A nonzero exit or
same route if transient; escalate tier or switch provider only deliberately, empty output is a failure even if the CLI printed something — check both.
and say you did. Retry once with the same route if transient; escalate tier or switch provider
only deliberately, and say you did.
-270
View File
@@ -1,270 +0,0 @@
#!/usr/bin/env python3
"""Delegate a self-contained task to a Claude or Codex subagent."""
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
ALLOWED_MODELS = {
"codex": frozenset({"gpt-5.6-terra", "gpt-5.6-sol"}),
"claude": frozenset(
{"claude-sonnet-4-6", "claude-sonnet-5", "claude-opus-4-8"}
),
}
# tier -> provider -> (model, effort)
ROUTES = {
"quick": {
"codex": ("gpt-5.6-terra", "medium"),
"claude": ("claude-sonnet-4-6", "medium"),
},
"quick-context": {
"codex": ("gpt-5.6-terra", "high"),
"claude": ("claude-sonnet-5", "high"),
},
"standard": {
"codex": ("gpt-5.6-sol", "medium"),
"claude": ("claude-opus-4-8", "medium"),
},
"deep": {
"codex": ("gpt-5.6-sol", "high"),
"claude": ("claude-opus-4-8", "high"),
},
}
READ_ONLY_RULES = """\
Hard constraints:
- You are in read-only mode. Do not modify, create, rename, or delete files.
- Do not commit, branch, push, or contact external systems that change state.
- Base claims on repository evidence. Cite file paths and line numbers."""
WRITE_RULES = """\
Hard constraints:
- Work only inside the given repository directory.
- Do not commit, branch, push, or open pull requests unless the task says to.
- Do not delete or rename files the task does not cover; if something looks
wrong or unexpected, stop and report instead of guessing.
- Verify your work (build, tests, or a targeted check) when feasible."""
REPORT_FORMAT = """\
End your reply with a report containing these sections:
- Summary: what you did or found, in a few sentences.
- Details: key evidence, decisions, or findings, with file:line references.
- Files changed: list each changed file, or "none".
- Verification: what you ran and the outcome, or "not verified" and why.
- Open questions: anything unresolved the caller must decide, or "none"."""
@dataclass(frozen=True)
class Route:
provider: str
model: str
effort: str
def select_route(provider: str, tier: str) -> Route:
if tier not in ROUTES:
raise ValueError(f"Unknown tier: {tier}")
if provider not in ("claude", "codex"):
raise ValueError(f"Unknown provider: {provider}")
model, effort = ROUTES[tier][provider]
return Route(provider, model, effort)
def apply_model_override(route: Route, model: str) -> Route:
if model not in ALLOWED_MODELS[route.provider]:
raise ValueError(f"Model {model!r} is not allowed for {route.provider}")
return Route(route.provider, model, route.effort)
def build_command(
route: Route,
repo: Path,
mode: str,
output_path: Path | None = None,
) -> list[str]:
if route.provider == "codex":
if output_path is None:
raise ValueError("Codex requires an output path")
sandbox = "read-only" if mode == "read" else "workspace-write"
return [
"codex",
"exec",
"--ignore-user-config",
"--model",
route.model,
"--config",
f'model_reasoning_effort="{route.effort}"',
"--config",
'approval_policy="never"',
"--sandbox",
sandbox,
"--ephemeral",
"--skip-git-repo-check",
"--output-last-message",
str(output_path),
"--cd",
str(repo),
"-",
]
command = [
"claude",
"--print",
"--model",
route.model,
"--effort",
route.effort,
"--disable-slash-commands",
"--no-session-persistence",
]
if mode == "read":
command += [
"--permission-mode",
"plan",
"--tools",
"Read,Glob,Grep",
]
else:
command += [
"--permission-mode",
"acceptEdits",
"--tools",
"Read,Glob,Grep,Edit,Write,Bash",
"--allowedTools",
"Edit,Write,Bash",
]
return command
def build_prompt(task: str, route: Route, mode: str) -> str:
rules = READ_ONLY_RULES if mode == "read" else WRITE_RULES
return f"""You are a {route.provider} subagent completing a delegated task \
inside the current repository. Work independently; the caller cannot answer \
questions mid-task. If the task is ambiguous, choose the safest reasonable \
interpretation and note the choice in your report.
{rules}
- Do not delegate further or invoke Claude or Codex recursively.
{REPORT_FORMAT}
Task:
{task.strip()}
"""
def run_delegation(
route: Route,
repo: Path,
prompt: str,
mode: str,
*,
timeout: int = 1800,
) -> str:
if shutil.which(route.provider) is None:
raise RuntimeError(f"Required CLI is not installed: {route.provider}")
with tempfile.TemporaryDirectory(prefix="delegate-") as temp_dir:
output_path = (
Path(temp_dir) / "report.md" if route.provider == "codex" else None
)
command = build_command(route, repo, mode, output_path)
try:
result = subprocess.run(
command,
cwd=repo,
input=prompt,
text=True,
capture_output=True,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired as error:
raise RuntimeError(
f"{route.provider} delegation timed out after {timeout}s"
) from error
if result.returncode != 0:
detail = (result.stderr or result.stdout).strip() or "no error output"
raise RuntimeError(
f"{route.provider} delegation failed ({result.returncode}): {detail}"
)
if route.provider == "codex":
if output_path is None or not output_path.is_file():
raise RuntimeError("Codex did not produce a report")
report = output_path.read_text().strip()
else:
report = result.stdout.strip()
if not report:
raise RuntimeError(f"{route.provider} returned an empty report")
return report
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Delegate a self-contained task to a Claude or Codex subagent."
)
parser.add_argument("--provider", choices=("claude", "codex"), required=True)
parser.add_argument(
"--tier",
choices=("quick", "quick-context", "standard", "deep"),
default="standard",
)
parser.add_argument("--mode", choices=("read", "write"), default="read")
parser.add_argument("--model", help="Approved model override")
parser.add_argument("--repo", type=Path, default=Path.cwd())
parser.add_argument("--timeout", type=int, default=1800)
parser.add_argument("--dry-run", action="store_true")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
try:
task = sys.stdin.read().strip()
if not task:
raise ValueError("Task must be provided on stdin")
repo = args.repo.expanduser().resolve()
if not repo.is_dir():
raise ValueError(f"Repository directory does not exist: {repo}")
route = select_route(args.provider, args.tier)
if args.model:
route = apply_model_override(route, args.model)
prompt = build_prompt(task, route, args.mode)
if args.dry_run:
output_path = (
Path(tempfile.gettempdir()) / "delegate-dry-run-report.md"
if route.provider == "codex"
else None
)
command = build_command(route, repo, args.mode, output_path)
print(f"provider: {route.provider}")
print(f"model: {route.model}")
print(f"effort: {route.effort}")
print(f"mode: {args.mode}")
print(f"command: {' '.join(command)}")
print(f"prompt:\n{prompt}")
return 0
report = run_delegation(
route,
repo,
prompt,
args.mode,
timeout=args.timeout,
)
print(f"[{route.provider} · {route.model} · {route.effort} · {args.mode}]")
print(report)
return 0
except (OSError, RuntimeError, ValueError) as error:
print(f"delegate: error: {error}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
+138
View File
@@ -0,0 +1,138 @@
---
name: diagnosing-bugs
description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.
---
# Diagnosing Bugs
A discipline for hard bugs. Skip phases only when explicitly justified.
When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
## Redact
This skill has you show commands, outputs and captured artifacts. **Redact every secret first** — write `<REDACTED>` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: quote only the lines that carry the signal.
If the redacted output is not enough to diagnose the bug, say so and ask the user.
## Phase 1 — Build a feedback loop
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug — one that goes red on _this_ bug — you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**
### Ways to construct one — try them in roughly this order
1. **Failing test** at whatever seam reaches the bug — unit, integration, e2e.
2. **Curl / HTTP script** against a running dev server.
3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.
4. **Headless browser script** (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network.
5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.
6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
7. **Property / fuzz loop.** If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode.
8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can `git bisect run` it.
9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.
10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.
Build the right feedback loop, and the bug is 90% fixed.
### Tighten the loop
Treat the loop as a product. Once you have _a_ loop, **tighten** it:
- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
- Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)
A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight — a debugging superpower.
### Non-deterministic bugs
The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable.
### When you genuinely cannot build a loop
Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
### Completion criterion — a tight loop that goes red
Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** — a script path, a test invocation, a curl — that you have **already run at least once** (show the invocation and its output, redacted), and that is:
- [ ] **Red-capable** — it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring" — it must be able to _catch this specific bug_.
- [ ] **Deterministic** — same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
- [ ] **Fast** — seconds, not minutes.
- [ ] **Agent-runnable** — you can run it unattended; a human in the loop only via `scripts/hitl-loop.template.sh`.
If you catch yourself reading code to build a theory before this command exists, **stop — jumping straight to a hypothesis is the exact failure this skill prevents.** No red-capable command, no Phase 2.
## Phase 2 — Reproduce + minimise
Run the loop. Watch it go red — the bug appears.
Confirm:
- [ ] The loop produces the failure mode the **user** described — not a different failure that happens to be nearby. Wrong bug = wrong fix.
- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.
### Minimise
Once it's red, shrink the repro to the **smallest scenario that still goes red**. Cut inputs, callers, config, data, and steps **one at a time**, re-running the loop after each cut — keep only what's load-bearing for the failure.
Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5.
Done when **every remaining element is load-bearing** — removing any one of them makes the loop go green.
Do not proceed until you have reproduced **and** minimised.
## Phase 3 — Hypothesise
Generate **35 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.
Each hypothesis must be **falsifiable**: state the prediction it makes.
> Format: "If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse."
If you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it.
**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK.
## Phase 4 — Instrument
Each probe must map to a specific prediction from Phase 3. **Change one variable at a time.**
Tool preference:
1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.
2. **Targeted logs** at the boundaries that distinguish hypotheses.
3. Never "log everything and grep".
**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.
**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.
## Phase 5 — Fix + regression test
Write the regression test **before the fix** — but only if there is a **correct seam** for it.
A correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.
**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.
If a correct seam exists:
1. Turn the minimised repro into a failing test at that seam.
2. Watch it fail.
3. Apply the fix.
4. Watch it pass.
5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
## Phase 6 — Cleanup
Required before declaring done:
- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
- [ ] Regression test passes (or absence of seam is documented)
- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)
- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
- [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns
@@ -0,0 +1,3 @@
interface:
display_name: "Diagnosing Bugs"
short_description: "Diagnose hard bugs and regressions"
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Human-in-the-loop reproduction loop.
# Copy this file, edit the steps below, and run it.
# The agent runs the script; the user follows prompts in their terminal.
#
# Usage:
# bash hitl-loop.template.sh
#
# Two helpers:
# step "<instruction>" → show instruction, wait for Enter
# capture VAR "<question>" → show question, read response into VAR
#
# At the end, captured values are printed as KEY=VALUE for the agent to parse.
#
# `capture` prints its value back to the terminal, where the agent reads it — so
# capture observations, and leave signing in to the user as a `step`.
set -euo pipefail
step() {
printf '\n>>> %s\n' "$1"
read -r -p " [Enter when done] " _
}
capture() {
local var="$1" question="$2" answer
printf '\n>>> %s\n' "$question"
read -r -p " > " answer
printf -v "$var" '%s' "$answer"
}
# --- edit below ---------------------------------------------------------
step "Open the app at http://localhost:3000 and sign in."
capture ERRORED "Click the 'Export' button. Did it throw an error? (y/n)"
capture ERROR_MSG "Paste the error message (or 'none'):"
# --- edit above ---------------------------------------------------------
printf '\n--- Captured ---\n'
printf 'ERRORED=%s\n' "$ERRORED"
printf 'ERROR_MSG=%s\n' "$ERROR_MSG"
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 Hardik Pandya
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-62
View File
@@ -1,62 +0,0 @@
# Stop Slop
A skill for removing AI tells from prose.
<img width="3840" height="2160" alt="G-Yg4RVbIAAhVxW" src="https://github.com/user-attachments/assets/902afc15-1f40-4a9d-af24-8cd67afb8ebf" />
## What this is
AI writing has patterns. Predictable phrases, structures, rhythms. This skill teaches Claude (or any LLM) to catch and remove them.
## Skill Structure
```
stop-slop/
├── SKILL.md # Core instructions
├── references/
│ ├── phrases.md # Phrases to remove
│ ├── structures.md # Structural patterns to avoid
│ └── examples.md # Before/after transformations
├── README.md
└── LICENSE
```
## Quick start
**Claude Code:** Add this folder as a skill.
**Claude Projects:** Upload `SKILL.md` and reference files to project knowledge.
**Custom instructions:** Copy core rules from `SKILL.md`.
**API calls:** Include `SKILL.md` in your system prompt. Reference files load on demand.
## What it catches
**Banned phrases** - Throat-clearing openers, emphasis crutches, business jargon, all adverbs, vague declaratives, meta-commentary. See `references/phrases.md`.
**Structural clichés** - Binary contrasts, negative listings, dramatic fragmentation, rhetorical setups, false agency, narrator-from-a-distance voice, passive voice. See `references/structures.md`.
**Sentence-level rules** - No Wh- sentence starters, no em dashes, no staccato fragmentation, no lazy extremes, active voice required.
## Scoring
Rate 1-10 on each dimension:
| Dimension | Question |
|-----------|----------|
| Directness | Statements or announcements? |
| Rhythm | Varied or metronomic? |
| Trust | Respects reader intelligence? |
| Authenticity | Sounds human? |
| Density | Anything cuttable? |
Below 35/50: revise.
## Author
[Hardik Pandya](https://hvpandya.com)
## License
MIT. Use freely, share widely.
-68
View File
@@ -1,68 +0,0 @@
---
name: stop-slop
description: Remove AI writing patterns from prose. Use when drafting, editing, or reviewing text to eliminate predictable AI tells.
metadata:
trigger: Writing prose, editing drafts, reviewing content for AI patterns
author: Hardik Pandya (https://hvpandya.com)
---
# Stop Slop
Eliminate predictable AI writing patterns from prose.
## Core Rules
1. **Cut filler phrases.** Remove throat-clearing openers, emphasis crutches, and all adverbs. See [references/phrases.md](references/phrases.md).
2. **Break formulaic structures.** Avoid binary contrasts, negative listings, dramatic fragmentation, rhetorical setups, false agency. See [references/structures.md](references/structures.md).
3. **Use active voice.** Every sentence needs a human subject doing something. No passive constructions. No inanimate objects performing human actions ("the complaint becomes a fix").
4. **Be specific.** No vague declaratives ("The reasons are structural"). Name the specific thing. No lazy extremes ("every," "always," "never") doing vague work.
5. **Put the reader in the room.** No narrator-from-a-distance voice. "You" beats "People." Specifics beat abstractions.
6. **Vary rhythm.** Mix sentence lengths. Two items beat three. End paragraphs differently. No em dashes.
7. **Trust readers.** State facts directly. Skip softening, justification, hand-holding.
8. **Cut quotables.** If it sounds like a pull-quote, rewrite it.
## Quick Checks
Before delivering prose:
- Any adverbs? Kill them.
- Any passive voice? Find the actor, make them the subject.
- Inanimate thing doing a human verb ("the decision emerges")? Name the person.
- Sentence starts with a Wh- word? Restructure it.
- Any "here's what/this/that" throat-clearing? Cut to the point.
- Any "not X, it's Y" contrasts? State Y directly.
- Three consecutive sentences match length? Break one.
- Paragraph ends with punchy one-liner? Vary it.
- Em-dash anywhere? Remove it.
- Vague declarative ("The implications are significant")? Name the specific implication.
- Narrator-from-a-distance ("Nobody designed this")? Put the reader in the scene.
- Meta-joiners ("The rest of this essay...")? Delete. Let the essay move.
## Scoring
Rate 1-10 on each dimension:
| Dimension | Question |
|-----------|----------|
| Directness | Statements or announcements? |
| Rhythm | Varied or metronomic? |
| Trust | Respects reader intelligence? |
| Authenticity | Sounds human? |
| Density | Anything cuttable? |
Below 35/50: revise.
## Examples
See [references/examples.md](references/examples.md) for before/after transformations.
## License
MIT
@@ -1,59 +0,0 @@
# Before/After Examples
## Example 1: Throat-Clearing + Binary Contrast
**Before:**
> "Here's the thing: building products is hard. Not because the technology is complex. Because people are complex. Let that sink in."
**After:**
> "Building products is hard. Technology is manageable. People aren't."
**Changes:** Removed opener, binary contrast structure, and emphasis crutch. Direct statements.
---
## Example 2: Filler + Unnecessary Reassurance
**Before:**
> "It turns out that most teams struggle with alignment. The uncomfortable truth is that nobody wants to admit they're confused. And that's okay."
**After:**
> "Teams struggle with alignment. Nobody admits confusion."
**Changes:** Cut hedging ("most"), removed throat-clearing phrases, deleted permission-granting ending.
---
## Example 3: Business Jargon Stack
**Before:**
> "In today's fast-paced landscape, we need to lean into discomfort and navigate uncertainty with clarity. This matters because your competition isn't waiting."
**After:**
> "Move faster. Your competition is."
**Changes:** Eliminated jargon entirely. Core message in six words.
---
## Example 4: Dramatic Fragmentation
**Before:**
> "Speed. Quality. Cost. You can only pick two. That's it. That's the tradeoff."
**After:**
> "Speed, quality, cost—pick two."
**Changes:** Single sentence. No performative emphasis.
---
## Example 5: Rhetorical Setup
**Before:**
> "What if I told you that the best teams don't optimize for productivity? Here's what I mean: they optimize for learning. Think about it."
**After:**
> "The best teams optimize for learning, not productivity."
**Changes:** Direct claim. No rhetorical scaffolding.
@@ -1,128 +0,0 @@
# Phrases to Remove
## Throat-Clearing Openers
Remove these announcement phrases. State the content directly.
- "Here's the thing:"
- "Here's what [X]"
- "Here's this [X]"
- "Here's that [X]"
- "Here's why [X]"
- "The uncomfortable truth is"
- "It turns out"
- "The real [X] is"
- "Let me be clear"
- "The truth is,"
- "I'll say it again:"
- "I'm going to be honest"
- "Can we talk about"
- "Here's what I find interesting"
- "Here's the problem though"
Any "here's what/this/that" construction is throat-clearing before the point. Cut it and state the point.
## Emphasis Crutches
These add no meaning. Delete them.
- "Full stop." / "Period."
- "Let that sink in."
- "This matters because"
- "Make no mistake"
- "Here's why that matters"
## Business Jargon
Replace with plain language.
| Avoid | Use instead |
|-------|-------------|
| Navigate (challenges) | Handle, address |
| Unpack (analysis) | Explain, examine |
| Lean into | Accept, embrace |
| Landscape (context) | Situation, field |
| Game-changer | Significant, important |
| Double down | Commit, increase |
| Deep dive | Analysis, examination |
| Take a step back | Reconsider |
| Moving forward | Next, from now |
| Circle back | Return to, revisit |
| On the same page | Aligned, agreed |
## Adverbs
Kill all adverbs. No -ly words. No softeners, no intensifiers, no hedges.
Specific offenders:
- "really"
- "just"
- "literally"
- "genuinely"
- "honestly"
- "simply"
- "actually"
- "deeply"
- "truly"
- "fundamentally"
- "inherently"
- "inevitably"
- "interestingly"
- "importantly"
- "crucially"
Also cut these filler phrases:
- "At its core"
- "In today's [X]"
- "It's worth noting"
- "At the end of the day"
- "When it comes to"
- "In a world where"
- "The reality is"
## Meta-Commentary
Remove self-referential asides. The essay should move, not announce its own structure.
- "Hint:"
- "Plot twist:" / "Spoiler:"
- "You already know this, but"
- "But that's another post"
- "X is a feature, not a bug"
- "Dressed up as"
- "The rest of this essay explains..."
- "Let me walk you through..."
- "In this section, we'll..."
- "As we'll see..."
- "I want to explore..."
## Performative Emphasis
False intimacy or manufactured sincerity:
- "creeps in"
- "I promise"
- "They exist, I promise"
## Telling Instead of Showing
Announcing difficulty or significance rather than demonstrating it:
- "This is genuinely hard"
- "This is what leadership actually looks like"
- "This is what X actually looks like"
- "actually matters"
## Vague Declaratives
Sentences that announce importance without naming the specific thing. Kill these.
- "The reasons are structural"
- "The implications are significant"
- "This is the deepest problem"
- "The stakes are high"
- "The consequences are real"
If a sentence says something is important/deep/structural without showing the specific thing, cut it or replace it with the specific thing.
@@ -1,134 +0,0 @@
# Structures to Avoid
## Binary Contrasts
These create false drama. State the point directly.
| Pattern | Problem |
|---------|---------|
| "Not because X. Because Y." / "Not because X, but because Y." | Telegraphed reversal |
| "[X] isn't the problem. [Y] is." | Formulaic reframe |
| "The answer isn't X. It's Y." | Predictable pivot |
| "It feels like X. It's actually Y." | Setup/reveal cliche |
| "The question isn't X. It's Y." | Rhetorical misdirection |
| "Not X. But Y." / "not X, it's Y" / "isn't X, it's Y" | Mechanical contrast |
| "It's not this. It's that." | Same formula, different words |
| "stops being X and starts being Y" | False transformation arc |
| "doesn't mean X, but actually Y" | Negation-then-assertion crutch |
| "is about X but not Y" | False distinction |
| "not just X but also Y" | Additive hedge |
**Instead:** State Y directly. "The problem is Y." "Y matters here." Drop the negation entirely.
## Negative Listing
Listing what something is *not* before revealing what it *is*. A rhetorical striptease.
| Pattern | Problem |
|---------|---------|
| "Not a X... Not a Y... A Z." | Dramatic buildup through negation |
| "It wasn't X. It wasn't Y. It was Z." | Same structure, past tense |
**Instead:** State Z. The reader doesn't need the runway.
## Dramatic Fragmentation
Sentence fragments for emphasis read as manufactured profundity.
| Pattern | Problem |
|---------|---------|
| "[Noun]. That's it. That's the [thing]." | Performative simplicity |
| "X. And Y. And Z." | Staccato drama |
| "This unlocks something. [Word]." | Artificial revelation |
**Instead:** Complete sentences. Trust content over presentation.
## Rhetorical Setups
These announce insight rather than deliver it.
| Pattern | Problem |
|---------|---------|
| "What if [reframe]?" | Socratic posturing |
| "Here's what I mean:" | Redundant preview |
| "Think about it:" | Condescending prompt |
| "And that's okay." | Unnecessary permission |
**Instead:** Make the point. Let readers draw conclusions.
## Formulaic Constructions
| Pattern | Problem |
|---------|---------|
| "By the time X, I was Y." | Narrative template |
| "X that isn't Y" | Indirect. Say "X is broken" |
## False Agency
Giving inanimate things human verbs. Complaints don't "become" fixes. Bets don't "live or die." Decisions don't "emerge." A person does something to make those things happen. AI loves this because it avoids naming the actor.
| Pattern | Problem |
|---------|---------|
| "a complaint becomes a fix" | The complaint did nothing. Someone fixed it. |
| "a bet lives or dies in days" | Bets don't have lifespans. Someone kills the project or ships it. |
| "the decision emerges" | Decisions don't emerge. Someone decides. |
| "the culture shifts" | Cultures don't shift on their own. People change behavior. |
| "the conversation moves toward" | Conversations don't move. Someone steers. |
| "the data tells us" | Data sits there. Someone reads it and draws a conclusion. |
| "the market rewards" | Markets don't reward. Buyers pay for things. |
**Instead:** Name the human. "The team fixed it that week" beats "the complaint becomes a fix." If no specific person fits, use "you" to put the reader in the seat.
## Narrator-from-a-Distance
Floating above the scene instead of putting the reader in it.
| Pattern | Problem |
|---------|---------|
| "Nobody designed this." | Disembodied observation |
| "This happens because..." | Lecturer voice |
| "This is why..." | Same |
| "People tend to..." | Armchair sociologist |
**Instead:** Put the reader in the room. "You don't sit down one day and decide to..." beats "Nobody designed this."
## Passive Voice
Every sentence needs a subject doing something. Passive voice hides the actor and drains energy.
| Pattern | Fix |
|---------|-----|
| "X was created" | Name who created it |
| "It is believed that" | Name who believes it |
| "Mistakes were made" | Name who made them |
| "The decision was reached" | Name who decided |
**Instead:** Find the actor. Put them at the front of the sentence.
## Sentence Starters to Avoid
| Pattern | Fix |
|---------|-----|
| Sentences starting with What, When, Where, Which, Who, Why, How | Restructure. Lead with the subject or the verb. |
| Paragraphs starting with "So" | Start with content |
| Sentences starting with "Look," | Remove |
Wh- openers become a crutch. "What makes this hard is..." becomes "The constraint is..." or better, name the specific constraint.
## Rhythm Patterns
| Pattern | Fix |
|---------|-----|
| Three-item lists | Use two items or one |
| Questions answered immediately | Let questions breathe or cut them |
| Every paragraph ends punchily | Vary endings |
| Em-dashes | Remove. Use commas or periods. No em dashes at all. |
| Staccato fragmentation | Don't stack short punchy sentences |
| "Not always. Not perfectly." | Hedging disguised as reassurance |
## Word Patterns
| Pattern | Problem |
|---------|---------|
| Lazy extremes (every, always, never, everyone, everybody, nobody) | False authority. Use specifics instead of sweeping claims. |
| All adverbs (-ly words, "really," "just," "literally," "genuinely," "honestly," "simply," "actually") | Empty emphasis. See phrases.md for full list. |
@@ -0,0 +1,31 @@
---
name: type-system-discipline
description: "Apply when designing types, reviewing a function signature, or writing code in any statically-typed language. Make illegal states unrepresentable, brand semantic primitives, parse external data at boundaries, refuse to lie to the compiler, exhaust variants, derive from authoritative schemas."
disable-model-invocation: true
---
# Type System Discipline
The type checker is a proof assistant. Use it to eliminate impossible states, mismatched primitives, and unhandled variants at compile time. A case the types let you ignore becomes a runtime failure the compiler could have stopped. Prefer defining errors and special cases out of existence over proliferating handlers; unrepresentable states, total functions, and interface redesign (the patterns below) are the tools.
Applies to any typed language. Skills like `typescript-best-practices` ground it in specific syntax.
**The patterns:**
- **Make illegal states unrepresentable.** Model variants as sum types: discriminated unions in TypeScript, enums with payloads in Rust/Swift/Kotlin, sealed classes in Scala, ADTs in Haskell/OCaml. Don't model state as a bag of optional fields where contradictory combinations compile. A subtle anti-pattern worth naming: `{ completed: boolean; completedAt?: Date }` admits `completed: true; completedAt: undefined`, which is meaningless. Derive the boolean from a single source like `completedAt !== null`, or model the variants explicitly as `{ kind: 'open' } | { kind: 'done'; at: Date }`. If a bug forces the question "wait, can this combination actually happen?", the type is too loose.
- **Types are constructions, not restrictions.** Build the type up from the values you want instead of carving them out of a looser type with checks. The invariant that seems to need a refinement type is usually a construction away. A non-empty list is a head plus a rest, not a list with a length check. A valid time range is a start plus a duration, not two timestamps you must keep ordered. No representation is privileged. A list of pairs is an even-length list if you interpret it that way, so choose the shape that cannot build the illegal value and expose the interface callers need on top.
- **Brand semantic primitives.** `UserId` and `OrderId` are strings underneath but should not be interchangeable. Newtypes in Rust, opaque types in Swift, value classes in Kotlin, phantom types in Haskell, branded intersections in TypeScript. Validate once at creation, trust the type downstream.
- **External data is untyped until parsed.** RPC payloads, JSON, IPC messages, CLI args, config files, environment variables, database rows. Have a parse function at every boundary that turns unstructured input into the typed model. See the **boundary-discipline** principle skill for where to put validation.
- **Don't lie to the type system.** Casts, unsafe coercions, and assertion functions that bypass the compiler are runtime crashes waiting to happen. If the compiler can't prove a fact, prove it (validate, narrow, refine the model) or accept that the cast is a hazard. The cast you bury today is the postmortem you write next week.
- **Exhaustive matching is the compiler's job.** When you match on a sum type, the compiler must fail compilation if a new variant is added without handling. Use the idiom your language provides: `never`-typed binding in TypeScript, unannotated `match` in Rust, `-Wincomplete-patterns` in Haskell, sealed-class match exhaustiveness in Kotlin.
- **Derive types from authoritative schemas.** When a protocol buffer, OpenAPI spec, GraphQL schema, database migration, or design-system token file defines a shape, derive from it instead of hand-rolling a parallel type. Manual duplication drifts. See the **encode-lessons-in-structure** principle skill.
- **Strengthen a type only where partiality appears.** A runtime assertion, null check, or "this should never happen" throw marks the place a type is too weak. Push that check up into the type. Then stop. The type system's job is to track the cases each use site must handle, not to describe the data as precisely as possible. Prefer total functions. `sum` of an empty list is 0, so it takes the plain list. `head` of an empty list has no answer, so it demands the non-empty one. Extra precision costs reuse and ceremony and buys no safety.
**The tests:**
- "Can I write a comment explaining when this combination of fields is valid?" If yes, the type is too loose. Split it into a sum type.
- "Do two of my function arguments share a primitive type but mean different things?" Brand them.
- "Where did this `any`, this `as`, this `assertNotNull` come from?" Trace it to the boundary and validate there instead.
- "If a new variant is added next month, will the compiler tell the next agent where to add a case?" If no, the match isn't exhaustive.
- "Is this type duplicating a shape another file owns?" Derive instead.
- "Am I strengthening this type to keep an operation total, or just to be more precise?" If nothing would otherwise panic, keep the plain type.
@@ -0,0 +1,28 @@
---
name: typescript-best-practices
description: TypeScript best practices. Use when reading or editing any .ts or .tsx file.
---
# TypeScript best practices
Apply the **type-system-discipline** principle skill first; this skill grounds it in TypeScript syntax.
| Rule | Summary |
|------|---------|
| Discriminated unions | Model variants with a `kind` literal discriminant so impossible states can't be represented. No optional-field bags. |
| Branded types | Brand primitives with `& { readonly __brand: "X" }` so they can't be mixed up. Validate once at creation. |
| Constructive modeling | Build the shape so the illegal value can't be constructed. `[T, ...T[]]` for non-empty, `[T, T][]` for even length, `start` plus `duration` for a range. Not a runtime guard, not a wish for refinement types. |
| Simplest total type | Keep `T[]` while every operation on it stays total. Strengthen to `NonEmpty<T>` only where the loose type forces `!`, a cast, or a "should never happen" throw. |
| `unknown` over `any` | External data is `unknown`. `any` disables type checking everywhere it touches. |
| No `as` casts | Every `as` is a runtime crash waiting. Cast only after validation. |
| Narrowing hierarchy | Discriminant switch > `in` operator > `typeof`/`instanceof` > user-defined type guard > `as`. |
| Type guards | Must verify the claim. A lying guard is worse than `as` because the bug hides behind a name that says it's safe. Name them `isX` or `hasX`. |
| Exhaustiveness | Inline `const _exhaustive: never = x;` in default arms so the compiler errors when a new variant is added. |
| `satisfies` over `as` | Validates the value without widening literal types. |
| Boundary validation | Validate where data crosses in; trust types inside. See the **boundary-discipline** principle skill. |
| Schema-derived types | Reach for `Pick`/`Omit`/`Parameters`/`ReturnType`/`Awaited`/`typeof` before declaring a new interface. |
| Object args | Pass objects, not positional, so argument order is self-documenting. Skip on hot paths (per-frame render, tokenizers, parsers). |
| Real tests | Don't mock what you can run. Prefer the framework's real test primitives with leak/disposable checks, and verify UI in a running build. Mock only what you can't run locally. |
| Structured telemetry | Prefer structured logger diagnostics with enough context to debug from an id. No `console.log` in shipped code. |
Examples: `references/patterns.md`.
@@ -0,0 +1,292 @@
# TypeScript patterns
Code examples for each rule in `SKILL.md`. The underlying principles are language-agnostic; see the **type-system-discipline** and **boundary-discipline** principle skills.
## Branded types
Brand primitives so they can't be mixed up. Validate once at creation; downstream code trusts the type.
```ts
type AgentId = string & { readonly __brand: "AgentId" };
function parseAgentId(input: string): AgentId {
if (!isUUID(input)) throw new Error(`Invalid agent id: ${input}`);
return input as AgentId;
}
function focusAgent(id: AgentId): void {
/* input is trusted */
}
```
Match the `readonly __brand: 'X'` shape; don't invent a new convention.
## Discriminated unions
If a bug forces the question "wait, can this combination actually happen?", the type is too loose. Model variants with a literal discriminant: every variant shares the field name and each variant's value is unique, so impossible combos can't be represented.
```ts
// Don't. Boolean + optionals lets contradictory states exist.
type DiffState = { loading: boolean; diff?: GitDiff; error?: string };
// Do. Only valid states exist.
type DiffState =
| { kind: "loading" }
| { kind: "ready"; diff: GitDiff }
| { kind: "error"; error: string };
```
Pick one discriminant name (`kind`, `type`, `tag`) and stick to it.
## Constructive modeling
Build the type from parts that are all legal instead of restricting a loose type with runtime checks. Adding is easier than subtracting.
Non-empty, via a variadic tuple:
```ts
type NonEmpty<T> = [T, ...T[]];
// Don't: T[] plus a length check every caller must repeat
function pickWinner(entries: string[]): string {
if (entries.length === 0) throw new Error("no entries");
return entries[Math.floor(Math.random() * entries.length)];
}
// Do: an empty value of the type can't exist
function pickWinner(entries: NonEmpty<string>): string {
return entries[Math.floor(Math.random() * entries.length)];
}
```
Where a plain `T[]` arrives, narrow once with a guard. The fact then travels in the type:
```ts
const isNonEmpty = <T>(arr: T[]): arr is NonEmpty<T> => arr.length > 0;
```
Even length, as pairs. TypeScript has no refinement types (no `arr.length % 2 === 0` at the type level); you don't need one:
```ts
type Pairs<T> = [T, T][];
```
A time range, as start plus duration:
```ts
// Don't: a comment holds the invariant
type TimeRange = { start: Date; end: Date }; // start <= end
// Do: a negative range can't be written; derive end when needed
type TimeRange = { start: Date; durationMs: number };
```
Keep `durationMs` a plain number. Brand it (per Branded types) only if a raw number could be passed where a duration is expected, not by reflex. A `Pairs<T>` is an even-length list under the interpretation you give it, the same way `{ start, durationMs }` is a range. Pick the representation that makes the bad state unconstructable, then expose the reading you need on top (`pairs.flat()`, a `rangeEnd()` helper).
## Simplest total type
Don't strengthen everything. Keep `T[]` when every operation on it is total:
```ts
const sum = (xs: number[]) => xs.reduce((a, b) => a + b, 0); // [] is 0, fine
```
Strengthen when the loose type forces a lie at a use site. The tells are `!`, `arr[0] as T`, and a "should never happen" throw:
```ts
// Don't: partiality smuggled past the compiler
function newestSession(sessions: Session[]): Session {
return sessions.at(0)!;
}
// Do: strengthen the input; the assertion disappears
function newestSession(sessions: NonEmpty<Session>): Session {
return sessions[0];
}
```
Weakening the result to `Session | undefined` is the other total signature. Either way the empty case lands at the call site, the one place that knows what empty means.
## `unknown` over `any`
`any` disables type checking for everything it touches. External data is always `unknown`. Narrow before use.
```ts
// Don't
function handle(input: any) {
return input.foo.bar;
}
// Do
function handle(input: unknown) {
if (typeof input === "object" && input !== null && "foo" in input) {
// narrowed; compiler verifies access
}
}
```
External sources include RPC payloads, `JSON.parse`, `postMessage`, IPC, file contents, environment variables, database results.
## No `as` casts
Every `as` is a potential runtime crash. Cast only after the type system has verified the claim.
```ts
// Don't
const user = data as User;
// Do. Earn the cast at the boundary.
function parseUser(data: unknown): User {
if (typeof data !== "object" || data === null) {
throw new Error("expected object");
}
if (!("id" in data) || typeof (data as Record<string, unknown>).id !== "string") {
throw new Error("expected id");
}
// ... validate all fields
return data as User; // OK, earned cast after full validation
}
```
When refactoring an `as` out of existing code, identify why TypeScript can't infer:
- Missing discriminant: add one, switch to a discriminated union.
- Overly wide source type (e.g. `Record<string, unknown>`): narrow it.
- Untyped boundary: add a parse function or schema.
- Genuinely inexpressible: use a branded type or `satisfies`.
## Narrowing hierarchy
From best to last-resort:
1. **Discriminated union switch / if.** Compiler narrows automatically.
2. **`in` operator.** `"key" in obj` narrows to variants containing that key.
3. **`typeof` / `instanceof`.** For primitives and class instances.
4. **User-defined type guard.** When the above aren't enough.
5. **`as` cast.** Only after validation.
```ts
function area(s: Shape): number {
if ("radius" in s) return Math.PI * s.radius ** 2; // narrowed to circle
return s.width * s.height; // narrowed to rect
}
```
## Type guards
A guard must actually verify the claim. A lying guard is worse than `as` because the bug hides behind a name that says it's safe.
```ts
function isCircle(s: Shape): s is Shape & { kind: "circle" } {
return s.kind === "circle";
}
```
Prefer discriminant narrowing when possible. The guard adds a layer the reader has to follow.
## Exhaustiveness
In default arms, assign the discriminant to a `never`-typed local. The compiler errors if a new variant is added without handling.
```ts
// Value-returning switch
function area(s: Shape): number {
switch (s.kind) {
case "circle":
return Math.PI * s.radius ** 2;
case "rect":
return s.width * s.height;
default: {
const _exhaustive: never = s;
return _exhaustive;
}
}
}
// Void switch
function handle(s: Shape): void {
switch (s.kind) {
case "circle":
drawCircle(s);
break;
case "rect":
drawRect(s);
break;
default: {
const _exhaustive: never = s;
void _exhaustive;
}
}
}
```
Return-style in value-returning switches; void-style in statement switches.
## `satisfies` over `as`
`satisfies` validates without widening literal types.
```ts
// Don't. Widens, loses literal types.
const config = { theme: "dark", cols: 3 } as Config;
// Do. Validates AND preserves literal types.
const config = { theme: "dark", cols: 3 } satisfies Config;
// config.theme is "dark" (literal), not string
```
## Boundary validation
Validate once where data crosses in; trust types inside. See the **boundary-discipline** principle skill.
- **Wire formats** (proto, JSON-RPC): parse with `ignoreUnknownFields` so forward-compatible changes don't break old clients.
- **Persisted JSON:** versioned blob with a try/catch around the parse.
- **Don't re-validate** deep in call chains.
## Schema-derived types
When a `.proto`, OpenAPI spec, GraphQL schema, or database migration already defines a shape, derive from the generated types instead of duplicating them.
```ts
// Don't. Duplicate shape, drifts when the schema changes.
type CheckSummary = {
totalCount: number;
checks: { name: string; status: string }[];
};
function renderChecks(s: CheckSummary) {
/* ... */
}
// Do. Derive from the generated schema type.
import type { ChecksMessage } from "<generated module>";
function renderChecks(s: Pick<ChecksMessage, "totalCount" | "checks">) {
/* ... */
}
```
Reach for `Pick`, `Omit`, `Parameters`, `ReturnType`, `Awaited`, `typeof` before writing a new interface.
## Object args
```ts
// Don't. Swap two args, still compiles.
openFile(uri, {
startLineNumber: 10,
startColumn: 1,
endLineNumber: 10,
endColumn: 1,
});
// Do. Order-independent, self-documenting.
openFile({
uri,
selection: {
startLineNumber: 10,
startColumn: 1,
endLineNumber: 10,
endColumn: 1,
},
});
```
Skip on hot paths: per-frame render, tokenizers, parsers, anything in a tight loop where the allocation cost matters.
+80
View File
@@ -0,0 +1,80 @@
---
name: unslop
description: Cut AI tells from any writing. Must always apply.
---
# Unslop
Edit text to remove AI patterns and add human voice.
## Process
1. Scan for the patterns below.
2. Rewrite. Preserve meaning, match intended tone.
3. Add soul (see next section).
4. Self-audit: "What makes this obviously AI generated?" Fix remaining tells.
## Adding soul
Removing patterns is half the job. Sterile, voiceless writing is just as obvious.
- **Have opinions.** React to facts instead of neutrally listing pros and cons.
- **Vary rhythm.** Short sentences. Then longer ones that take their time. Mix it up.
- **Acknowledge complexity.** "Impressive but also kind of unsettling" beats "impressive."
- **Use "I" when it fits.** First person isn't unprofessional.
- **Let some mess in.** Perfect structure looks machine-made.
- **Be specific.** Not "this is concerning" but "there's something unsettling about agents churning away at 3am."
## Patterns to detect and fix
### Content
1. **Puffery.** "pivotal moment", "testament to", "evolving landscape", "setting the stage for", "indelible mark", "deeply rooted". Cut puffery, state what happened.
2. **Name-dropping.** Listing media outlets without context. Pick one, say what was said.
3. **Superficial -ing phrases.** "highlighting...", "ensuring...", "reflecting...", "showcasing...", "fostering...". Delete or expand with real sources.
4. **Promotional language.** "nestled", "vibrant", "breathtaking", "groundbreaking", "renowned", "stunning", "must-visit". Use neutral descriptions.
5. **Vague attributions.** "Experts believe", "Industry reports suggest", "Some critics argue". Name the source or delete.
6. **Formulaic challenges.** "Despite challenges... continues to thrive." Replace with specific facts.
### Language
7. **AI vocabulary.** Additionally, crucial, delve, enduring, enhance, fostering, garner, interplay, intricate, landscape (abstract), pivotal, showcase, tapestry (abstract), testament, underscore, vibrant. Replace with plain words.
8. **Fancy ways to say "is".** "serves as", "stands as", "boasts", "features". Just say "is" or "has".
9. **"Not just X, but Y."** State the point directly instead.
10. **Rule of three.** Forcing ideas into groups of three. Use the natural number.
11. **Synonym cycling.** Protagonist, main character, central figure, hero all in one paragraph. Pick one, repeat it.
12. **False ranges.** "from X to Y" where X and Y aren't on a meaningful scale. List topics directly.
### Style
13. **Em dash overuse.** Avoid em dashes entirely. Use periods or commas only (no parentheses, no en dashes, no hyphen-as-dash substitutes). Em dashes are an AI tell, and reaching for parentheses instead just trades one tell for another. If a thought needs separation, end the sentence or use a comma.
14. **Colon overuse.** Colons are fine before a list or example. Not as mid-sentence connectors. "If you're coming from traditional automation: instead of registering event handlers, you describe conditions" adds nothing with the colon. Rewrite to let the point stand on its own without comparison framing. "Describing when the scheduler should fire works best as plain English." Same meaning, no crutch punctuation.
15. **Boldface overuse.** Don't bold every proper noun or acronym.
16. **Inline-header lists.** The tell is a bold label and colon that restates the line: "**Performance:** Performance improved...". Convert those to prose. A bold lead-in that ends in a period, names the item, and is followed by genuinely new detail ("**Schema in TypeScript.** Tables live in one file.") is fine, not a tell.
17. **Title case headings.** Use sentence case.
18. **Decorative emojis.** Remove from headings and bullets.
19. **Curly quotes.** Replace with straight quotes.
### Communication artifacts
20. **Chatbot phrases.** "I hope this helps!", "Let me know if...", "Of course!", "Certainly!", "Found the smoking gun!" Remove.
21. **Cutoff disclaimers.** "While specific details are limited..." Find sources or remove.
22. **Sycophantic tone.** "Great question! You're absolutely right!" Respond directly.
### Filler
23. **Filler phrases.** "In order to" becomes "To". "Due to the fact that" becomes "Because". "It is important to note that" gets deleted.
24. **Excessive hedging.** "could potentially possibly be argued that it might" becomes "may".
25. **Generic conclusions.** "The future looks bright." State specific plans or facts.
### Jargon
26. **Abstract metaphor nouns.** Substrate, wedge, vector, locus, vantage, nexus, primitive (as noun), harness (as metaphor), surface (as in "API surface"), bedrock, scaffolding (as metaphor), modality, paradigm, gold-plating, ratchet (as metaphor), evacuate (for moving code), endgame, north star, flywheel. These read as technical but usually have a plainer concrete word. "Substrate" becomes "base". "Wedge in" becomes "add". "Vector" becomes "way" or "method". "Gold-plating" becomes "more than the job needs". "Ratchet" becomes the mechanism's real name or "a limit that only tightens". "Evacuate" becomes "move out". "Endgame" becomes "the last phase". Pick the concrete word.
### Plain speech
27. **Say what it does, not how it feels.** "the database stays close at hand", "SQL you can read", "types that follow your schema" name a feeling. The fix names the mechanism or a number: "`.toSQL()` returns the exact string sent to the database", "a column rename fails the build". Ask what the sentence tells the reader to do or know, then write that. If you can't restate it as a concrete instruction, fact, or number, cut it. One more check: if the sentence could appear unchanged in another project's docs, it says nothing about this one. Cut it.
28. **Shorten or split dense sentences.** If the reader has to backtrack to parse a sentence, break it in two or drop clauses. One idea per sentence.
29. **Active voice.** Prefer it. Catch "is/are/was/were + past participle" and name the actor: "queries are validated" becomes "the compiler validates queries", "the file is parsed by the loader" becomes "the loader parses the file". Passive is fine only when the actor is unknown or genuinely doesn't matter.
30. **Cut adverbs, or use a stronger verb.** "runs quickly" becomes "is fast" or the number. "significantly improves" becomes the measured delta. An adverb propping up a weak verb means the verb is wrong.
31. **Prefer the plain word.** "utilize" becomes "use", "leverage" becomes "use", "facilitate" becomes "help", "numerous" becomes "many", "in the event that" becomes "if". The fancier synonym is rarely clearer.
+32 -8
View File
@@ -1,5 +1,5 @@
model = "gpt-5.5" model = "gpt-5.6-sol"
model_reasoning_effort = "medium" model_reasoning_effort = "high"
personality = "pragmatic" personality = "pragmatic"
tool_output_token_limit = 25000 tool_output_token_limit = 25000
# Leave room for native compaction near the 272273k context window. # Leave room for native compaction near the 272273k context window.
@@ -94,6 +94,15 @@ trust_level = "trusted"
[projects."/Users/sudacode/projects/japanese/anilist-jiten"] [projects."/Users/sudacode/projects/japanese/anilist-jiten"]
trust_level = "trusted" trust_level = "trusted"
[projects."/Users/sudacode/Documents/Codex/2026-08-16/on"]
trust_level = "trusted"
[projects."/Users/sudacode/Documents/Codex/2026-08-16/help"]
trust_level = "trusted"
[projects."/Users/sudacode/Documents/Codex/2026-08-19/this"]
trust_level = "trusted"
[mcp_servers.backlog] [mcp_servers.backlog]
command = "backlog" command = "backlog"
args = ["mcp", "start"] args = ["mcp", "start"]
@@ -115,22 +124,23 @@ startup_timeout_sec = 120
NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS = "1000" NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS = "1000"
NODE_REPL_NODE_MODULE_DIRS = "/Applications/ChatGPT.app/Contents/Resources/cua_node/lib/node_modules" NODE_REPL_NODE_MODULE_DIRS = "/Applications/ChatGPT.app/Contents/Resources/cua_node/lib/node_modules"
NODE_REPL_NODE_PATH = "/Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node" NODE_REPL_NODE_PATH = "/Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node"
NODE_REPL_TRUSTED_CODE_PATHS = "/Users/sudacode/.codex" NODE_REPL_TRUSTED_CODE_PATHS = "/Users/sudacode/.codex:/Applications/ChatGPT.app/Contents/Resources/cua_node/lib/node_modules"
CODEX_HOME = "/Users/sudacode/.codex" CODEX_HOME = "/Users/sudacode/.codex"
NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S = "6d25aa7656feac858f3a3bdaea5bcbab0dbfd426c9de8e6931ce90c399ee8e4f,c8b7e809d7cf9e20a57123b2530d476ccec9a01a6230a7b2b924fea7d94d7f4a" NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S = "9230e2bd8b24b7ac7a0ba6774c64bf0d78ecdabbdd91d0ed627b02a587bae2df"
BROWSER_USE_AVAILABLE_BACKENDS = "chrome,iab" BROWSER_USE_AVAILABLE_BACKENDS = "chrome,iab"
NODE_REPL_INSTRUCTIONS_USE_CASE_BROWSER = "Control the in-app browser in conjunction with the Browser Plugin." NODE_REPL_INSTRUCTIONS_USE_CASE_BROWSER = "Control the in-app browser in conjunction with the Browser Plugin."
NODE_REPL_INSTRUCTIONS_USE_CASE_CHROME = "Control the Chrome browser in conjunction with the Chrome Plugin. Prefer this method of controlling Chrome over alternatives (such as Computer Use) unless the user explicitly mentions an alternative." NODE_REPL_INSTRUCTIONS_USE_CASE_CHROME = "Control the Chrome browser in conjunction with the Chrome Plugin. Prefer this method of controlling Chrome over alternatives (such as Computer Use) unless the user explicitly mentions an alternative."
NODE_REPL_INSTRUCTIONS_USE_CASE_COMPUTER_USE = "Control desktop apps on macOS through Computer Use." NODE_REPL_INSTRUCTIONS_USE_CASE_COMPUTER_USE = "Control desktop apps on macOS through Computer Use."
BROWSER_USE_CODEX_APP_BUILD_FLAVOR = "prod" BROWSER_USE_CODEX_APP_BUILD_FLAVOR = "prod"
BROWSER_USE_CODEX_APP_VERSION = "26.707.31428" BROWSER_USE_CODEX_APP_VERSION = "26.810.52044"
SKY_CUA_SERVICE_PATH = "/Users/sudacode/.codex/computer-use/Codex Computer Use.app"
CODEX_CLI_PATH = "/Applications/ChatGPT.app/Contents/Resources/codex" CODEX_CLI_PATH = "/Applications/ChatGPT.app/Contents/Resources/codex"
[mcp_servers.computer-use] [mcp_servers.computer-use]
command = "./Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient" command = "./Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient"
args = ["mcp"] args = ["mcp"]
cwd = "." cwd = "."
enabled = true enabled = false
[plugins."github@openai-curated"] [plugins."github@openai-curated"]
enabled = true enabled = true
@@ -156,9 +166,18 @@ enabled = true
[plugins."visualize@openai-bundled"] [plugins."visualize@openai-bundled"]
enabled = true enabled = true
[plugins."computer-use@openai-bundled"]
enabled = true
[plugins."browser@openai-bundled"] [plugins."browser@openai-bundled"]
enabled = true enabled = true
[plugins."pdf@openai-primary-runtime"]
enabled = true
[plugins."template-creator@openai-primary-runtime"]
enabled = true
[notice.model_migrations] [notice.model_migrations]
"gpt-5.2-codex" = "gpt-5.4" "gpt-5.2-codex" = "gpt-5.4"
@@ -167,12 +186,12 @@ enabled = true
"gpt-5.6-sol" = 4 "gpt-5.6-sol" = 4
[marketplaces.openai-bundled] [marketplaces.openai-bundled]
last_updated = "2026-07-10T05:36:55Z" last_updated = "2026-08-16T07:38:41Z"
source_type = "local" source_type = "local"
source = "/Users/sudacode/.codex/.tmp/bundled-marketplaces/openai-bundled" source = "/Users/sudacode/.codex/.tmp/bundled-marketplaces/openai-bundled"
[marketplaces.openai-primary-runtime] [marketplaces.openai-primary-runtime]
last_updated = "2026-05-09T00:22:09Z" last_updated = "2026-08-16T07:39:44Z"
source_type = "local" source_type = "local"
source = "/Users/sudacode/.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime" source = "/Users/sudacode/.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime"
@@ -228,3 +247,8 @@ global = "vscode"
"/Users/sudacode/github/SubMiner" = "vscode" "/Users/sudacode/github/SubMiner" = "vscode"
"/Users/sudacode/projects/japanese/SubMiner" = "vscode" "/Users/sudacode/projects/japanese/SubMiner" = "vscode"
"/Users/sudacode/.codex/worktrees/204a/SubMiner" = "ghostty" "/Users/sudacode/.codex/worktrees/204a/SubMiner" = "ghostty"
[shell_environment_policy.set]
BROWSER_USE_AVAILABLE_BACKENDS = "chrome,iab"
NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S = "9230e2bd8b24b7ac7a0ba6774c64bf0d78ecdabbdd91d0ed627b02a587bae2df"
NODE_REPL_TRUSTED_CODE_PATHS = "/Users/sudacode/.codex:/Applications/ChatGPT.app/Contents/Resources/cua_node/lib/node_modules"
+35 -2
View File
@@ -1,5 +1,5 @@
model = "gpt-5.6-sol" model = "gpt-6-astra"
model_reasoning_effort = "high" model_reasoning_effort = "medium"
personality = "pragmatic" personality = "pragmatic"
tool_output_token_limit = 25000 tool_output_token_limit = 25000
# Leave room for native compaction near the 272273k context window. # Leave room for native compaction near the 272273k context window.
@@ -9,6 +9,7 @@ model_auto_compact_token_limit = 233000
suppress_unstable_features_warning = true suppress_unstable_features_warning = true
sandbox_mode = "workspace-write" sandbox_mode = "workspace-write"
service_tier = "default" service_tier = "default"
approvals_reviewer = "auto_review"
[tui] [tui]
notifications = ["agent-turn-complete", "approval-requested"] notifications = ["agent-turn-complete", "approval-requested"]
@@ -17,6 +18,7 @@ notification_condition = "always"
[tui.model_availability_nux] [tui.model_availability_nux]
"gpt-5.5" = 4 "gpt-5.5" = 4
"gpt-5.6-sol" = 4 "gpt-5.6-sol" = 4
gpt-6-astra = 4
[sandbox_workspace_write] [sandbox_workspace_write]
network_access = true network_access = true
@@ -42,6 +44,10 @@ args = ["@playwright/mcp@latest", "--executable-path", "/usr/bin/helium-browser"
[mcp_servers.openaiDeveloperDocs] [mcp_servers.openaiDeveloperDocs]
url = "https://developers.openai.com/mcp" url = "https://developers.openai.com/mcp"
[mcp_servers.anki]
command = "npx"
args = ["-y", "@ankimcp/anki-mcp-server", "--stdio"]
[projects."/home/sudacode/projects"] [projects."/home/sudacode/projects"]
trust_level = "trusted" trust_level = "trusted"
@@ -228,6 +234,33 @@ trust_level = "trusted"
[projects."/home/sudacode/games/ModEngine2"] [projects."/home/sudacode/games/ModEngine2"]
trust_level = "trusted" trust_level = "trusted"
[projects."/truenas/jellyfin/anime/BanG Dream! It's MyGO!!!!!/Season-1"]
trust_level = "trusted"
[projects."/tmp/claude-1000/-home-sudacode--agents-skills-claude-code-computer-delegate/df15b0bc-3ae1-4e90-a006-21c31ddc8634/scratchpad"]
trust_level = "trusted"
[projects."/home/sudacode/.local/share/Steam/steamapps/common/DARK SOULS III/Game"]
trust_level = "trusted"
[projects."/home/sudacode/projects/japanese/kiku"]
trust_level = "trusted"
[projects."/home/sudacode/Documents/anki"]
trust_level = "trusted"
[projects."/home/sudacode/Documents/anki/stats"]
trust_level = "trusted"
[projects."/home/sudacode/projects/sudacode-blog"]
trust_level = "trusted"
[projects."/home/sudacode/projects/japanese/Mangatan"]
trust_level = "trusted"
[projects."/truenas/jellyfin/anime/I Made Friends with the Second Prettiest Girl in My Class/Season-1"]
trust_level = "trusted"
[notice.model_migrations] [notice.model_migrations]
"gpt-5.3-codex" = "gpt-5.4" "gpt-5.3-codex" = "gpt-5.4"
+1 -1
View File
@@ -68,7 +68,7 @@ update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct", #* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly. #* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "memory" proc_sorting = "cpu lazy"
#* Reverse sorting order, True or False. #* Reverse sorting order, True or False.
proc_reversed = false proc_reversed = false
+1
View File
@@ -419,3 +419,4 @@ hl.on("hyprland.start", function()
hl.exec_cmd("uwsm app -sb -t service -- tailscale systray") hl.exec_cmd("uwsm app -sb -t service -- tailscale systray")
hl.exec_cmd("~/.local/bin/aria") hl.exec_cmd("~/.local/bin/aria")
end) end)
pcall(require, "/home/sudacode/.config/hypr/openwhispr-binds.lua")
+8 -1
View File
@@ -104,7 +104,7 @@ bind = $mainMod SHIFT, d, exec, "$HOME/.config/rofi/scripts/rofi-docs.sh"
bind = SUPER SHIFT, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh" bind = SUPER SHIFT, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh"
bind = SUPER, t, exec, "$HOME/.config/rofi/scripts/rofi-launch-texthooker-steam.sh" bind = SUPER, t, exec, "$HOME/.config/rofi/scripts/rofi-launch-texthooker-steam.sh"
bind = $mainMod SHIFT, t, exec, "$HOME/projects/scripts/popup-ai-translator.py" bind = $mainMod SHIFT, t, exec, "$HOME/projects/scripts/popup-ai-translator.py"
bind = SUPER SHIFT, g, exec, "$HOME/.config/rofi/scripts/rofi-vn-helper.sh" # bind = SUPER SHIFT, g, exec, "$HOME/.config/rofi/scripts/rofi-vn-helper.sh"
bind = $mainMod SHIFT, i, exec, "$HOME/.config/rofi/scripts/rofi-image-browser.sh" bind = $mainMod SHIFT, i, exec, "$HOME/.config/rofi/scripts/rofi-image-browser.sh"
# ncmcppp # ncmcppp
@@ -167,6 +167,13 @@ bind = $mainMod, a, exec, ~/.config/rofi/scripts/rofi-anki-script.sh
# bindl = , mouse:276, exec, xdotool key alt+grave # bottom mouse to overlay # bindl = , mouse:276, exec, xdotool key alt+grave # bottom mouse to overlay
bind = ALT, g, exec, /opt/mpv-yomitan/mpv-yomitan.AppImage --toggle bind = ALT, g, exec, /opt/mpv-yomitan/mpv-yomitan.AppImage --toggle
# Hold Left Shift to show the GSM overlay while this submap is active.
bind = SUPER, g, submap, gsm
submap = gsm
bind = SHIFT SUPER, g, submap, reset
bind = SHIFT, Shift_L, pass, class:^(gsm_overlay)$
submap = reset
# F5 # F5
# bind = ,code:71, exec, ~/projects/scripts/whisper_record_transcribe.py --mode toggle --output type # bind = ,code:71, exec, ~/projects/scripts/whisper_record_transcribe.py --mode toggle --output type
bind = ,code:71, exec, uv run --directory ~/projects/scripts/faster-whisper-transcribe faster-whisper-transcribe --backend ctranslate2 --device cpu --mode toggle --output type bind = ,code:71, exec, uv run --directory ~/projects/scripts/faster-whisper-transcribe faster-whisper-transcribe --backend ctranslate2 --device cpu --mode toggle --output type
+9 -2
View File
@@ -11,7 +11,7 @@ local menu = "~/.config/rofi/launchers/type-6/launcher.sh 1"
local mainMod = "ALT" -- Sets "Windows" key as main modifier local mainMod = "ALT" -- Sets "Windows" key as main modifier
-- Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more -- Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
hl.bind("SUPER + SUPER_L", hl.dsp.exec_cmd("~/.config/rofi/launchers/type-2/launcher.sh 10")) -- hl.bind("SUPER + SUPER_L", hl.dsp.exec_cmd("~/.config/rofi/launchers/type-2/launcher.sh 10"))
hl.bind(mainMod .. " + RETURN", hl.dsp.exec_cmd(terminal)) hl.bind(mainMod .. " + RETURN", hl.dsp.exec_cmd(terminal))
hl.bind(mainMod .. " + Q", hl.dsp.window.close()) hl.bind(mainMod .. " + Q", hl.dsp.window.close())
hl.bind(mainMod .. " + SHIFT + M", hl.dsp.exec_cmd("uwsm stop")) hl.bind(mainMod .. " + SHIFT + M", hl.dsp.exec_cmd("uwsm stop"))
@@ -116,7 +116,7 @@ hl.bind(mainMod .. " + SHIFT + d", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-
hl.bind("SUPER + SHIFT + j", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-jellyfin-dir.sh")) hl.bind("SUPER + SHIFT + j", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-jellyfin-dir.sh"))
hl.bind("SUPER + t", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-launch-texthooker-steam.sh")) hl.bind("SUPER + t", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-launch-texthooker-steam.sh"))
hl.bind(mainMod .. " + SHIFT + t", hl.dsp.exec_cmd("~/projects/scripts/popup-ai-translator.py")) hl.bind(mainMod .. " + SHIFT + t", hl.dsp.exec_cmd("~/projects/scripts/popup-ai-translator.py"))
hl.bind("SUPER + SHIFT + g", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-vn-helper.sh")) -- hl.bind("SUPER + SHIFT + g", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-vn-helper.sh"))
hl.bind(mainMod .. " + SHIFT + i", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-image-browser.sh")) hl.bind(mainMod .. " + SHIFT + i", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-image-browser.sh"))
-- ncmcppp -- ncmcppp
@@ -209,6 +209,13 @@ hl.bind(mainMod .. " + a", hl.dsp.exec_cmd("~/.config/rofi/scripts/rofi-anki-scr
-- hl.bind("mouse:276", hl.dsp.exec_cmd("xdotool key alt+grave"), { locked = true }) -- hl.bind("mouse:276", hl.dsp.exec_cmd("xdotool key alt+grave"), { locked = true })
hl.bind("ALT + g", hl.dsp.exec_cmd("/opt/mpv-yomitan/mpv-yomitan.AppImage --toggle")) hl.bind("ALT + g", hl.dsp.exec_cmd("/opt/mpv-yomitan/mpv-yomitan.AppImage --toggle"))
-- Hold Left Shift to show the GSM overlay while this submap is active.
hl.bind("SUPER + g", hl.dsp.submap("gsm"))
hl.define_submap("gsm", function()
hl.bind("SUPER + SHIFT + g", hl.dsp.submap("reset"))
hl.bind("SHIFT + Shift_L", hl.dsp.pass({ window = "class:^(gsm_overlay)$" }))
end)
hl.bind("ALT + SHIFT + f", hl.dsp.exec_cmd("uwsm app -sb -- flameshot gui")) hl.bind("ALT + SHIFT + f", hl.dsp.exec_cmd("uwsm app -sb -- flameshot gui"))
-- F5 -- F5
+23 -5
View File
@@ -299,13 +299,18 @@ hl.window_rule({
-- {{{ GSM Overlay and LunaTranslator tweaks -- {{{ GSM Overlay and LunaTranslator tweaks
hl.window_rule({ hl.window_rule({
match = { match = {
class = "gsm_overlay", class = "com.beangate.gamesentenceminer",
}, },
float = true, float = true,
-- TODO: manual review — unmapped window rule action: "border_size 0" border_size = 0,
-- TODO: manual review — unmapped window rule action: "xray off" xray = false,
-- TODO: manual review — unmapped window rule action: "no_shadow on" no_shadow = true,
-- TODO: manual review — unmapped window rule action: "no_blur on" no_blur = true,
no_dim = true,
opaque = true,
dim_around = false,
opacity = "1.0 override 1.0 override",
pin = false,
}) })
hl.window_rule({ hl.window_rule({
@@ -390,6 +395,19 @@ hl.window_rule({
pin = true, pin = true,
}) })
hl.window_rule({
match = {
class = "open-whispr",
title = "Voice Recorder",
},
float = true,
xray = false,
no_shadow = true,
no_blur = true,
no_dim = true,
opaque = true,
})
-- TODO: manual review — top-level key 'windowurle = no_vrr on, match:class mpv' has no enclosing section -- TODO: manual review — top-level key 'windowurle = no_vrr on, match:class mpv' has no enclosing section
-- aibar popup (AI usage widget) -- aibar popup (AI usage widget)
+2 -2
View File
@@ -71,7 +71,7 @@ x-scheme-handler/tonsite=org.telegram.desktop.desktop;
x-scheme-handler/tradingview=tradingview.desktop;TradingView.desktop; x-scheme-handler/tradingview=tradingview.desktop;TradingView.desktop;
application/x-wine-extension-ini=nvim.desktop; application/x-wine-extension-ini=nvim.desktop;
x-scheme-handler/subminer=subminer.desktop;SubMiner.desktop; x-scheme-handler/subminer=subminer.desktop;SubMiner.desktop;
x-scheme-handler/t3code=t3code-url-handler.desktop; x-scheme-handler/t3code=t3code-url-handler.desktop;t3code.desktop;
[Default Applications] [Default Applications]
application/x-extension-htm=helium.desktop;zen.desktop application/x-extension-htm=helium.desktop;zen.desktop
@@ -160,5 +160,5 @@ x-scheme-handler/subminer=subminer.desktop
x-scheme-handler/claude-cli=claude-code-url-handler.desktop x-scheme-handler/claude-cli=claude-code-url-handler.desktop
x-scheme-handler/mux=mux.desktop x-scheme-handler/mux=mux.desktop
x-scheme-handler/claude=com.anthropic.claude-desktop.desktop x-scheme-handler/claude=com.anthropic.claude-desktop.desktop
x-scheme-handler/t3code=t3code-url-handler.desktop x-scheme-handler/t3code=com.t3tools.T3Code.desktop
x-scheme-handler/codex=ChatGPT.desktop x-scheme-handler/codex=ChatGPT.desktop
+2 -2
View File
@@ -46,8 +46,8 @@ hwdec=nvdec
hwdec-codecs=all hwdec-codecs=all
gpu-api=vulkan gpu-api=vulkan
gpu-context=waylandvk gpu-context=waylandvk
vulkan-queue-count=2 vulkan-queue-count=1
vulkan-async-compute=yes # Use independent compute queues for tone mapping/shaders vulkan-async-compute=no # Use independent compute queues for tone mapping/shaders
vulkan-async-transfer=yes # Parallelize frame uploads to free the graphics queue vulkan-async-transfer=yes # Parallelize frame uploads to free the graphics queue
vd-lavc-dr=yes # Direct rendering keeps frames resident on the GPU longer vd-lavc-dr=yes # Direct rendering keeps frames resident on the GPU longer
vd-lavc-threads=0 # Let ffmpeg auto-pick the optimal thread count vd-lavc-threads=0 # Let ffmpeg auto-pick the optimal thread count
+3
View File
@@ -0,0 +1,3 @@
std = "lua51"
globals = { "vim", "Snacks" }
max_line_length = false
+11
View File
@@ -0,0 +1,11 @@
WRN 2026-02-24T11:33:23.184 ?.27376 server_start:199: Failed to start server: operation not permitted: /run/user/1000//nvim.27376.0
WRN 2026-02-24T12:04:35.999 ?.29592 server_start:199: Failed to start server: operation not permitted: /run/user/1000//nvim.29592.0
WRN 2026-08-28T15:33:15.778 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-08-28T15:38:46.688 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-08-28T16:08:02.008 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-08-28T16:08:06.345 ?.2 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.2.0
WRN 2026-08-28T16:41:19.378 ?.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: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
+6
View File
@@ -0,0 +1,6 @@
column_width = 120
line_endings = "Unix"
indent_type = "Tabs"
quote_style = "AutoPreferDouble"
call_parentheses = "Always"
collapse_simple_statement = "Never"
+50
View File
@@ -0,0 +1,50 @@
# Neovim Config
read_when: updating keymaps, startup order, or plugin layout
## Load Order
1. `init.lua`
2. `core.lazy` (bootstraps lazy.nvim, loads options)
3. colorscheme
4. `core.keymaps` (core editing, LSP, commands, and which-key groups)
5. `core.autocmds`
6. `core.highlights`
7. Hyprland LSP helper
## Where To Edit
- Options: `lua/core/options.lua`
- Autocmds: `lua/core/autocmds.lua`
- Highlights: `lua/core/highlights.lua`
- Keymaps entrypoint: `lua/core/keymaps/init.lua`
- Keymaps by domain:
- `lua/core/keymaps/editing.lua`
- `lua/core/keymaps/lsp.lua`
- `lua/core/keymaps/commands.lua`
- `lua/core/keymaps/groups.lua`
- Plugin setup and plugin-owned mappings: `lua/plugins/*.lua`
## Structure Notes
- Keep plugin specs in `lua/plugins/`.
- Keep core logic in `lua/core/`.
- Keep reusable helpers in `lua/utils/`.
- Put plugin mappings in the plugin spec's `keys` table so Lazy can load them on demand.
- Use `which-key` only to label mapping groups.
## Tool Ownership
- Formatting: Conform, with explicit formatters and no LSP fallback.
- Linting: nvim-lint on save. Codespell and pydoclint are linters.
- Completion: nvim-cmp with LuaSnip. None-ls is not part of completion.
- File browsing: Snacks Explorer.
- Notifications: Snacks Notifier. Fidget owns LSP progress, and Noice owns command/message UI.
## Checks
```sh
stylua --check .
luacheck .
nvim --headless -i NONE -u ./init.lua '+qa'
```
-5
View File
@@ -3,9 +3,4 @@ vim.cmd("colorscheme catppuccin")
require("core.keymaps") require("core.keymaps")
require("core.autocmds") require("core.autocmds")
require("core.highlights") require("core.highlights")
-- require("core.lsp-notifications")
require("utils.extensions")
require("utils.telescope_extra").setup()
require("utils.functions.git_paste").setup({ telescope_key = "<leader>pg" })
-- require("utils.treesitter.parsers.hyprlang")
require("utils.hyprland.lsp") require("utils.hyprland.lsp")
+61
View File
@@ -0,0 +1,61 @@
{
"LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
"any-jump.vim": { "branch": "master", "commit": "f95674d9a4251ac02f452d5f1861e4422f4652c7" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"catppuccin": { "branch": "main", "commit": "edefef779ab08ce1a4a404713e3012b0d202bd35" },
"cmp-async-path": { "branch": "main", "commit": "98185a91d49ff5dd249aebf2f7456e18063fa2a0" },
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
"cmp-cmdline": { "branch": "main", "commit": "d126061b624e0af6c3a556428712dd4d4194ec6d" },
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
"cmp-nvim-lsp-document-symbol": { "branch": "main", "commit": "f94f7ba948e32cd302caba1c2ca3f7c697fb4fcf" },
"cmp-nvim-lsp-signature-help": { "branch": "main", "commit": "fd3e882e56956675c620898bf1ffcf4fcbe7ec84" },
"cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
"codecompanion.nvim": { "branch": "main", "commit": "6eec6b429de1340096553036a7aa3b08067eab32" },
"conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
"copilot-cmp": { "branch": "master", "commit": "15fc12af3d0109fa76b60b5cffa1373697e261d1" },
"copilot-lualine": { "branch": "main", "commit": "222e90bd8dcdf16ca1efc4e784416afb5f011c31" },
"copilot.lua": { "branch": "master", "commit": "7e6723aabea044519462958ffcea68d7985c5ed0" },
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
"fidget.nvim": { "branch": "main", "commit": "6f793b2bcd2d35e201c09520f698bb763220908a" },
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
"gitsigns.nvim": { "branch": "main", "commit": "5be654f2232c10ddcad19c1607a67b6b4b78fc29" },
"goto-preview": { "branch": "main", "commit": "d2d6923c9b9e0e43f0b9b566f261a8b1ae016540" },
"hererocks": { "branch": "master", "commit": "5d77b0bafc8b96f82355ca2ce5637c00d78a065c" },
"image.nvim": { "branch": "master", "commit": "5c6f29a5069e1f7bd5773ce5907454063b0f125d" },
"img-clip.nvim": { "branch": "main", "commit": "b6ddfb97b5600d99afe3452d707444afda658aca" },
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
"lspkind.nvim": { "branch": "master", "commit": "c7274c48137396526b59d86232eabcdc7fed8a32" },
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
"mcphub.nvim": { "branch": "main", "commit": "7cd5db330f41b7bae02b2d6202218a061c3ebc1f" },
"mini.diff": { "branch": "main", "commit": "626b8a5b93874c4d05ca25aedec56cfff0b378fb" },
"mini.nvim": { "branch": "main", "commit": "9d01f392b33fb2ba36fbc87fc0bf4453e63ffb0a" },
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
"nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
"nvim-autopairs": { "branch": "master", "commit": "430522f95fe4fb7c511ec64f8c1a90cc6a66c05c" },
"nvim-cmp": { "branch": "main", "commit": "2ffe79f1f021def8dd1fcd81deb16f1bb0d989f3" },
"nvim-colorizer.lua": { "branch": "master", "commit": "a065833f35a3a7cc3ef137ac88b5381da2ba302e" },
"nvim-html-css": { "branch": "main", "commit": "3f246f6166e75fb8afc1866ce8f1e4cb8d95757a" },
"nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" },
"nvim-lspconfig": { "branch": "master", "commit": "ee1e369181a9e64904cdd7b739e98c70778cfc71" },
"nvim-nio": { "branch": "master", "commit": "edcc181a875301dd21840189aa2f2f9ad69fc172" },
"nvim-treesitter": { "branch": "main", "commit": "19071296d3d643b48615ee574a20e8a03ac40872" },
"nvim-treesitter-context": { "branch": "master", "commit": "f3061339b8eaf9fda873600bc425b8d2d8502533" },
"nvim-web-devicons": { "branch": "master", "commit": "5f032a85be210cd1c6ac98861eb3b187ff3bd5eb" },
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
"presence.nvim": { "branch": "main", "commit": "87c857a56b7703f976d3a5ef15967d80508df6e6" },
"rainbow-delimiters.nvim": { "branch": "master", "commit": "012f1480cd9a5fc99fce7678e0a536421a53fc46" },
"render-markdown.nvim": { "branch": "main", "commit": "4663eb3ecd538bd5062628fb6d95bbe6bdca78f6" },
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
"telescope-color-names.nvim": { "branch": "main", "commit": "95b372b9a8ba0fc7cf6a67be637ee37453f322da" },
"telescope-fzf-native.nvim": { "branch": "main", "commit": "b25b749b9db64d375d782094e2b9dce53ad53a40" },
"telescope-glyph.nvim": { "branch": "master", "commit": "6e0bdece0d0382e664b2dc716a9c5641994148c9" },
"telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" },
"telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" },
"toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" },
"vim-commentary": { "branch": "master", "commit": "64a654ef4a20db1727938338310209b6a63f60c9" },
"vim-dotenv": { "branch": "master", "commit": "5c51cfcf8d87280d6414e03cd6b253eb70ecb800" },
"vim-surround": { "branch": "master", "commit": "3d188ed2113431cf8dac77be61b842acb64433d9" },
"vim-wakatime": { "branch": "master", "commit": "9f8a1d3b9c6f3a948988a0896b3227c1e1f74a58" },
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
}
+2 -18
View File
@@ -39,29 +39,13 @@ autocmd("TextYankPost", {
group = highlight_yank, group = highlight_yank,
pattern = "*", pattern = "*",
callback = function() callback = function()
vim.highlight.on_yank({ higroup = "IncSearch", timeout = 420 }) vim.hl.on_yank({ higroup = "IncSearch", timeout = 420 })
end, end,
}) })
-- }}} -- }}}
-- {{{ Disable indent-blankline for dashboard
function disable_for_dashboard()
local buftype = vim.api.nvim_buf_get_option(0, "buftype")
local filetype = vim.api.nvim_buf_get_option(0, "filetype")
if buftype == "nofile" and filetype == "dashboard" then
vim.b.indent_blankline_enabled = false
end
end
vim.cmd([[
augroup IndentBlankline
autocmd!
autocmd FileType dashboard lua disable_for_dashboard()
augroup END
]])
-- }}}
-- {{{ Code companion hook -- {{{ Code companion hook
local group = augroup("CodeCompanionHooks", {}) local group = augroup("CodeCompanionHooks", { clear = true })
autocmd({ "User" }, { autocmd({ "User" }, {
pattern = "CodeCompanionInline*", pattern = "CodeCompanionInline*",
-681
View File
@@ -1,681 +0,0 @@
local map = vim.keymap.set
local map_from_table = require("utils.keymaps.converters.from_table").set_keybindings
local add_to_whichkey = require("utils.keymaps.converters.whichkey").addToWhichKey
local telescope_paste_img = require("utils.telescope_extra").find_and_paste_image
local mkdir_under_cursor = require("utils.functions.mkdir_under_cursor").setup()
local term = require("utils.terminal")
local term_factory = term.term_factory
local term_toggle = term.term_toggle
local nosilent = { silent = false, noremap = true }
-- Leader key
vim.g.mapleader = " "
vim.g.maplocalleader = ","
-- Create a custom command with the given trigger, command, and description
--- @param trigger string The command trigger
--- @param command string The command to execute
--- @param description string Description of the command
--- @return nil
local create_custom_command = function(trigger, command, description)
vim.api.nvim_create_user_command(trigger, command, { desc = description })
end
-- Custom commands
create_custom_command("Config", "edit ~/.config/nvim", "Edit nvim configuration")
create_custom_command("Keymaps", "edit ~/.config/nvim/lua/core/keymaps.lua", "Edit Hyprland keybindings")
create_custom_command("Hypr", "edit ~/.config/hypr/hyprland.conf", "Edit Hyprland configuration")
vim.keymap.set("", "<Leader>tl", function()
vim.diagnostic.enable(not vim.diagnostic.is_enabled())
end, { desc = "Toggle diagnostics virtual text" })
-- {{{ Basic Mappings
local basic_mappings = {
{ key = "<C-u>", cmd = "<C-u>zz", desc = "Scroll up and center", mode = "n" },
{ key = "n", cmd = "nzzzv", desc = "Next search result and center", mode = "n" },
{ key = "N", cmd = "Nzzzv", desc = "Previous search result and center", mode = "n" },
{ key = "<leader>pp", cmd = '"_dP', desc = "Paste without yanking", mode = "x" },
{ key = "<", cmd = "<gv", desc = "Reselect after indent", mode = "v" },
{ key = ">", cmd = ">gv", desc = "Reselect after indent", mode = "v" },
{ key = "J", cmd = ":m '>+1<CR>gv=gv", desc = "Move line down", mode = "v" },
{ key = "K", cmd = ":m '<-2<CR>gv=gv", desc = "Move line up", mode = "v" },
}
--}}}
--{{{ Buffer Navigation Mappings
local buffer_navigation_mappings = {
{ key = "<C-J>", cmd = ":bnext<CR>", desc = "Next buffer", mode = "n" },
{ key = "<C-K>", cmd = ":bprev<CR>", desc = "Previous buffer", mode = "n" },
{ key = "<leader>bb", cmd = ":Telescope buffers<CR>", desc = "List buffers", mode = "n" },
{ key = "<leader>bk", cmd = ":bdelete<CR>", desc = "Delete buffer", mode = "n" },
{ key = "<leader>bn", cmd = ":bnext<CR>", desc = "Next buffer", mode = "n" },
{ key = "<leader>bp", cmd = ":bprev<CR>", desc = "Previous buffer", mode = "n" },
}
--}}}
--{{{ Terminal Mappings
local terminal_mappings = {
-- {
-- key = "op",
-- cmd = "<C-\\><C-N>:ToggleTerm name=ipython",
-- desc = "Open IPython",
-- mode = "v",
-- group = "Open",
-- },
-- {
-- key = "oP",
-- cmd = "<C-\\><C-N>:ToggleTerm name=ipython-full",
-- desc = "Open full IPython",
-- mode = "v",
-- group = "Open",
-- },
{
key = "<C-T>",
cmd = ":ToggleTerm name=toggleterm<CR>",
desc = "Toggle terminal",
mode = "n",
},
{
key = "<leader>tt",
cmd = ":ToggleTerm name=toggleterm<CR>",
desc = "Toggle terminal",
mode = "n",
},
{
key = "<leader>tT",
cmd = ":ToggleTerm name=toggleterm-full direction=tab<CR>",
desc = "Toggle full terminal",
mode = "n",
},
{
key = "<leader>ot",
cmd = ":ToggleTerm name=toggleterm<CR>",
desc = "Open terminal",
mode = "n",
},
{
key = "<leader>oT",
cmd = ":ToggleTerm name=toggleterm-full direction=tab<CR>",
desc = "Open full terminal",
mode = "n",
},
{
key = "<leader>ts",
cmd = ":TermSelect<CR>",
desc = "Select terminal",
mode = "n",
},
{
key = "<leader>tv",
cmd = ":ToggleTerm direction=vertical name=toggleterm-vert<CR>",
desc = "Toggle vertical terminal",
mode = "n",
},
{
key = "<leader>th",
cmd = ":ToggleTerm direction=horizontal name=toggleterm-hori<CR>",
desc = "Toggle horizontal terminal",
mode = "n",
},
{
key = "<leader>ov",
cmd = ":ToggleTerm direction=vertical name=toggleterm-vert<CR>",
desc = "Open vertical terminal",
mode = "n",
},
{
key = "<leader>oh",
cmd = ":ToggleTerm direction=horizontal name=toggleterm-hori<CR>",
desc = "Open horizontal terminal",
mode = "n",
},
{
key = "<leader>tf",
cmd = ":ToggleTerm name=toggleterm<CR>",
desc = "Toggle terminal",
mode = "n",
},
{
key = "<leader>-",
cmd = ":ToggleTerm direction='horizontal'<CR>",
desc = "Toggle horizontal terminal",
mode = "n",
},
{
key = "<leader>|",
cmd = ":ToggleTerm direction='vertical'<CR>",
desc = "Toggle vertical terminal",
mode = "n",
},
}
--}}}
-- {{{ LSP Mappings
local lsp_mappings = {
{ mode = "n", key = "gA", cmd = vim.lsp.buf.code_action, group = "Code Action" },
{ mode = "n", key = "gd", cmd = ":Telescope lsp_definitions<CR>", group = "LSP Definitions" },
{ mode = "n", key = "gDc", cmd = ":Telescope lsp_implementations<CR>", group = "LSP Implementations" },
{ mode = "n", key = "gDf", cmd = ":Telescope lsp_definitions<CR>", group = "LSP Definitions" },
{ mode = "n", key = "gF", cmd = ":edit <cfile><CR>", group = "Edit File" },
{ mode = "n", key = "gT", cmd = ":Telescope lsp_type_definitions<CR>", group = "LSP Type Definitions" },
{ mode = "n", key = "gb", cmd = ":Gitsigns blame_line<CR>", group = "Blame Line" },
{ mode = "n", key = "<leader>gb", cmd = ":Gitsigns blame<CR>", group = "Git Blame" },
{ mode = "n", key = "gi", cmd = ":Telescope lsp_implementations<CR>", group = "Telescope Implementations" },
{ mode = "n", key = "gj", cmd = ":Telescope jumplist<CR>", group = "Telescope Jumplist" },
{ mode = "n", key = "gr", cmd = ":Telescope lsp_references<CR>", group = "LSP References" },
{ mode = "n", key = "gs", cmd = vim.lsp.buf.signature_help },
-- { mode = "n", key = "K", cmd = vim.lsp.buf.hover },
{ mode = "n", key = "<leader>ca", cmd = vim.lsp.buf.code_action, group = "Code" },
{ mode = "n", key = "<leader>ch", cmd = ":lua vim.lsp.buf.signature_help()<CR>", group = "Signature Help" },
{ mode = "n", key = "<leader>cR", cmd = ":lua vim.lsp.buf.rename()<CR>", group = "Rename" },
{ mode = "n", key = "<leader>cr", cmd = ":Telescope lsp_references<CR>", group = "LSP References" },
{ mode = "n", key = "<leader>cs", cmd = ":Telescope lsp_document_symbols<CR>", group = "LSP Document Symbols" },
{ mode = "n", key = "<leader>ct", cmd = ":Telescope lsp_type_definitions<CR>", group = "LSP Definitions" },
{
mode = "n",
key = "<leader>cw",
cmd = ":Telescope lsp_dynamic_workspace_symbols<CR>",
group = "LSP Workspace Symbols",
},
{ mode = "n", key = "<leader>ci", cmd = ":Telescope lsp_implementations<CR>", group = "LSP Implementations" },
{ mode = "n", key = "<leader>cci", cmd = ":Telescope lsp_incoming_calls<CR>", group = "LSP Incoming Calls" },
{ mode = "n", key = "<leader>cco", cmd = ":Telescope lsp_outgoing_calls<CR>", group = "LSP Outgoing Calls" },
{
mode = "n",
key = "<leader>cd",
cmd = ":Telescope diagnostics theme=dropdown layout_config={width=0.8}<CR>",
group = "Telecope Diagnostics",
},
{
mode = "n",
key = "<leader>cDs",
cmd = ":Telescope diagnostics theme=dropdown layout_config={width=0.8}<CR>",
group = "Telecope Diagnostics",
},
{ mode = "n", key = "<leader>cDn", cmd = ":lua vim.diagnostic.goto_next()<CR>", group = "Goto Next Preview" },
{
mode = "n",
key = "<leader>cDp",
cmd = ":lua vim.diagnostic.goto_prev()<CR>",
group = "Goto Previous Preview",
},
{ mode = "n", key = "<leader>cl", cmd = ":lua vim.diagnostic.setloclist()<CR>", group = "Set Loclist" },
{ mode = "n", key = "<leader>Clr", cmd = ":LspRestart<CR>", group = "Restart LSP" },
{
mode = "n",
key = "<leader>cPs",
cmd = function()
vim.cmd("!pyright --createstub " .. vim.fn.expand("<cword>"))
end,
group = "Generate Stub File",
},
}
-- }}}
-- {{{ Code Companion Mappings
local code_companion_mappings = {
{ mode = "n", key = "<leader>cp", cmd = ":vert Copilot panel<CR>", group = "Copilot Panel" },
{
mode = "n",
key = "<leader>Cf",
cmd = function()
local chat = require("codecompanion").chat
chat({ window_opts = { height = 1.0, layout = "buffer" } })
end,
group = "Codecompanion Fullscreen",
},
{
mode = "n",
key = "<leader>Ch",
cmd = function()
local chat = require("codecompanion").chat
chat({ window_opts = { height = 0.24, layout = "horizontal", position = "bottom" } })
end,
group = "Codecompanion Horizontal Split",
},
{ mode = "n", key = "<leader>Cc", cmd = ":CodeCompanionChat Toggle<CR>", group = "Toggle Codecompanion" },
{ mode = "n", key = "<leader>oc", cmd = ":CodeCompanionChat Toggle<CR>", group = "Toggle Codecompanion" },
{
mode = "n",
key = "<leader>Ci",
cmd = function()
vim.api.nvim_feedkeys(":CodeCompanion #{buffer} ", "n", false)
end,
group = "Inline CodeCompanion",
opts = nosilent,
},
{ mode = "n", key = "<leader>Ct", cmd = ":CodeCompanionChat Toggle<CR>", group = "CodeCompanion Toggle" },
{ mode = "n", key = "<leader>CA", cmd = ":CodeCompanionActions<CR>", group = "CodeCompanion Actions" },
{ mode = "v", key = "<leader>Ca", cmd = ":CodeCompanionChat Add<CR>", group = "CodeCompanion Add" },
{
mode = "v",
key = "<leader>Ci",
cmd = function()
vim.api.nvim_feedkeys(":CodeCompanion #{buffer} ", "n", false)
end,
group = "CodeCompanion Inline",
opts = nosilent,
},
{ mode = "v", key = "<leader>Ce", cmd = ":CodeCompanion /explain<CR>", group = "CodeCompanion /explain" },
{ mode = "v", key = "<leader>Cf", cmd = ":CodeCompanion /fix<CR>", group = "CodeCompanion /fix" },
{ mode = "v", key = "<leader>Cl", cmd = ":CodeCompanion /lsp<CR>", group = "CodeCompanion /lsp" },
{ mode = "v", key = "<leader>Ct", cmd = ":CodeCompanion /tests<CR>", group = "CodeCompanion /tests" },
}
-- }}}
-- {{{ Telescope mappings
local telescope_mappings = {
{
mode = "n",
key = "//",
cmd = ":Telescope current_buffer_fuzzy_find previewer=false<CR>",
desc = "Current buffer fuzzy find",
},
{
mode = "n",
key = "??",
cmd = ":Telescope lsp_document_symbols theme=dropdown layout_config={width=0.5}<CR>",
group = "Lsp document symbols",
},
{
mode = "n",
key = "<leader>fc",
cmd = ':Telescope color_names theme=dropdown layout_config={width=0.45,height=25,prompt_position="bottom"} layout_strategy=vertical<CR>',
group = "Telescope color names",
},
{
mode = "n",
key = "<leader>Tc",
cmd = ":Telescope colorscheme<CR>",
group = "Telescope colorscheme",
},
{
mode = "n",
key = "<leader>TC",
cmd = ':Telescope color_names theme=dropdown layout_config={width=0.45,height=25,prompt_position="bottom"} layout_strategy=vertical<CR>',
group = "Telescope color names",
},
{
mode = "n",
key = "<leader>Tn",
cmd = ":Telescope notify theme=dropdown layout_config={width=0.75}<CR>",
group = "Telescope notify",
},
{
mode = "n",
key = "<leader>TN",
cmd = ":Telescope noice theme=dropdown layout_config={width=0.75}<CR>",
group = "Telescope Noice",
},
{
mode = "n",
key = "<leader>Ti",
cmd = function()
telescope_paste_img()
end,
desc = "Find and Paste Image",
},
{
mode = "n",
key = "<leader>ff",
cmd = ":Telescope find_files find_command=rg,--ignore,--follow,--hidden,--files prompt_prefix=🔍<CR>",
group = "Find files",
},
{ mode = "n", key = "<leader>fg", cmd = ":Telescope live_grep<CR>", group = "Live Grep" },
{
mode = "n",
key = "<leader>Tg",
cmd = ':Telescope glyph theme=dropdown layout_config={width=0.45,height=35,prompt_position="bottom"} layout_strategy=vertical<CR>',
group = "Telescope Glyph",
},
{
mode = "n",
key = "<leader>fG",
cmd = ':Telescope glyph theme=dropdown layout_config={width=0.45,height=35,prompt_position="bottom"} layout_strategy=vertical<CR>',
group = "Glhph",
},
{ mode = "n", key = "<leader>fb", cmd = ":Telescope file_browser<CR>", group = "File browser" },
{
mode = "n",
key = "<leader>fr",
cmd = ":Telescope oldfiles theme=dropdown layout_config={width=0.5}<CR>",
group = "Oldfiles",
},
{
mode = "n",
key = "<leader>hc",
cmd = ":Telescope commands<CR>",
group = "Commands",
},
{
mode = "n",
key = "<leader>hv",
cmd = ":Telescope vim_options<CR>",
group = "Vim options",
},
{
mode = "n",
key = "<leader>hk",
cmd = ":Telescope keymaps<CR>",
group = "Keymaps",
},
{
mode = "n",
key = "<leader>hs",
cmd = ":Telescope spell_suggest<CR>",
group = "Spell suggest",
},
{
mode = "n",
key = "<leader>ht",
cmd = ":Telescope help_tags<CR>",
group = "Help tags",
},
{
mode = "n",
key = "<leader>hm",
cmd = ":Telescope man_pages theme=dropdown layout_config={width=0.75}<CR>",
group = "Man pages",
},
{
mode = "n",
key = "<leader>sf",
cmd = ":Telescope find_files find_command=rg,--ignore,--follow,--hidden,--files prompt_prefix=🔍<CR>",
group = "Search files",
},
{ mode = "n", key = "<leader>sF", cmd = ":Telescope fidget<CR>", group = "Fidget" },
{ mode = "n", key = "<leader>sg", cmd = ":Telescope live_grep<CR>", group = "Live grep" },
{ mode = "n", key = "<leader>sh", cmd = ":Telescope command_history<CR>", group = "Command history" },
{
mode = "n",
key = "<leader>sn",
cmd = function()
Snacks.win({
file = vim.api.nvim_get_runtime_file("doc/news.txt", false)[1],
width = 0.6,
height = 0.6,
wo = {
spell = false,
wrap = false,
signcolumn = "yes",
statuscolumn = " ",
conceallevel = 3,
},
})
end,
group = "News",
},
{ mode = "n", key = "<leader>sm", cmd = ":Telescope man_pages<CR>", group = "Man pages" },
{ mode = "n", key = "<leader>s/", cmd = ":Telescope search_history<CR>", group = "Search history" },
{ mode = "n", key = "<leader>gc", cmd = ":Telescope git_commits<CR>", group = "Git commits" },
{ mode = "n", key = "<leader>gf", cmd = ":Telescope git_files<CR>", group = "Git files" },
{ mode = "n", key = "<leader>Tr", cmd = ":Telescope reloader<CR>", group = "Telescope reloader" },
}
--}}}
-- {{{ File Explorer Mappings (i guess)
local file_explorer_mappings = {
{ mode = "n", key = "<leader>nt", cmd = ":NvimTreeToggle<CR>" },
{ mode = "n", key = "<leader>nc", cmd = ":lua Snacks.notifier.hide()<CR>" },
{ mode = "n", key = "<leader>nh", cmd = ":lua Snacks.notifier.show_history()<CR>" },
}
-- }}}
-- {{{ Misc Utilities Mappings
local misc_utilities_mappings = {
{ mode = "n", key = "<leader>x", cmd = "<cmd>!chmod +x %<CR>", group = "Make Executable" },
{ mode = "n", key = "<leader>y", cmd = '"+y', group = "System Yank" },
{ mode = "v", key = "<leader>y", cmd = '"+y', group = "System Yank" },
{ mode = "n", key = "<leader>sc", cmd = ":nohls<CR>", group = "Search" },
{
mode = "n",
key = "<leader>m",
cmd = function()
mkdir_under_cursor()
end,
group = "mkdir under cursor",
},
{
mode = "v",
key = "<leader>m",
cmd = function()
mkdir_under_cursor()
end,
group = "mkdir selection",
},
}
-- }}}
-- {{{ Goto Preview Mappings
local goto_preview_mappings = {
{ mode = "n", key = "gpc", cmd = ':lua require("goto-preview").close_all_win()<CR>', group = "Goto Preview" },
{
mode = "n",
key = "gpd",
cmd = ':lua require("goto-preview").goto_preview_definition()<CR>',
group = "Goto Preview",
},
{
mode = "n",
key = "gpi",
cmd = ':lua require("goto-preview").goto_preview_implementation()<CR>',
group = "Goto Preview",
},
}
-- }}}
-- {{{ Workspace Management Mappings
local workspace_management_mappings = {
{ mode = "n", key = "<leader>wa", cmd = vim.lsp.buf.add_workspace_folder },
{ mode = "n", key = "<leader>wr", cmd = vim.lsp.buf.remove_workspace_folder },
{
mode = "n",
key = "<leader>wl",
cmd = function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end,
},
}
-- }}}
-- {{{ Noice Mappings
local noice_mappings = {
{ mode = "n", key = "<leader>Nh", cmd = ":Noice telescope<CR>", group = "Noice" },
{ mode = "n", key = "<leader>Nl", cmd = ":Noice last<CR>", group = "Noice" },
{ mode = "n", key = "<leader>Nd", cmd = ":Noice dismiss<CR>", group = "Noice" },
{ mode = "n", key = "<leader>Ne", cmd = ":Noice errors<CR>", group = "Noice" },
{ mode = "n", key = "<leader>Ns", cmd = ":Noice stats<CR>", group = "Noice" },
}
-- }}}
-- {{{ ODIS Mappings
local odis_mappings = {
{
mode = "n",
key = "<leader>dv",
cmd = ':lua require("odis").show_documentation("vsplit")<CR>',
group = "Vertical split",
},
{ mode = "n", key = "<leader>dh", cmd = ':lua require("odis").show_documentation("split")<CR>', group = "Split" },
{ mode = "n", key = "<leader>db", cmd = ':lua require("odis").show_documentation("buffer")<CR>', group = "Buffer" },
{ mode = "n", key = "<leader>dt", cmd = ':lua require("odis").show_documentation("tab")<CR>', group = "Tab" },
{ mode = "n", key = "<leader>df", cmd = ':lua require("odis").show_documentation("float")<CR>', group = "Float" },
}
-- }}}
-- {{{ Diffview Mappings
local diffview_mappings = {
{
mode = "n",
key = "<leader>gdo",
cmd = ":DiffviewOpen<CR>",
group = "DiffviewOpen",
},
{
mode = "n",
key = "<leader>gdf",
cmd = ":DiffviewFileHistory %<CR>",
group = "Git",
},
{
mode = "n",
key = "<leader>gdh",
cmd = ":DiffviewHistory<CR>",
group = "Git",
},
{
mode = "n",
key = "<leader>gdc",
cmd = ":DiffviewClose<CR>",
group = "Git",
},
{
mode = "n",
key = "<leader>gdt",
cmd = ":DiffviewToggleFiles<CR>",
group = "Git",
desc = "Toggle files view",
},
{
mode = "n",
key = "<leader>gdr",
cmd = ":DiffviewRefresh<CR>",
desc = "Refresh diffview",
group = "Git",
},
{
mode = "n",
key = "<leader>gg",
cmd = ":lua Snacks.lazygit()<CR>",
desc = "Open Lazygit",
},
}
-- }}}
--{{{ Custom Terminals
local programs_map = {
op = { cmd = "ipython", display_name = "ipython", direction = "vertical", hidden = true },
oP = {
cmd = "ipython",
display_name = "ipython-full",
direction = "tab",
hidden = true,
},
oi = { cmd = "sudo iotop", display_name = "iotop", direction = "tab", hidden = true },
on = { cmd = "rmpc", display_name = "rmpc", direction = "tab", hidden = true },
oN = { cmd = "nvtop", display_name = "nvtop", direction = "tab", hidden = true },
ob = { cmd = "/usr/bin/btop", display_name = "btop", direction = "tab", hidden = true },
od = { cmd = "lazydocker", display_name = "lazydocker", direction = "tab", hidden = true },
}
local temp
local tbl = {}
for key, value in pairs(programs_map) do
temp = {
cmd = function()
term_toggle(term_factory(value))
end,
key = "<leader>" .. key,
group = value.group,
mode = "n",
desc = "Open " .. value.display_name,
}
table.insert(tbl, temp)
end
add_to_whichkey(tbl, { key = "<leader>o", group = "Open" })
function _G.set_terminal_keymaps()
local opts = { buffer = 0 }
map("t", "<esc>", [[<C-\><C-n>]], opts)
map("t", "<C-w>", [[<C-\><C-n><C-w>]], opts)
end
vim.cmd("autocmd! TermOpen term://* lua set_terminal_keymaps()")
--}}}
-- {{{ IMAGE
local image_mappings = {
{
mode = "n",
key = "<leader>id",
cmd = ":lua require('image').disable()<CR>",
desc = "Disable image rendering",
},
{
mode = "n",
key = "<leader>ie",
cmd = ":lua require('image').enable()<CR>",
desc = "Enable image rendering",
},
{
mode = "n",
key = "<leader>pi",
cmd = function()
telescope_paste_img()
end,
desc = "Find and Paste Image",
},
}
-- }}}
--{{{ Groups
add_to_whichkey(nil, { key = "<leader>a", group = "AnyJump" })
add_to_whichkey(nil, { key = "<leader>b", group = "Buffers" })
add_to_whichkey(nil, { key = "<leader>c", group = "Code" })
add_to_whichkey(nil, { key = "<leader>ca", group = "Code Actions" })
add_to_whichkey(nil, { key = "<leader>cc", group = "Calls" })
add_to_whichkey(nil, { key = "<leader>C", group = "CodeCompanion" })
add_to_whichkey(nil, { key = "<leader>cL", group = "LSP" })
add_to_whichkey(nil, { key = "<leader>d", group = "ODIS" })
add_to_whichkey(nil, { key = "<leader>f", group = "Find" })
add_to_whichkey(nil, { key = "<leader>g", group = "Git" })
add_to_whichkey(nil, { key = "<leader>gd", group = "DiffView" })
add_to_whichkey(nil, { key = "<leader>h", group = "Help" })
add_to_whichkey(nil, { key = "<leader>i", group = "Image" })
add_to_whichkey(nil, { key = "<leader>j", group = "AnyJump" })
add_to_whichkey(nil, { key = "<leader>N", group = "Noice" })
-- add_to_whichkey(nil, { key = "<leader>o", group = "Open" })
add_to_whichkey(nil, { key = "<leader>p", group = "Paste" })
add_to_whichkey(nil, { key = "<leader>pg", group = "Paste Git Raw" })
add_to_whichkey(nil, { key = "<leader>s", group = "Search" })
add_to_whichkey(nil, { key = "<leader>t", group = "Terminal" })
add_to_whichkey(nil, { key = "<leader>T", group = "Telescope" })
add_to_whichkey(nil, { key = "<leader>w", group = "Workspace" })
add_to_whichkey(nil, { key = "<leader>x", group = "Make Executable" })
add_to_whichkey(nil, { key = "<leader>y", group = "System Yank" })
add_to_whichkey(nil, { key = "<leader>0", group = "Horizontal Terminal" })
add_to_whichkey(nil, { key = "<leader>cP", group = "Python" })
--}}}
--{{{ Whichkey Mappings
local mappings_tables = {
basic_mappings,
buffer_navigation_mappings,
terminal_mappings,
lsp_mappings,
code_companion_mappings,
telescope_mappings,
file_explorer_mappings,
misc_utilities_mappings,
goto_preview_mappings,
workspace_management_mappings,
noice_mappings,
odis_mappings,
diffview_mappings,
image_mappings,
}
for _, mapping in ipairs(mappings_tables) do
add_to_whichkey(map_from_table(mapping))
end
add_to_whichkey(nil, { key = "<leader>dc", group = "Close" })
--}}}
@@ -0,0 +1,21 @@
local M = {}
function M.setup()
vim.api.nvim_create_user_command("Config", "edit ~/.config/nvim", { desc = "Edit Neovim configuration" })
vim.api.nvim_create_user_command(
"Keymaps",
"edit ~/.config/nvim/lua/core/keymaps/init.lua",
{ desc = "Edit keymaps" }
)
vim.api.nvim_create_user_command(
"Hypr",
"edit ~/.config/hypr/hyprland.conf",
{ desc = "Edit Hyprland configuration" }
)
vim.keymap.set("", "<Leader>tl", function()
vim.diagnostic.enable(not vim.diagnostic.is_enabled())
end, { desc = "Toggle diagnostics" })
end
return M
+38
View File
@@ -0,0 +1,38 @@
local M = {}
local map = vim.keymap.set
function M.setup()
map("n", "<C-u>", "<C-u>zz", { desc = "Scroll up and center" })
map("n", "n", "nzzzv", { desc = "Next search result and center" })
map("n", "N", "Nzzzv", { desc = "Previous search result and center" })
map("x", "<leader>pp", '"_dP', { desc = "Paste without yanking" })
map("v", "<", "<gv", { desc = "Reselect after indent" })
map("v", ">", ">gv", { desc = "Reselect after indent" })
map("v", "J", ":m '>+1<CR>gv=gv", { desc = "Move line down" })
map("v", "K", ":m '<-2<CR>gv=gv", { desc = "Move line up" })
map("n", "<C-J>", "<cmd>bnext<cr>", { desc = "Next buffer" })
map("n", "<C-K>", "<cmd>bprevious<cr>", { desc = "Previous buffer" })
map("n", "<leader>bk", "<cmd>bdelete<cr>", { desc = "Delete buffer" })
map("n", "<leader>bn", "<cmd>bnext<cr>", { desc = "Next buffer" })
map("n", "<leader>bp", "<cmd>bprevious<cr>", { desc = "Previous buffer" })
map("n", "<leader>x", "<cmd>!chmod +x %<cr>", { desc = "Make file executable" })
map({ "n", "v" }, "<leader>y", '"+y', { desc = "Yank to system clipboard" })
map("n", "<leader>sc", "<cmd>nohlsearch<cr>", { desc = "Clear search highlights" })
map({ "n", "v" }, "<leader>m", require("utils.functions.mkdir_under_cursor").mkdir_under_cursor, {
desc = "Create directory from text",
})
map("n", "<leader>pg", require("utils.functions.git_paste").git_paste_prompt, {
desc = "Paste content from Git raw URL",
})
map("n", "<leader>wa", vim.lsp.buf.add_workspace_folder, { desc = "Add workspace folder" })
map("n", "<leader>wr", vim.lsp.buf.remove_workspace_folder, { desc = "Remove workspace folder" })
map("n", "<leader>wl", function()
vim.print(vim.lsp.buf.list_workspace_folders())
end, { desc = "List workspace folders" })
end
return M
+33
View File
@@ -0,0 +1,33 @@
local M = {}
function M.setup()
require("which-key").add({
{ "<leader>a", group = "AnyJump" },
{ "<leader>b", group = "Buffers" },
{ "<leader>c", group = "Code" },
{ "<leader>ca", group = "Code actions" },
{ "<leader>cc", group = "Calls" },
{ "<leader>cL", group = "LSP" },
{ "<leader>cP", group = "Python" },
{ "<leader>C", group = "CodeCompanion" },
{ "<leader>f", group = "Find" },
{ "<leader>g", group = "Git" },
{ "<leader>gd", group = "Diffview" },
{ "<leader>h", group = "Help" },
{ "<leader>i", group = "Image" },
{ "<leader>j", group = "AnyJump" },
{ "<leader>n", group = "Navigation and notifications" },
{ "<leader>N", group = "Noice" },
{ "<leader>o", group = "Open" },
{ "<leader>p", group = "Paste" },
{ "<leader>pg", group = "Paste Git raw" },
{ "<leader>s", group = "Search" },
{ "<leader>t", group = "Terminal" },
{ "<leader>T", group = "Telescope" },
{ "<leader>w", group = "Workspace" },
{ "<leader>x", group = "Executable bit" },
{ "<leader>y", group = "System clipboard" },
})
end
return M
+4
View File
@@ -0,0 +1,4 @@
require("core.keymaps.commands").setup()
require("core.keymaps.editing").setup()
require("core.keymaps.lsp").setup()
require("core.keymaps.groups").setup()
+47
View File
@@ -0,0 +1,47 @@
local M = {}
local map = vim.keymap.set
function M.setup()
map("n", "gA", vim.lsp.buf.code_action, { desc = "Code action" })
map("n", "gd", "<cmd>Telescope lsp_definitions<cr>", { desc = "Definitions" })
map("n", "gDc", "<cmd>Telescope lsp_implementations<cr>", { desc = "Implementations" })
map("n", "gDf", "<cmd>Telescope lsp_definitions<cr>", { desc = "Definitions" })
map("n", "gF", "<cmd>edit <cfile><cr>", { desc = "Edit file under cursor" })
map("n", "gT", "<cmd>Telescope lsp_type_definitions<cr>", { desc = "Type definitions" })
map("n", "gb", "<cmd>Gitsigns blame_line<cr>", { desc = "Blame line" })
map("n", "<leader>gb", "<cmd>Gitsigns blame<cr>", { desc = "Git blame" })
map("n", "gi", "<cmd>Telescope lsp_implementations<cr>", { desc = "Implementations" })
map("n", "gj", "<cmd>Telescope jumplist<cr>", { desc = "Jumplist" })
map("n", "gr", "<cmd>Telescope lsp_references<cr>", { desc = "References" })
map("n", "gs", vim.lsp.buf.signature_help, { desc = "Signature help" })
map("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "Code action" })
map("n", "<leader>ch", vim.lsp.buf.signature_help, { desc = "Signature help" })
map("n", "<leader>cR", vim.lsp.buf.rename, { desc = "Rename" })
map("n", "<leader>cr", "<cmd>Telescope lsp_references<cr>", { desc = "References" })
map("n", "<leader>cs", "<cmd>Telescope lsp_document_symbols<cr>", { desc = "Document symbols" })
map("n", "<leader>ct", "<cmd>Telescope lsp_type_definitions<cr>", { desc = "Type definitions" })
map("n", "<leader>cw", "<cmd>Telescope lsp_dynamic_workspace_symbols<cr>", { desc = "Workspace symbols" })
map("n", "<leader>ci", "<cmd>Telescope lsp_implementations<cr>", { desc = "Implementations" })
map("n", "<leader>cci", "<cmd>Telescope lsp_incoming_calls<cr>", { desc = "Incoming calls" })
map("n", "<leader>cco", "<cmd>Telescope lsp_outgoing_calls<cr>", { desc = "Outgoing calls" })
map("n", "<leader>cd", "<cmd>Telescope diagnostics theme=dropdown layout_config={width=0.8}<cr>", {
desc = "Diagnostics",
})
map("n", "<leader>cDs", "<cmd>Telescope diagnostics theme=dropdown layout_config={width=0.8}<cr>", {
desc = "Diagnostics",
})
map("n", "<leader>cDn", function()
vim.diagnostic.jump({ count = 1, float = true })
end, { desc = "Next diagnostic" })
map("n", "<leader>cDp", function()
vim.diagnostic.jump({ count = -1, float = true })
end, { desc = "Previous diagnostic" })
map("n", "<leader>cl", vim.diagnostic.setloclist, { desc = "Diagnostics to location list" })
map("n", "<leader>Clr", "<cmd>LspRestart<cr>", { desc = "Restart LSP" })
map("n", "<leader>cPs", function()
vim.cmd("!pyright --createstub " .. vim.fn.expand("<cword>"))
end, { desc = "Generate Python stub" })
end
return M
+12 -39
View File
@@ -1,60 +1,33 @@
-- Bootstrap lazy.nvim -- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then if not (vim.uv or vim.loop).fs_stat(lazypath) then
local lazyrepo = "https://github.com/folke/lazy.nvim.git" local lazyrepo = "https://github.com/folke/lazy.nvim.git"
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
if vim.v.shell_error ~= 0 then if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({ vim.api.nvim_echo({
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" }, { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
{ out, "WarningMsg" }, { out, "WarningMsg" },
{ "\nPress any key to exit..." }, { "\nPress any key to exit..." },
}, true, {}) }, true, {})
vim.fn.getchar() vim.fn.getchar()
os.exit(1) os.exit(1)
end end
end end
vim.opt.rtp:prepend(lazypath) vim.opt.rtp:prepend(lazypath)
require("core.options") require("core.options")
-- Setup lazy.nvim
require("lazy").setup({ require("lazy").setup({
spec = { spec = {
-- import your plugins
{ import = "plugins" }, { import = "plugins" },
}, },
-- Configure any other settings here. See the documentation for more details.
-- colorscheme that will be used when installing plugins.
install = { colorscheme = { "habamax" } }, install = { colorscheme = { "habamax" } },
-- automatically check for plugin updates
checker = { enabled = true }, checker = { enabled = true },
dev = {
-- Directory where you store your local plugin projects. If a function is used,
-- the plugin directory (e.g. `~/projects/plugin-name`) must be returned.
---@type string | fun(plugin: LazyPlugin): string
path = "~/.config/nvim/test",
---@type string[] plugins that match these patterns will use your local versions instead of being fetched from GitHub
patterns = {}, -- For example {"folke"}
fallback = false, -- Fallback to git when local plugin doesn't exist
},
-- lazy can generate helptags from the headings in markdown readme files,
-- so :help works even for plugins that don't have vim docs.
-- when the readme opens with :help it will be correctly displayed as markdown
readme = { readme = {
enabled = true, enabled = true,
root = vim.fn.stdpath("state") .. "/lazy/readme", root = vim.fn.stdpath("state") .. "/lazy/readme",
files = { "README.md", "lua/**/README.md" }, files = { "README.md", "lua/**/README.md" },
-- only generate markdown helptags for plugins that don't have docs
skip_if_doc_exists = true, skip_if_doc_exists = true,
}, },
state = vim.fn.stdpath("state") .. "/lazy/state.json", -- state info for checker and other things state = vim.fn.stdpath("state") .. "/lazy/state.json",
-- Enable profiling of lazy.nvim. This will add some overhead,
-- so only enable this when you are debugging lazy.nvim
profiling = {
-- Enables extra stats on the debug tab related to the loader cache.
-- Additionally gathers stats about all package.loaders
loader = false,
-- Track each new require in the Lazy profiling tab
require = false,
},
}) })
-107
View File
@@ -1,107 +0,0 @@
-- Utility functions shared between progress reports for LSP and DAP
vim.notify = require("notify")
local client_notifs = {}
local function get_notif_data(client_id, token)
if not client_notifs[client_id] then
client_notifs[client_id] = {}
end
if not client_notifs[client_id][token] then
client_notifs[client_id][token] = {}
end
return client_notifs[client_id][token]
end
local spinner_frames = { "", "", "", "", "", "", "", "" }
local function update_spinner(client_id, token, title)
local notif_data = get_notif_data(client_id, token)
if notif_data.spinner then
local new_spinner = (notif_data.spinner + 1) % #spinner_frames
notif_data.spinner = new_spinner
notif_data.notification = vim.notify("", nil, {
hide_from_history = true,
icon = spinner_frames[new_spinner],
replace = notif_data.notification,
title = title,
})
vim.defer_fn(function()
update_spinner(client_id, token, title)
end, 100)
end
end
local function format_title(title, client_name)
return client_name .. (title and #title > 0 and ": " .. title or "")
end
local function format_message(message, percentage)
return (percentage and percentage .. "%\t" or "") .. (message or "")
end
-- LSP integration
-- Make sure to also have the snippet with the common helper functions in your config!
vim.lsp.handlers["$/progress"] = function(_, result, ctx)
local client_id = ctx.client_id
local val = result.value
if not val.kind then
return
end
local notif_data = get_notif_data(client_id, result.token)
if val.kind == "begin" then
local message = format_message(val.message, val.percentage)
notif_data.notification = vim.notify(message, "info", {
title = format_title("", vim.lsp.get_client_by_id(client_id).name),
icon = spinner_frames[1],
timeout = false,
hide_from_history = false,
})
notif_data.spinner = 1
update_spinner(client_id, result.token, val.title)
elseif val.kind == "report" and notif_data then
notif_data.notification = vim.notify(format_message(val.message, val.percentage), "info", {
replace = notif_data.notification,
title = format_title("", vim.lsp.get_client_by_id(client_id).name),
hide_from_history = false,
})
elseif val.kind == "end" and notif_data then
notif_data.notification = vim.notify(val.message and format_message(val.message) or "Complete", "info", {
icon = "",
replace = notif_data.notification,
title = format_title("", vim.lsp.get_client_by_id(client_id).name),
timeout = 3000,
})
notif_data.spinner = nil
end
end
vim.lsp.handlers["window/showMessage"] = function(err, result, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
local lvl = ({
"ERROR",
"WARN",
"INFO",
"DEBUG",
})[result.type]
vim.notify("LSP Message: " .. result.message, lvl, {
title = client.name,
timeout = 5000,
keep = function()
return lvl == "ERROR" or lvl == "WARN"
end,
})
end
+1 -15
View File
@@ -1,7 +1,5 @@
local g = vim.g local g = vim.g
local o = vim.o local o = vim.o
local A = vim.api
local l = vim.lsp
g.mapleader = " " g.mapleader = " "
g.maplocalleader = "," g.maplocalleader = ","
@@ -11,14 +9,12 @@ o.showmode = false
o.termguicolors = true o.termguicolors = true
o.background = "dark" o.background = "dark"
o.mouse = "a" o.mouse = "a"
o.syntax = "on"
o.laststatus = 3 o.laststatus = 3
o.number = true o.number = true
o.relativenumber = true o.relativenumber = true
o.colorcolumn = "88" o.colorcolumn = "88"
o.textwidth = 80 o.textwidth = 80
o.shiftwidth = 4 o.shiftwidth = 4
o.expandtab = true
o.tabstop = 4 o.tabstop = 4
o.autoindent = true o.autoindent = true
o.ignorecase = true o.ignorecase = true
@@ -37,29 +33,19 @@ o.wildignore = ".git,.hg,.svn,CVS,.DS_Store,.idea,.vscode,.vscode-test,node_modu
o.showmatch = true o.showmatch = true
o.list = true o.list = true
o.listchars = "tab:»·,trail:·,nbsp:·,extends:>,precedes:<" o.listchars = "tab:»·,trail:·,nbsp:·,extends:>,precedes:<"
o.encoding = "utf-8"
o.guifont = "JetBrainsMono Nerd Font 14" o.guifont = "JetBrainsMono Nerd Font 14"
o.expandtab = true o.expandtab = true
o.hidden = true
o.cmdheight = 1 o.cmdheight = 1
o.updatetime = 300 o.updatetime = 300
o.timeoutlen = 500 o.timeoutlen = 500
o.pumwidth = 35 o.pumwidth = 35
o.foldmethod = "marker" o.foldmethod = "marker"
o.conceallevel = 1 o.conceallevel = 1
g.db_ui_use_nerd_fonts = 1
-- vim.cmd.colorscheme = 'catppuccin-macchiato'
vim.cmd.colorscheme = "catppuccin"
-- set border for floating windows and signature help
-- UNSUPPPORTED: https://github.com/neovim/neovim/issues/32242#issuecomment-2777120640
-- l.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = border })
-- l.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = border })
o.winborder = "rounded" o.winborder = "rounded"
vim.diagnostic.config({ vim.diagnostic.config({
virtual_text = false, virtual_text = false,
virtual_lines = { current_line = true },
signs = true, signs = true,
underline = true, underline = true,
severity_sort = true, severity_sort = true,
@@ -1,137 +0,0 @@
return {
"sontungexpt/better-diagnostic-virtual-text",
-- event = "LspAttach",
enabled = true,
config = function()
local diagnostic = require("better-diagnostic-virtual-text")
local vt = require("better-diagnostic-virtual-text.api")
local tbl_insert = table.insert
local strdisplaywidth = vim.fn.strdisplaywidth
local space = function(n)
return string.rep(" ", n)
end
local SEVERITY_SUFFIXS = {
[vim.diagnostic.severity.ERROR] = "Error",
[vim.diagnostic.severity.WARN] = "Warn",
[vim.diagnostic.severity.INFO] = "Info",
[vim.diagnostic.severity.HINT] = "Hint",
}
vt.format_line_chunks = function(
ui_opts,
line_idx,
line_msg,
severity,
max_line_length,
lasted_line,
virt_text_offset,
should_display_below,
above_instead,
removed_parts,
diagnostic
)
local chunks = {}
local first_line = line_idx == 1
local severity_suffix = SEVERITY_SUFFIXS[severity]
local msg = string.format("[%s]: %s", diagnostic.code, line_msg)
local function hls(extend_hl_groups)
local default_groups = {
"DiagnosticVirtualText" .. severity_suffix,
"BetterDiagnosticVirtualText" .. severity_suffix,
}
if extend_hl_groups then
for i, hl in ipairs(extend_hl_groups) do
default_groups[2 + i] = hl
end
end
return default_groups
end
local message_highlight = hls()
if should_display_below then
local arrow_symbol = (above_instead and ui_opts.down_arrow or ui_opts.up_arrow):match("^%s*(.*)")
local space_offset = space(virt_text_offset)
if first_line then
if not removed_parts.arrow then
tbl_insert(chunks, {
space_offset .. arrow_symbol,
hls({
"BetterDiagnosticVirtualTextArrow",
"BetterDiagnosticVirtualTextArrow" .. severity_suffix,
}),
})
end
else
tbl_insert(chunks, {
space_offset .. space(strdisplaywidth(arrow_symbol)),
message_highlight,
})
end
else
local arrow_symbol = ui_opts.arrow
if first_line then
if not removed_parts.arrow then
tbl_insert(chunks, {
arrow_symbol,
hls({
"BetterDiagnosticVirtualTextArrow",
"BetterDiagnosticVirtualTextArrow" .. severity_suffix,
}),
})
end
else
tbl_insert(chunks, {
space(virt_text_offset + strdisplaywidth(arrow_symbol)),
message_highlight,
})
end
end
if not removed_parts.left_kept_space then
local tree_symbol = " "
if first_line then
if not lasted_line then
tree_symbol = above_instead and "" or ""
end
elseif lasted_line then
tree_symbol = above_instead and "" or ""
else
tree_symbol = ""
end
tbl_insert(chunks, {
tree_symbol,
hls({ "BetterDiagnosticVirtualTextTree", "BetterDiagnosticVirtualTextTree" .. severity_suffix }),
})
end
tbl_insert(chunks, {
msg,
message_highlight,
})
if not removed_parts.right_kept_space then
local last_space = space(max_line_length - strdisplaywidth(msg) + ui_opts.right_kept_space)
tbl_insert(chunks, { last_space, message_highlight })
end
return chunks
end
diagnostic.setup({
ui = {
wrap_line_after = 150, -- wrap the line after this length to avoid the virtual text is too long
left_kept_space = 3, --- the number of spaces kept on the left side of the virtual text, make sure it enough to custom for each line
right_kept_space = 3, --- the number of spaces kept on the right side of the virtual text, make sure it enough to custom for each line
arrow = "",
up_arrow = "",
down_arrow = "",
above = false, -- the virtual text will be displayed above the line
},
priority = 10000, -- the priority of virtual text
inline = true,
})
end,
}
+2 -2
View File
@@ -7,7 +7,7 @@ return {
-- numbers = function(opts) -- numbers = function(opts)
-- return string.format("%s", opts.id) -- return string.format("%s", opts.id)
-- end, -- end,
numbers = function(opts) numbers = function(_opts)
return "" return ""
end, end,
-- number_style = "superscript" | "subscript" | "" | { "none", "subscript" }, -- buffer_id at index 1, ordinal at index 2 -- number_style = "superscript" | "subscript" | "" | { "none", "subscript" }, -- buffer_id at index 1, ordinal at index 2
@@ -45,7 +45,7 @@ return {
-- diagnostics = false | "nvim_lsp" | "coc", -- diagnostics = false | "nvim_lsp" | "coc",
diagnostics = "nvim_lsp", diagnostics = "nvim_lsp",
diagnostics_update_in_insert = false, diagnostics_update_in_insert = false,
diagnostics_indicator = function(count, level, diagnostics_dict, context) diagnostics_indicator = function(_count, _level, diagnostics_dict, _context)
local s = " " local s = " "
for e, n in pairs(diagnostics_dict) do for e, n in pairs(diagnostics_dict) do
local sym = e == "error" and "" local sym = e == "error" and ""
+1 -7
View File
@@ -41,7 +41,6 @@ return {
integrations = { integrations = {
cmp = true, cmp = true,
gitsigns = true, gitsigns = true,
nvimtree = true,
mini = { mini = {
enabled = true, enabled = true,
indentscope_color = "", indentscope_color = "",
@@ -51,11 +50,7 @@ return {
diffview = true, diffview = true,
fidget = true, fidget = true,
noice = true, noice = true,
indent_blankline = { snacks = true,
enabled = true,
scope_color = "lavendar", -- catppuccin color (eg. `lavender`) Default: text
colored_indent_levels = true,
},
copilot_vim = true, copilot_vim = true,
native_lsp = { native_lsp = {
enabled = true, enabled = true,
@@ -77,7 +72,6 @@ return {
background = true, background = true,
}, },
}, },
notify = true,
treesitter = true, treesitter = true,
rainbow_delimiters = true, rainbow_delimiters = true,
render_markdown = true, render_markdown = true,
+49 -15
View File
@@ -6,6 +6,42 @@ return {
"j-hui/fidget.nvim", "j-hui/fidget.nvim",
"ravitemer/mcphub.nvim", "ravitemer/mcphub.nvim",
}, },
keys = {
{
"<leader>Cf",
function()
require("codecompanion").chat({ window_opts = { height = 1, layout = "buffer" } })
end,
desc = "Fullscreen chat",
},
{
"<leader>Ch",
function()
require("codecompanion").chat({
window_opts = { height = 0.24, layout = "horizontal", position = "bottom" },
})
end,
desc = "Horizontal chat",
},
{ "<leader>Cc", "<cmd>CodeCompanionChat Toggle<cr>", desc = "Toggle chat" },
{ "<leader>oc", "<cmd>CodeCompanionChat Toggle<cr>", desc = "Toggle chat" },
{
"<leader>Ci",
function()
vim.api.nvim_feedkeys(":CodeCompanion #{buffer} ", "n", false)
end,
mode = { "n", "v" },
desc = "Inline prompt",
silent = false,
},
{ "<leader>Ct", "<cmd>CodeCompanionChat Toggle<cr>", desc = "Toggle chat" },
{ "<leader>CA", "<cmd>CodeCompanionActions<cr>", desc = "Actions" },
{ "<leader>Ca", "<cmd>CodeCompanionChat Add<cr>", mode = "v", desc = "Add selection to chat" },
{ "<leader>Ce", "<cmd>CodeCompanion /explain<cr>", mode = "v", desc = "Explain selection" },
{ "<leader>Cf", "<cmd>CodeCompanion /fix<cr>", mode = "v", desc = "Fix selection" },
{ "<leader>Cl", "<cmd>CodeCompanion /lsp<cr>", mode = "v", desc = "Explain LSP diagnostics" },
{ "<leader>Ct", "<cmd>CodeCompanion /tests<cr>", mode = "v", desc = "Generate tests" },
},
opts = { opts = {
adapters = { adapters = {
-- {{{ HTTP -- {{{ HTTP
@@ -177,7 +213,7 @@ return {
---@param adapter CodeCompanion.Adapter ---@param adapter CodeCompanion.Adapter
---@param context table ---@param context table
---@return string ---@return string
prompt_decorator = function(message, adapter, context) prompt_decorator = function(message, _adapter, _context)
return string.format([[<prompt>%s</prompt>]], message) return string.format([[<prompt>%s</prompt>]], message)
end, end,
completion_provider = "cmp", completion_provider = "cmp",
@@ -202,7 +238,7 @@ return {
action_palette = { action_palette = {
provider = "telescope", provider = "telescope",
width = 75, width = 75,
heigth = 45, height = 45,
}, },
chat = { chat = {
layout = "vertical", layout = "vertical",
@@ -291,7 +327,7 @@ return {
---@param tokens number ---@param tokens number
---@param adapter CodeCompanion.Adapter ---@param adapter CodeCompanion.Adapter
---@return string ---@return string
token_count = function(tokens, adapter) token_count = function(tokens, _adapter)
return " (" .. tokens .. " tokens)" return " (" .. tokens .. " tokens)"
end, end,
}, },
@@ -300,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,
}, },
}, },
}, },
@@ -329,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,
}, },
@@ -343,7 +377,7 @@ return {
}, },
}, },
init = function() init = function()
require("utils.codecompanion.fidget-spinner"):init() require("utils.codecompanion.fidget-spinner").init()
require("utils.codecompanion.extmarks").setup() require("utils.codecompanion.extmarks").setup()
end, end,
} }
+8 -10
View File
@@ -1,18 +1,13 @@
return { return {
"stevearc/conform.nvim", "stevearc/conform.nvim",
opts = { opts = {
on_init = function(client)
require("conform").formatters.shfmt = {
append_args = { "-i", "0", "-ci", "-sr" },
}
end,
formatters_by_ft = { formatters_by_ft = {
python = function(bufnr) python = function(bufnr)
if require("conform").get_formatter_info("ruff_format", bufnr).available then if require("conform").get_formatter_info("ruff_format", bufnr).available then
return { return {
"ruff_fix", "ruff_fix",
"ruff_format",
"ruff_organize_imports", "ruff_organize_imports",
"ruff_format",
} }
else else
return { "isort", "black" } return { "isort", "black" }
@@ -25,14 +20,17 @@ return {
javascriptreact = { "prettier" }, javascriptreact = { "prettier" },
typescript = { "prettier" }, typescript = { "prettier" },
typescriptreact = { "prettier" }, typescriptreact = { "prettier" },
md = { "markdownlint" }, markdown = { "markdownlint" },
["*"] = { "codespell" },
["_"] = { "trim_whitespace" }, ["_"] = { "trim_whitespace" },
}, },
formatters = {
shfmt = {
append_args = { "-i", "0", "-ci", "-sr" },
},
},
format_on_save = { format_on_save = {
-- These options will be passed to conform.format()
timeout_ms = 500, timeout_ms = 500,
-- lsp_format = "fallback", lsp_format = "never",
}, },
}, },
} }
@@ -1,3 +0,0 @@
return {
'AndreM222/copilot-lualine'
}
+3
View File
@@ -2,6 +2,9 @@ return {
"zbirenbaum/copilot.lua", "zbirenbaum/copilot.lua",
cmd = "Copilot", cmd = "Copilot",
event = "InsertEnter", event = "InsertEnter",
keys = {
{ "<leader>cp", "<cmd>vertical Copilot panel<cr>", desc = "Copilot panel" },
},
opts = { opts = {
panel = { panel = {
enabled = true, enabled = true,
+22 -15
View File
@@ -1,6 +1,14 @@
return { return {
"sindrets/diffview.nvim", "sindrets/diffview.nvim",
dependencies = "nvim-tree/nvim-web-devicons", dependencies = "nvim-tree/nvim-web-devicons",
keys = {
{ "<leader>gdc", "<cmd>DiffviewClose<cr>", desc = "Close Diffview" },
{ "<leader>gdf", "<cmd>DiffviewFileHistory %<cr>", desc = "File history" },
{ "<leader>gdh", "<cmd>DiffviewHistory<cr>", desc = "Repository history" },
{ "<leader>gdo", "<cmd>DiffviewOpen<cr>", desc = "Open Diffview" },
{ "<leader>gdr", "<cmd>DiffviewRefresh<cr>", desc = "Refresh Diffview" },
{ "<leader>gdt", "<cmd>DiffviewToggleFiles<cr>", desc = "Toggle files" },
},
opts = { opts = {
view = { view = {
-- Disable the default normal mode mapping for `<tab>`: -- Disable the default normal mode mapping for `<tab>`:
@@ -8,20 +16,19 @@ return {
-- Disable the default visual mode mapping for `gf`: -- Disable the default visual mode mapping for `gf`:
-- { "x", "gf", false }, -- { "x", "gf", false },
}, },
}, hooks = {
hooks = { diff_buf_read = function()
diff_buf_read = function(bufnr) vim.opt_local.wrap = false
-- Change local options in diff buffers vim.opt_local.list = false
vim.opt_local.wrap = false vim.opt_local.colorcolumn = { 80 }
vim.opt_local.list = false end,
vim.opt_local.colorcolumn = { 80 } view_opened = function(view)
end, vim.notify(
view_opened = function(view) ("A new %s was opened on tab page %d!"):format(view.class:name(), view.tabpage),
require("notify").notify( vim.log.levels.INFO,
("A new %s was opened on tab page %d!"):format(view.class:name(), view.tabpage), { timeout = 5000, title = "Diffview" }
"info", )
{ timeout = 5000, title = "Diffview" } end,
) },
end,
}, },
} }
-19
View File
@@ -42,8 +42,6 @@ return {
icon_style = "Question", -- Highlight group for group icons icon_style = "Question", -- Highlight group for group icons
priority = 30, -- Ordering priority for LSP notification group priority = 30, -- Ordering priority for LSP notification group
skip_history = true, -- Whether progress notifications should be omitted from history skip_history = true, -- Whether progress notifications should be omitted from history
-- How to format a progress message
format_message = require("fidget.progress.display").default_format_message,
-- How to format a progress annotation -- How to format a progress annotation
format_annote = function(msg) format_annote = function(msg)
return msg.title return msg.title
@@ -71,15 +69,6 @@ return {
filter = vim.log.levels.INFO, -- Minimum notifications level filter = vim.log.levels.INFO, -- Minimum notifications level
history_size = 128, -- Number of removed messages to retain in history history_size = 128, -- Number of removed messages to retain in history
override_vim_notify = false, -- Automatically override vim.notify() with Fidget override_vim_notify = false, -- Automatically override vim.notify() with Fidget
-- How to configure notification groups when instantiated
configs = { default = require("fidget.notification").default_config },
-- Conditionally redirect notifications to another backend
redirect = function(msg, level, opts)
if opts and opts.on_open then
return require("fidget.integration.nvim-notify").delegate(msg, level, opts)
end
end,
-- Options related to how notifications are rendered as text -- Options related to how notifications are rendered as text
view = { view = {
stack_upwards = true, -- Display notification items from bottom to top stack_upwards = true, -- Display notification items from bottom to top
@@ -87,14 +76,6 @@ return {
group_separator = "---", -- Separator between notification groups group_separator = "---", -- Separator between notification groups
}, },
}, },
integration = {
["nvim-tree"] = {
enable = true, -- Integrate with nvim-tree/nvim-tree.lua (if installed)
},
["xcodebuild-nvim"] = {
enable = false, -- Integrate with wojciech-kulik/xcodebuild.nvim (if installed)
},
},
-- Options related to logging -- Options related to logging
logger = { logger = {
level = vim.log.levels.WARN, -- Minimum logging level level = vim.log.levels.WARN, -- Minimum logging level
+26 -1
View File
@@ -1 +1,26 @@
return { "rmagatti/goto-preview" } return {
"rmagatti/goto-preview",
keys = {
{
"gpc",
function()
require("goto-preview").close_all_win()
end,
desc = "Close preview windows",
},
{
"gpd",
function()
require("goto-preview").goto_preview_definition()
end,
desc = "Preview definition",
},
{
"gpi",
function()
require("goto-preview").goto_preview_implementation()
end,
desc = "Preview implementation",
},
},
}
+16
View File
@@ -1,5 +1,21 @@
return { return {
"3rd/image.nvim", "3rd/image.nvim",
keys = {
{
"<leader>id",
function()
require("image").disable()
end,
desc = "Disable image rendering",
},
{
"<leader>ie",
function()
require("image").enable()
end,
desc = "Enable image rendering",
},
},
opts = { opts = {
backend = "kitty", backend = "kitty",
-- processor = "magick_rock", -- or "magick_cli" -- processor = "magick_rock", -- or "magick_cli"
+24
View File
@@ -1,6 +1,30 @@
local function find_and_paste_image()
local builtin = require("telescope.builtin")
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
builtin.find_files({
attach_mappings = function(_, map)
local function paste_image(prompt_bufnr)
local entry = action_state.get_selected_entry()
actions.close(prompt_bufnr)
require("img-clip").paste_image(nil, entry.path or entry[1])
end
map("i", "<CR>", paste_image)
map("n", "<CR>", paste_image)
return true
end,
})
end
return { return {
"HakonHarnes/img-clip.nvim", "HakonHarnes/img-clip.nvim",
event = "VeryLazy", event = "VeryLazy",
keys = {
{ "<leader>pi", find_and_paste_image, desc = "Find and paste image" },
{ "<leader>Ti", find_and_paste_image, desc = "Find and paste image" },
},
opts = { opts = {
default = { default = {
-- file and directory options -- file and directory options
+44 -94
View File
@@ -1,7 +1,7 @@
return { return {
"neovim/nvim-lspconfig", "neovim/nvim-lspconfig",
dependencies = { "hrsh7th/cmp-nvim-lsp" },
config = function() config = function()
vim.notify = require("notify")
local servers = { local servers = {
"bashls", "bashls",
"basedpyright", "basedpyright",
@@ -13,113 +13,63 @@ return {
"html", "html",
"cssls", "cssls",
"lua_ls", "lua_ls",
"eslint",
-- "ts_ls",
"vtsls", "vtsls",
"ansiblels", "ansiblels",
"docker_compose_language_service", "docker_compose_language_service",
"docker_language_server", "docker_language_server",
"golangci_lint_ls",
"gopls", "gopls",
"ruff",
} }
local capabilities = require("cmp_nvim_lsp").default_capabilities() local capabilities = require("cmp_nvim_lsp").default_capabilities()
for _, lsp in ipairs(servers) do vim.lsp.config("*", { capabilities = capabilities })
if lsp == "lua_ls" then
vim.lsp.config("lua_ls", {
capabilities = capabilities,
on_init = function(client)
if client.workspace_folders then
local path = client.workspace_folders[1].name
if
path ~= vim.fn.stdpath("config")
and (vim.uv.fs_stat(path .. "/.luarc.json") or vim.uv.fs_stat(path .. "/.luarc.jsonc"))
then
return
end
end
client.config.settings.Lua = vim.tbl_deep_extend("force", client.config.settings.Lua, { vim.lsp.config("lua_ls", {
runtime = { on_init = function(client)
-- Tell the language server which version of Lua you're using if client.workspace_folders then
-- (most likely LuaJIT in the case of Neovim) local path = client.workspace_folders[1].name
version = "LuaJIT", if
}, path ~= vim.fn.stdpath("config")
-- Make the server aware of Neovim runtime files and (vim.uv.fs_stat(path .. "/.luarc.json") or vim.uv.fs_stat(path .. "/.luarc.jsonc"))
workspace = { then
checkThirdParty = false, return
library = { end
vim.env.VIMRUNTIME, end
-- Depending on the usage, you might want to add additional paths here.
-- "${3rd}/luv/library", client.config.settings.Lua = vim.tbl_deep_extend("force", client.config.settings.Lua, {
-- "${3rd}/busted/library", runtime = {
"/usr/lib/lua-language-server/meta/3rd/busted/library", version = "LuaJIT",
}, },
-- or pull in all of 'runtimepath'. NOTE: this is a lot slower and will cause issues when working on your own configuration (see https://github.com/neovim/nvim-lspconfig/issues/3189) workspace = {
-- library = vim.api.nvim_get_runtime_file("", true) checkThirdParty = false,
}, library = {
}) vim.env.VIMRUNTIME,
end, "/usr/lib/lua-language-server/meta/3rd/busted/library",
settings = { },
Lua = {},
}, },
handlers = {},
root_dir = function(bufnr, on_dir)
if not vim.fn.bufname(bufnr):match("%.txt$") then
on_dir(vim.fn.getcwd())
end
end,
}) })
elseif lsp == "basedpyright" then end,
vim.lsp.config(lsp, { settings = { Lua = {} },
capabilities = capabilities, })
settings = {
basedpyright = { vim.lsp.config("basedpyright", {
analysis = { settings = {
autoSearchPaths = true, basedpyright = {
diagnosticMode = "openFilesOnly", analysis = {
useLibraryCodeForTypes = true, autoSearchPaths = true,
autoFormatStrings = true, diagnosticMode = "openFilesOnly",
}, autoFormatStrings = true,
diagnosticMode = "openFilesOnly", inlayHints = {
inlayHints = { callArgumentNames = true,
callArgumentNames = true, },
}, diagnosticSeverityOverrides = {
allowedUntypedLibraries = true,
reportMissingTypeStubs = true, reportMissingTypeStubs = true,
reportImportCycles = true, reportImportCycles = true,
reportUnusedImport = true, reportUnusedImport = true,
}, },
python = {
analysis = {
ignore = { "*" },
},
},
}, },
}) },
elseif lsp == "ruff" then },
vim.api.nvim_create_autocmd("LspAttach", { })
group = vim.api.nvim_create_augroup("lsp_attach_disable_ruff_hover", { clear = true }),
callback = function(args) vim.lsp.enable(servers)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client == nil then
return
end
if client.name == "ruff" then
-- Disable hover in favor of Pyright
client.server_capabilities.hoverProvider = false
end
end,
desc = "LSP: Disable hover capability from Ruff",
})
vim.lsp.config(lsp, {
settings = {
configuration = vim.fn.stdpath("config") .. "/lua/utils/ruff.toml",
logLevel = "info",
},
})
end
vim.lsp.enable(lsp)
end
end, end,
} }
+115 -90
View File
@@ -1,90 +1,115 @@
return { local mcphub_spinner_frames = { "", "", "", "", "", "", "", "", "", "" }
"nvim-lualine/lualine.nvim",
config = function() local function mcphub_status()
require("lualine").setup({ if not vim.g.loaded_mcphub then
options = { return "󰐻 -"
icons_enabled = true, end
theme = "catppuccin",
-- theme = 'dracula', local status = vim.g.mcphub_status or "stopped"
-- theme = 'horizon', if status == "stopped" then
-- theme = 'onedark', return "󰐻 -"
component_separators = { left = "", right = "" }, end
section_separators = { left = "", right = "" },
disabled_filetypes = {}, if vim.g.mcphub_executing or status == "starting" or status == "restarting" then
always_divide_middle = true, local frame = math.floor(vim.uv.now() / 100) % #mcphub_spinner_frames + 1
}, return "󰐻 " .. mcphub_spinner_frames[frame]
sections = { end
lualine_a = { "mode" },
lualine_b = { "branch", "diff" }, return "󰐻 " .. (vim.g.mcphub_servers_count or 0)
lualine_c = { "filename" }, end
lualine_x = {
{ local function mcphub_color()
"seachcount", if not vim.g.loaded_mcphub then
{ require("mcphub.extensions.lualine") }, return { fg = "#6c7086" }
"copilot", end
symbols = {
status = { local status = vim.g.mcphub_status or "stopped"
icons = { if status == "ready" or status == "restarted" then
enabled = "", return { fg = "#50fa7b" }
sleep = "", -- auto-trigger disabled elseif status == "starting" or status == "restarting" then
disabled = "", return { fg = "#ffb86c" }
warning = "", end
unknown = "",
}, return { fg = "#ff5555" }
hl = { end
enabled = "#50FA7B",
sleep = "#AEB7D0", return {
disabled = "#6272A4", "nvim-lualine/lualine.nvim",
warning = "#FFB86C", dependencies = {
unknown = "#FF5555", "AndreM222/copilot-lualine",
}, "nvim-tree/nvim-web-devicons",
}, },
spinners = "dots", -- has some premade spinners config = function()
spinner_color = "#6272A4", require("lualine").setup({
}, options = {
show_colors = true, -- theme = "catppuccin",
show_loading = true, theme = "auto",
}, component_separators = { left = "", right = "" },
{ section_separators = { left = "", right = "" },
"diagnostics", },
"fileformat", sections = {
symbols = { lualine_a = { "mode" },
unix = "", -- e712 lualine_b = { "branch", "diff" },
dos = "", -- e70f lualine_c = { "filename" },
mac = "", -- e711 lualine_x = {
}, "searchcount",
}, { mcphub_status, color = mcphub_color },
"encoding", {
"fileformat", "copilot",
{ "filetype", colored = true, icon_only = false }, symbols = {
}, status = {
lualine_y = { "progress" }, icons = {
lualine_z = { "location" }, disabled = "",
}, enabled = "",
inactive_sections = { sleep = "",
lualine_a = {}, unknown = "",
lualine_b = {}, warning = "",
lualine_c = { },
{ hl = {
"filename", disabled = "#6272A4",
file_status = true, -- Displays file status (readonly status, modified status) enabled = "#50FA7B",
path = 0, -- 0: Just the filename sleep = "#AEB7D0",
shorting_target = 40, -- Shortens path to leave 40 spaces in the window unknown = "#FF5555",
symbols = { warning = "#FFB86C",
modified = "[+]", -- Text to show when the file is modified. },
readonly = "[-]", -- Text to show when the file is non-modifiable or readonly. },
unnamed = "[No Name]", -- Text to show for unnamed buffers. spinners = "dots",
}, spinner_color = "#6272A4",
}, },
M, show_colors = true,
}, show_loading = true,
lualine_x = { "location" }, },
lualine_y = {}, "diagnostics",
lualine_z = {}, "encoding",
}, {
tabline = {}, "fileformat",
extensions = { "quickfix", "fzf", "nvim-tree", "symbols-outline", "fugitive", "toggleterm", "man" }, symbols = { dos = "", mac = "", unix = "" },
}) },
end, { "filetype", colored = true },
depends = { "kyazdani42/nvim-web-devicons" }, },
} lualine_y = { "progress" },
lualine_z = { "location" },
},
inactive_sections = {
lualine_a = {},
lualine_b = {},
lualine_c = {
{
"filename",
file_status = true,
path = 0,
symbols = {
modified = "[+]",
readonly = "[-]",
unnamed = "[No Name]",
},
},
},
lualine_x = { "location" },
lualine_y = {},
lualine_z = {},
},
extensions = { "man", "quickfix", "toggleterm" },
})
end,
}
-1
View File
@@ -1,6 +1,5 @@
return { return {
"echasnovski/mini.diff", "echasnovski/mini.diff",
depends = { "echasnovski/mini.nvim" },
config = function() config = function()
local diff = require("mini.diff") local diff = require("mini.diff")
diff.setup({ diff.setup({
+13 -10
View File
@@ -1,10 +1,17 @@
return { return {
"folke/noice.nvim", "folke/noice.nvim",
event = "VeryLazy", event = "VeryLazy",
keys = {
{ "<leader>Nd", "<cmd>Noice dismiss<cr>", desc = "Dismiss messages" },
{ "<leader>Ne", "<cmd>Noice errors<cr>", desc = "Errors" },
{ "<leader>Nh", "<cmd>Noice telescope<cr>", desc = "Message history" },
{ "<leader>Nl", "<cmd>Noice last<cr>", desc = "Last message" },
{ "<leader>Ns", "<cmd>Noice stats<cr>", desc = "Statistics" },
},
opts = { opts = {
lsp = { lsp = {
progress = { progress = {
enabled = true, enabled = false,
-- Lsp Progress is formatted using the builtins for lsp_progress. See config.format.builtin -- Lsp Progress is formatted using the builtins for lsp_progress. See config.format.builtin
-- See the section on formatting for more details on how to customize. -- See the section on formatting for more details on how to customize.
--- @type NoiceFormat|string --- @type NoiceFormat|string
@@ -99,10 +106,9 @@ return {
-- Noice can be used as `vim.notify` so you can route any notification like other messages -- Noice can be used as `vim.notify` so you can route any notification like other messages
-- Notification messages have their level and other properties set. -- Notification messages have their level and other properties set.
-- event is always "notify" and kind can be any log level as a string -- event is always "notify" and kind can be any log level as a string
-- The default routes will forward notifications to nvim-notify -- Keep command and message history available through Noice.
-- Benefit of using Noice for this is the routing and consistent history view -- Benefit of using Noice for this is the routing and consistent history view
enabled = true, enabled = false,
view = "notify",
}, },
documentation = { documentation = {
view = "hover", view = "hover",
@@ -118,7 +124,9 @@ return {
markdown = { markdown = {
hover = { hover = {
["|(%S-)|"] = vim.cmd.help, -- vim help links ["|(%S-)|"] = vim.cmd.help, -- vim help links
["%[.-%]%((%S-)%)"] = require("noice.util").open, -- markdown links ["%[.-%]%((%S-)%)"] = function(url)
require("noice.util").open(url)
end, -- markdown links
}, },
highlights = { highlights = {
["|%S-|"] = "@text.reference", ["|%S-|"] = "@text.reference",
@@ -131,11 +139,6 @@ return {
}, },
}, },
dependencies = { dependencies = {
-- if you lazy-load any plugin below, make sure to add proper `module="..."` entries
"MunifTanjim/nui.nvim", "MunifTanjim/nui.nvim",
-- OPTIONAL:
-- `nvim-notify` is only needed, if you want to use the notification view.
-- If not available, we use `mini` as the fallback
"rcarriga/nvim-notify",
}, },
} }
-75
View File
@@ -1,75 +0,0 @@
return {
"nvimtools/none-ls.nvim",
config = function()
local null_ls = require("null-ls")
local helpers = require("null-ls.helpers")
-- syncronous formatting
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
local sources = {
null_ls.builtins.completion.luasnip,
-- null_ls.builtins.diagnostics.mypy,
null_ls.builtins.diagnostics.pydoclint,
null_ls.builtins.diagnostics.markdownlint,
null_ls.builtins.formatting.black,
null_ls.builtins.formatting.isort,
null_ls.builtins.formatting.stylua,
null_ls.builtins.formatting.markdownlint,
null_ls.builtins.formatting.prettier, -- handled by lsp server
null_ls.builtins.formatting.shfmt.with({
filetypes = { "sh", "bash" },
extra_args = { "-i", "0", "-ci", "-sr" },
}),
null_ls.builtins.formatting.gofmt,
null_ls.builtins.formatting.goimports,
null_ls.builtins.formatting.goimports_reviser,
null_ls.builtins.hover.printenv,
}
require("null-ls").setup({
border = "rounded",
cmd = { "nvim" },
debounce = 250,
debug = false,
default_timeout = 5000,
diagnostic_config = {
virtual_text = false,
signs = true,
underline = true,
float = { border = "rounded", source = true },
severity_sort = true,
},
-- diagnostics_format = "#{m}",
diagnostics_format = "[#{c}] #{m} (#{s})",
fallback_severity = vim.diagnostic.severity.ERROR,
log_level = "warn",
notify_format = "[null-ls] %s",
on_init = nil,
on_exit = nil,
root_dir = require("null-ls.utils").root_pattern(".null-ls-root", "Makefile", ".git"),
root_dir_async = nil,
should_attach = nil,
sources = sources,
temp_dir = nil,
update_in_insert = false,
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({
async = false,
bufnr = bufnr,
filter = function(client)
return client.name == "null-ls"
end,
})
end,
})
end
end,
})
end,
}
-20
View File
@@ -1,20 +0,0 @@
return {
"rcarriga/nvim-notify",
opts = {
background_colour = "#000000",
fps = 144,
icons = {
DEBUG = "",
ERROR = "",
INFO = "",
TRACE = "",
WARN = "",
},
level = 2,
minimum_width = 50,
render = "default",
stages = "fade_in_slide_out",
timeout = 3000,
top_down = true,
},
}
+1 -1
View File
@@ -3,7 +3,7 @@ return {
event = "InsertEnter", event = "InsertEnter",
config = true, config = true,
opts = { opts = {
enabled = function(bufnr) enabled = function(_bufnr)
return true return true
end, end,
disable_filetype = { "TelescopePrompt", "spectre_panel" }, disable_filetype = { "TelescopePrompt", "spectre_panel" },
+4 -4
View File
@@ -22,7 +22,7 @@ return {
local cmp_autopairs = require("nvim-autopairs.completion.cmp") local cmp_autopairs = require("nvim-autopairs.completion.cmp")
local has_words_before = function() local has_words_before = function()
if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then if vim.api.nvim_get_option_value("buftype", { buf = 0 }) == "prompt" then
return false return false
end end
local line, col = unpack(vim.api.nvim_win_get_cursor(0)) local line, col = unpack(vim.api.nvim_win_get_cursor(0))
@@ -43,7 +43,7 @@ return {
cmp.setup.cmdline(":", { cmp.setup.cmdline(":", {
mapping = cmp.mapping.preset.cmdline(), mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({ { name = "path" } }, { sources = cmp.config.sources({ { name = "async_path" } }, {
{ name = "cmdline", option = { ignore_cmds = { "Man", "!" } } }, { name = "cmdline", option = { ignore_cmds = { "Man", "!" } } },
}), }),
matching = { disallow_symbol_nonprefix_matching = false }, matching = { disallow_symbol_nonprefix_matching = false },
@@ -51,7 +51,7 @@ return {
cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done()) cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done())
require("cmp").setup({ cmp.setup({
snippet = { snippet = {
expand = function(args) expand = function(args)
-- vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users. -- vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
@@ -180,7 +180,7 @@ return {
{ name = "render-markdown", group_index = 2 }, { name = "render-markdown", group_index = 2 },
{ {
name = "html-css", name = "html-css",
group_indx = 2, group_index = 2,
option = { option = {
enable_on = { "html", "jsx", "tsx", "typescript", "typescriptreact" }, -- html is enabled by default enable_on = { "html", "jsx", "tsx", "typescript", "typescriptreact" }, -- html is enabled by default
notify = false, notify = false,
+41 -27
View File
@@ -1,39 +1,53 @@
return { return {
"mfussenegger/nvim-lint", "mfussenegger/nvim-lint",
event = { "BufNewFile", "BufReadPre" },
config = function() config = function()
local lint = require("lint") local lint = require("lint")
local parser = require("lint.parser")
lint.linters.pydoclint = {
cmd = "pydoclint",
args = { "--show-filenames-in-every-violation-message=true", "-q" },
stdin = false,
stream = "stderr",
ignore_exitcode = true,
parser = parser.from_pattern("(.+):(%d+): (DOC%d+): (.+)", { "file", "lnum", "code", "message" }, nil, {
severity = vim.diagnostic.severity.WARN,
source = "pydoclint",
}),
}
local python_linters = { "ruff" }
if vim.fn.executable("pydoclint") == 1 then
table.insert(python_linters, "pydoclint")
end
lint.linters_by_ft = { lint.linters_by_ft = {
markdown = { "markdownlint" },
lua = { "luacheck" },
python = { "ruff" },
sh = { "shellcheck" },
json = { "jsonlint" },
yaml = { "yamllint" },
vim = { "vint" },
go = { "golangcilint" }, go = { "golangcilint" },
json = { "jsonlint" },
lua = { "luacheck" },
markdown = { "markdownlint" },
python = python_linters,
sh = { "shellcheck" },
typescript = { "eslint" }, typescript = { "eslint" },
typescriptreact = { "eslint" }, typescriptreact = { "eslint" },
vim = { "vint" },
yaml = { "yamllint" },
} }
lint.linters.jsonlint.cmd = "vscode-json-language-server"
lint.linters.shellcheck.args = {
"-s",
"bash",
"-o",
"all",
"-e",
"2250",
}
-- Save original function
local orig_try_lint = lint.try_lint
lint.try_lint = function(...) lint.linters.shellcheck.args = { "-s", "bash", "-o", "all", "-e", "2250" }
local opts = select(2, ...)
local bufnr = (type(opts) == "table" and opts.bufnr) or vim.api.nvim_get_current_buf() local lint_group = vim.api.nvim_create_augroup("LintOnSave", { clear = true })
if vim.api.nvim_get_option_value("buftype", { buf = bufnr }) ~= "" then vim.api.nvim_create_autocmd("BufWritePost", {
return group = lint_group,
end callback = function(args)
return orig_try_lint(...) if vim.bo[args.buf].buftype ~= "" then
end return
end
lint.try_lint(nil, { bufnr = args.buf })
if vim.fn.executable("codespell") == 1 then
lint.try_lint("codespell", { bufnr = args.buf })
end
end,
})
end, end,
event = { "BufReadPre", "BufNewFile" },
} }
-11
View File
@@ -1,11 +0,0 @@
return {
"nvim-tree/nvim-tree.lua",
version = "*",
lazy = false,
dependencies = {
"nvim-tree/nvim-web-devicons",
},
config = function()
require("nvim-tree").setup({})
end,
}
+64 -38
View File
@@ -2,6 +2,68 @@ return {
"folke/snacks.nvim", "folke/snacks.nvim",
priority = 1000, priority = 1000,
lazy = false, lazy = false,
keys = {
{
"<leader>fb",
function()
Snacks.explorer()
end,
desc = "File browser",
},
{
"<leader>gg",
function()
Snacks.lazygit()
end,
desc = "Lazygit",
},
{
"<leader>nc",
function()
Snacks.notifier.hide()
end,
desc = "Dismiss notifications",
},
{
"<leader>nh",
function()
Snacks.notifier.show_history()
end,
desc = "Notification history",
},
{
"<leader>nt",
function()
Snacks.explorer()
end,
desc = "File explorer",
},
{
"<leader>sn",
function()
Snacks.win({
file = vim.api.nvim_get_runtime_file("doc/news.txt", false)[1],
width = 0.6,
height = 0.6,
wo = {
spell = false,
wrap = false,
signcolumn = "yes",
statuscolumn = " ",
conceallevel = 3,
},
})
end,
desc = "Neovim news",
},
{
"<leader>Tn",
function()
Snacks.notifier.show_history()
end,
desc = "Notifications",
},
},
---@type snacks.Config ---@type snacks.Config
opts = { opts = {
-- your configuration comes here -- your configuration comes here
@@ -73,7 +135,7 @@ return {
debug = "", debug = "",
trace = "", trace = "",
}, },
keep = function(notif) keep = function()
return vim.fn.getcmdpos() > 0 return vim.fn.getcmdpos() > 0
end, end,
---@type snacks.notifier.style ---@type snacks.notifier.style
@@ -92,43 +154,7 @@ return {
scroll = { enabled = false }, scroll = { enabled = false },
statuscolumn = { enabled = false }, statuscolumn = { enabled = false },
words = { enabled = false }, words = { enabled = false },
terminal = { terminal = { enabled = false },
enabled = true,
bo = {
filetype = "snacks_terminal",
},
wo = {},
keys = {
q = "hide",
gf = function(self)
local f = vim.fn.findfile(vim.fn.expand("<cfile>"), "**")
if f == "" then
Snacks.notify.warn("No file under cursor")
else
self:hide()
vim.schedule(function()
vim.cmd("e " .. f)
end)
end
end,
term_normal = {
"<esc>",
function(self)
self.esc_timer = self.esc_timer or (vim.uv or vim.loop).new_timer()
if self.esc_timer:is_active() then
self.esc_timer:stop()
vim.cmd("stopinsert")
else
self.esc_timer:start(200, 0, function() end)
return "<esc>"
end
end,
mode = "t",
expr = true,
desc = "Double escape to normal mode",
},
},
},
win = { enabled = true }, win = { enabled = true },
styles = { styles = {
input = { input = {
+78 -15
View File
@@ -2,9 +2,7 @@ return {
"nvim-telescope/telescope.nvim", "nvim-telescope/telescope.nvim",
dependencies = { dependencies = {
"nvim-lua/plenary.nvim", "nvim-lua/plenary.nvim",
-- "jonarrien/telescope-cmdline.nvim",
"nat-418/telescope-color-names.nvim", "nat-418/telescope-color-names.nvim",
"nvim-telescope/telescope-file-browser.nvim",
"ghassan0/telescope-glyph.nvim", "ghassan0/telescope-glyph.nvim",
"nvim-telescope/telescope-ui-select.nvim", "nvim-telescope/telescope-ui-select.nvim",
{ {
@@ -13,6 +11,64 @@ return {
}, },
"folke/noice.nvim", "folke/noice.nvim",
}, },
cmd = "Telescope",
keys = {
{ "//", "<cmd>Telescope current_buffer_fuzzy_find previewer=false<cr>", desc = "Find in current buffer" },
{
"??",
"<cmd>Telescope lsp_document_symbols theme=dropdown layout_config={width=0.5}<cr>",
desc = "Document symbols",
},
{ "<leader>bb", "<cmd>Telescope buffers<cr>", desc = "Buffers" },
{
"<leader>fc",
'<cmd>Telescope color_names theme=dropdown layout_config={width=0.45,height=25,prompt_position="bottom"} layout_strategy=vertical<cr>',
desc = "Color names",
},
{
"<leader>ff",
"<cmd>Telescope find_files find_command=rg,--ignore,--follow,--hidden,--files prompt_prefix=🔍<cr>",
desc = "Find files",
},
{ "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Live grep" },
{
"<leader>fG",
'<cmd>Telescope glyph theme=dropdown layout_config={width=0.45,height=35,prompt_position="bottom"} layout_strategy=vertical<cr>',
desc = "Glyphs",
},
{ "<leader>fr", "<cmd>Telescope oldfiles theme=dropdown layout_config={width=0.5}<cr>", desc = "Recent files" },
{ "<leader>gc", "<cmd>Telescope git_commits<cr>", desc = "Git commits" },
{ "<leader>gf", "<cmd>Telescope git_files<cr>", desc = "Git files" },
{ "<leader>hc", "<cmd>Telescope commands<cr>", desc = "Commands" },
{ "<leader>hk", "<cmd>Telescope keymaps<cr>", desc = "Keymaps" },
{ "<leader>hm", "<cmd>Telescope man_pages theme=dropdown layout_config={width=0.75}<cr>", desc = "Man pages" },
{ "<leader>hs", "<cmd>Telescope spell_suggest<cr>", desc = "Spelling suggestions" },
{ "<leader>ht", "<cmd>Telescope help_tags<cr>", desc = "Help tags" },
{ "<leader>hv", "<cmd>Telescope vim_options<cr>", desc = "Neovim options" },
{ "<leader>s/", "<cmd>Telescope search_history<cr>", desc = "Search history" },
{ "<leader>sF", "<cmd>Telescope fidget<cr>", desc = "Fidget history" },
{
"<leader>sf",
"<cmd>Telescope find_files find_command=rg,--ignore,--follow,--hidden,--files prompt_prefix=🔍<cr>",
desc = "Search files",
},
{ "<leader>sg", "<cmd>Telescope live_grep<cr>", desc = "Live grep" },
{ "<leader>sh", "<cmd>Telescope command_history<cr>", desc = "Command history" },
{ "<leader>sm", "<cmd>Telescope man_pages<cr>", desc = "Man pages" },
{ "<leader>Tc", "<cmd>Telescope colorscheme<cr>", desc = "Colorschemes" },
{
"<leader>TC",
'<cmd>Telescope color_names theme=dropdown layout_config={width=0.45,height=25,prompt_position="bottom"} layout_strategy=vertical<cr>',
desc = "Color names",
},
{
"<leader>Tg",
'<cmd>Telescope glyph theme=dropdown layout_config={width=0.45,height=35,prompt_position="bottom"} layout_strategy=vertical<cr>',
desc = "Glyphs",
},
{ "<leader>TN", "<cmd>Telescope noice theme=dropdown layout_config={width=0.75}<cr>", desc = "Noice history" },
{ "<leader>Tr", "<cmd>Telescope reloader<cr>", desc = "Reload Lua module" },
},
opts = { opts = {
defaults = { defaults = {
-- Default configuration for telescope goes here: -- Default configuration for telescope goes here:
@@ -95,19 +151,6 @@ return {
enabled = true, enabled = true,
}, },
}, },
file_browser = {
theme = "ivy",
-- disables netrw and use telescope-file-browser in its place
hijack_netrw = true,
mappings = {
["i"] = {
-- your custom insert mode mappings
},
["n"] = {
-- your custom normal mode mappings
},
},
},
-- ["ui-select"] = { -- ["ui-select"] = {
-- require("telescope.themes").get_dropdown({ -- require("telescope.themes").get_dropdown({
-- winblend = 10, -- winblend = 10,
@@ -119,4 +162,24 @@ return {
-- }, -- },
}, },
}, },
config = function(_, opts)
local telescope = require("telescope")
local actions = require("telescope.actions")
local config = require("telescope.config")
local vimgrep_arguments = vim.deepcopy(config.values.vimgrep_arguments)
vim.list_extend(vimgrep_arguments, { "--hidden", "--glob", "!**/.git/*" })
opts.defaults.vimgrep_arguments = vimgrep_arguments
opts.defaults.mappings = vim.tbl_deep_extend("force", opts.defaults.mappings or {}, {
i = {
["<C-h>"] = actions.results_scrolling_left,
["<C-l>"] = actions.results_scrolling_right,
},
})
telescope.setup(opts)
for _, extension in ipairs({ "color_names", "fzf", "glyph", "noice", "ui-select" }) do
pcall(telescope.load_extension, extension)
end
end,
} }
+80 -1
View File
@@ -1,6 +1,52 @@
local terminals = {}
local function toggle(name)
return function()
terminals[name]:toggle()
end
end
return { return {
"akinsho/toggleterm.nvim", "akinsho/toggleterm.nvim",
version = "*", version = "*",
keys = {
{ "<C-T>", "<cmd>ToggleTerm name=toggleterm<cr>", desc = "Toggle terminal" },
{ "<leader>-", "<cmd>ToggleTerm direction=horizontal<cr>", desc = "Toggle horizontal terminal" },
{ "<leader>|", "<cmd>ToggleTerm direction=vertical<cr>", desc = "Toggle vertical terminal" },
{ "<leader>ob", toggle("btop"), desc = "Open btop" },
{ "<leader>od", toggle("lazydocker"), desc = "Open Lazydocker" },
{
"<leader>oh",
"<cmd>ToggleTerm direction=horizontal name=toggleterm-hori<cr>",
desc = "Open horizontal terminal",
},
{ "<leader>oi", toggle("iotop"), desc = "Open iotop" },
{ "<leader>on", toggle("rmpc"), desc = "Open rmpc" },
{ "<leader>oN", toggle("nvtop"), desc = "Open nvtop" },
{ "<leader>op", toggle("ipython"), desc = "Open IPython" },
{ "<leader>oP", toggle("ipython-full"), desc = "Open full IPython" },
{ "<leader>ot", "<cmd>ToggleTerm name=toggleterm<cr>", desc = "Open terminal" },
{ "<leader>oT", "<cmd>ToggleTerm name=toggleterm-full direction=tab<cr>", desc = "Open full terminal" },
{
"<leader>ov",
"<cmd>ToggleTerm direction=vertical name=toggleterm-vert<cr>",
desc = "Open vertical terminal",
},
{ "<leader>tf", "<cmd>ToggleTerm name=toggleterm<cr>", desc = "Toggle terminal" },
{
"<leader>th",
"<cmd>ToggleTerm direction=horizontal name=toggleterm-hori<cr>",
desc = "Toggle horizontal terminal",
},
{ "<leader>ts", "<cmd>TermSelect<cr>", desc = "Select terminal" },
{ "<leader>tt", "<cmd>ToggleTerm name=toggleterm<cr>", desc = "Toggle terminal" },
{ "<leader>tT", "<cmd>ToggleTerm name=toggleterm-full direction=tab<cr>", desc = "Toggle full terminal" },
{
"<leader>tv",
"<cmd>ToggleTerm direction=vertical name=toggleterm-vert<cr>",
desc = "Toggle vertical terminal",
},
},
opts = { opts = {
-- size can be a number or function which is passed the current terminal -- size can be a number or function which is passed the current terminal
size = function(term) size = function(term)
@@ -10,7 +56,6 @@ return {
return vim.o.columns * 0.45 return vim.o.columns * 0.45
end end
end, end,
open_mapping = { [[<c-t>]] }, -- or { [[<c-\>]], [[<c-¥>]] } if you also use a Japanese keyboard.
-- on_create = fun(t: Terminal), -- function to run when the terminal is first created -- on_create = fun(t: Terminal), -- function to run when the terminal is first created
-- on_open = fun(t: Terminal), -- function to run when the terminal opens -- on_open = fun(t: Terminal), -- function to run when the terminal opens
-- on_close = fun(t: Terminal), -- function to run when the terminal closes -- on_close = fun(t: Terminal), -- function to run when the terminal closes
@@ -82,4 +127,38 @@ return {
horizontal_breakpoint = 135, horizontal_breakpoint = 135,
}, },
}, },
config = function(_, opts)
require("toggleterm").setup(opts)
local Terminal = require("toggleterm.terminal").Terminal
local programs = {
btop = { cmd = "/usr/bin/btop", display_name = "btop", direction = "tab", hidden = true },
ipython = { cmd = "ipython", display_name = "ipython", direction = "vertical", hidden = true },
["ipython-full"] = { cmd = "ipython", display_name = "ipython-full", direction = "tab", hidden = true },
iotop = { cmd = "sudo iotop", display_name = "iotop", direction = "tab", hidden = true },
lazydocker = { cmd = "lazydocker", display_name = "lazydocker", direction = "tab", hidden = true },
nvtop = { cmd = "nvtop", display_name = "nvtop", direction = "tab", hidden = true },
rmpc = { cmd = "rmpc", display_name = "rmpc", direction = "tab", hidden = true },
}
for name, program in pairs(programs) do
program.on_stderr = function(_, job, data, process_name)
vim.notify(
("%s encountered an error on job %d\n%s"):format(process_name, job, table.concat(data, "\n")),
vim.log.levels.ERROR
)
end
terminals[name] = Terminal:new(program)
end
local terminal_keys = vim.api.nvim_create_augroup("TerminalKeys", { clear = true })
vim.api.nvim_create_autocmd("TermOpen", {
group = terminal_keys,
pattern = "term://*",
callback = function(args)
vim.keymap.set("t", "<esc>", [[<C-\><C-n>]], { buffer = args.buf })
vim.keymap.set("t", "<C-w>", [[<C-\><C-n><C-w>]], { buffer = args.buf })
end,
})
end,
} }
@@ -1 +0,0 @@
return { "tpope/vim-commentary" }
@@ -1,126 +0,0 @@
local notify = require("notify")
local spinner_frames = { "", "", "", "", "", "", "", "", "", "" }
local spinner_len = #spinner_frames -- cache spinner length
local M = {}
local timeout = 2999
-- Helper function to safely call notify
local function safe_notify(msg, level, opts)
local ok, res = pcall(notify, msg, level, opts)
return ok, res
end
function M:init()
local group = vim.api.nvim_create_augroup("CodeCompanionFidgetHooks", {})
vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionRequestStarted",
group = group,
callback = function(request)
local handle = M:create_progress_handle(request)
M:store_progress_handle(request.data.id, handle)
end,
})
vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionRequestFinished",
group = group,
callback = function(request)
local handle = M:pop_progress_handle(request.data.id)
if handle then
M:report_exit_status(handle, request)
handle:finish()
end
end,
})
end
M.handles = {}
function M:store_progress_handle(id, handle)
M.handles[id] = handle
end
function M:pop_progress_handle(id)
local handle = M.handles[id]
M.handles[id] = nil
return handle
end
function M:create_progress_handle(request)
local title = " Requesting assistance"
.. " ("
.. request.data.interaction
.. ") from "
.. request.data.adapter.formatted_name
.. " using "
.. request.data.adapter.model
local idx = 1
local start_time = os.time()
local notification_id =
notify(spinner_frames[idx] .. " In progress (" .. "0s" .. ")...", "info", { title = title, timeout = false })
local handle = { notification_id = notification_id, title = title, finished = false }
local timer = vim.loop.new_timer()
timer:start(
0,
100,
vim.schedule_wrap(function()
if handle.finished then
return
end -- stop updating if finished
idx = idx % spinner_len + 1
local elapsed = os.difftime(os.time(), start_time)
local opts = { replace = handle.notification_id, title = title, timeout = false }
local ok, new_id = safe_notify(spinner_frames[idx] .. " In progress (" .. elapsed .. "s)...", "info", opts)
if ok then
handle.notification_id = new_id
else
handle.notification_id = notify(
spinner_frames[idx] .. " In progress (" .. elapsed .. "s)...",
"info",
{ title = title, timeout = false }
)
end
end)
)
handle.timer = timer
handle.finish = function()
handle.finished = true -- mark as finished to abort future updates
if handle.timer then
handle.timer:stop()
handle.timer:close()
handle.timer = nil
end
end
return handle
end
function M:report_exit_status(handle, request)
local title = handle.title
or (
" Requesting assistance"
.. " ("
.. request.data.strategy
.. ") from "
.. request.data.adapter.formatted_name
.. " using "
.. request.data.adapter.model
)
local function report(msg, level)
local opts = { replace = handle.notification_id, title = title, timeout = timeout }
local ok = safe_notify(msg, level, opts)
if not ok then
notify(msg, level, { title = title, timeout = timeout })
end
end
if request.data.status == "success" then
report("Completed", "info")
elseif request.data.status == "error" then
report(" Error", "error")
else
report("󰜺 Cancelled", "warn")
end
end
return M
@@ -1,18 +1,16 @@
-- lua/plugins/codecompanion/fidget-spinner.lua
local progress = require("fidget.progress") local progress = require("fidget.progress")
local M = {} local M = {}
function M:init() function M.init()
local group = vim.api.nvim_create_augroup("CodeCompanionFidgetHooks", {}) local group = vim.api.nvim_create_augroup("CodeCompanionFidgetHooks", { clear = true })
vim.api.nvim_create_autocmd({ "User" }, { vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionRequestStarted", pattern = "CodeCompanionRequestStarted",
group = group, group = group,
callback = function(request) callback = function(request)
local handle = M:create_progress_handle(request) local handle = M.create_progress_handle(request)
M:store_progress_handle(request.data.id, handle) M.store_progress_handle(request.data.id, handle)
end, end,
}) })
@@ -20,9 +18,9 @@ function M:init()
pattern = "CodeCompanionRequestFinished", pattern = "CodeCompanionRequestFinished",
group = group, group = group,
callback = function(request) callback = function(request)
local handle = M:pop_progress_handle(request.data.id) local handle = M.pop_progress_handle(request.data.id)
if handle then if handle then
M:report_exit_status(handle, request) M.report_exit_status(handle, request)
handle:finish() handle:finish()
end end
end, end,
@@ -31,27 +29,28 @@ end
M.handles = {} M.handles = {}
function M:store_progress_handle(id, handle) function M.store_progress_handle(id, handle)
M.handles[id] = handle M.handles[id] = handle
end end
function M:pop_progress_handle(id) function M.pop_progress_handle(id)
local handle = M.handles[id] local handle = M.handles[id]
M.handles[id] = nil M.handles[id] = nil
return handle return handle
end end
function M:create_progress_handle(request) function M.create_progress_handle(request)
local adapter = request.data.adapter
return progress.handle.create({ return progress.handle.create({
title = " Requesting assistance (" .. request.data.adapter.model .. ")", title = " Requesting assistance (" .. (adapter.model or "default") .. ")",
message = "In progress...", message = "In progress...",
lsp_client = { lsp_client = {
name = M:llm_role_title(request.data.adapter.name), name = M.llm_role_title(adapter),
}, },
}) })
end end
function M:llm_role_title(adapter) function M.llm_role_title(adapter)
local parts = {} local parts = {}
table.insert(parts, adapter.formatted_name) table.insert(parts, adapter.formatted_name)
if adapter.model and adapter.model ~= "" then if adapter.model and adapter.model ~= "" then
@@ -60,7 +59,7 @@ function M:llm_role_title(adapter)
return table.concat(parts, " ") return table.concat(parts, " ")
end end
function M:report_exit_status(handle, request) function M.report_exit_status(handle, request)
if request.data.status == "success" then if request.data.status == "success" then
handle.message = "Completed" handle.message = "Completed"
elseif request.data.status == "error" then elseif request.data.status == "error" then
@@ -1,3 +0,0 @@
return {
require("utils.extensions.telescope"),
}
@@ -1,7 +0,0 @@
local ts = require("telescope")
-- ts.load_extension("fzf")
ts.load_extension("color_names")
ts.load_extension("file_browser")
ts.load_extension("glyph")
ts.load_extension("ui-select")
ts.load_extension("noice")
@@ -42,16 +42,4 @@ function M.git_paste_prompt()
end) end)
end end
--- Sets up the git-paste module.
---
--- The module expects an optional configuration table:
--- { telescope_key = "<leader>pg" } (or any other keymap you prefer)
---
---@param opts table|nil
function M.setup(opts)
opts = opts or {}
local telescope_key = opts.telescope_key or "<leader>pg"
vim.keymap.set("n", telescope_key, M.git_paste_prompt, { desc = "Git Paste: paste content from git raw URL" })
end
return M return M
@@ -1,4 +0,0 @@
return {
require("utils.functions.git_paste"),
require("utils.functions.mkdir_under_cursor"),
}
@@ -1,5 +1,4 @@
local M = {} local M = {}
vim.notify = require("notify")
function M.mkdir_under_cursor() function M.mkdir_under_cursor()
local word local word
@@ -19,18 +18,16 @@ function M.mkdir_under_cursor()
-- Remove quotes if present -- Remove quotes if present
word = word:gsub("^[\"']", ""):gsub("[\"']$", "") word = word:gsub("^[\"']", ""):gsub("[\"']$", "")
-- Check if directory exists -- Check if directory exists
local stat = vim.loop.fs_stat(word) local stat = vim.uv.fs_stat(word)
if not stat then if not stat then
-- Create directory (recursive) if vim.fn.mkdir(word, "p") == 1 then
vim.loop.fs_mkdir(word, 493) -- 493 = 0755 in decimal vim.notify("Directory created: " .. word, vim.log.levels.INFO)
vim.notify("Directory created: " .. word, vim.log.levels.INFO) else
vim.notify("Failed to create directory: " .. word, vim.log.levels.ERROR)
end
else else
vim.notify("Directory already exists: " .. word, vim.log.levels.WARN) vim.notify("Directory already exists: " .. word, vim.log.levels.WARN)
end end
end end
function M.setup(opts)
return M.mkdir_under_cursor
end
return M return M
+6 -5
View File
@@ -1,5 +1,3 @@
vim.notify = require("notify")
local client_notifs = {} local client_notifs = {}
local function get_notif_data(client_id, token) local function get_notif_data(client_id, token)
@@ -20,7 +18,7 @@ local function update_spinner(client_id, token)
local notif_data = get_notif_data(client_id, token) local notif_data = get_notif_data(client_id, token)
if notif_data.spinner then if notif_data.spinner then
local new_spinner = (notif_data.spinner + 1) % #spinner_frames local new_spinner = (notif_data.spinner % #spinner_frames) + 1
notif_data.spinner = new_spinner notif_data.spinner = new_spinner
notif_data.notification = vim.notify("", nil, { notif_data.notification = vim.notify("", nil, {
@@ -42,10 +40,12 @@ end
local function format_message(message, percentage) local function format_message(message, percentage)
return (percentage and percentage .. "%\t" or "") .. (message or "") return (percentage and percentage .. "%\t" or "") .. (message or "")
end end
local hyprland_lsp = vim.api.nvim_create_augroup("HyprlandLsp", { clear = true })
vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, {
group = hyprland_lsp,
pattern = { "*.hl", "hypr*.conf" }, pattern = { "*.hl", "hypr*.conf" },
callback = function(event) callback = function()
-- print(string.format("starting hyprls for %s", vim.inspect(event)))
vim.lsp.start({ vim.lsp.start({
name = "hyprlang", name = "hyprlang",
cmd = { "hyprls" }, cmd = { "hyprls" },
@@ -94,6 +94,7 @@ vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, {
}) })
notif_data.spinner = nil notif_data.spinner = nil
client_notifs[client_id][result.token] = nil
end end
end, end,
}, },
@@ -1,19 +0,0 @@
local M = {}
local map = vim.keymap.set
local opts = { noremap = true, silent = false }
--- Set keybindings from a table of mappings.
--- @param bindings table A list of keybinding mappings.
--- Each mapping should be a table with the following keys:
--- - mode: string, the mode in which the keybinding applies (e.g., 'n', 'i', 'v').
--- - key: string, the key to bind.
--- - cmd: string, the command to execute when the key is pressed.
--- - opts: table, optional, additional options for the keybinding (default:
function M.set_keybindings(bindings)
for _, binding in ipairs(bindings) do
map(binding.mode, binding.key, binding.cmd, binding.opts or opts)
end
return bindings
end
return M
@@ -1,51 +0,0 @@
local M = {}
local whichkey = require("which-key")
vim.notify = require("notify")
---Helper function to add mappings to which-key
---@param mappings table List of mappings to add
---@param group table Group to add mappings to (optional)
---@return nil
---@usage addToWhichKey(mappings, group)
---@example addToWhichKey({{key = "n", cmd = "next", mode = "n", desc = "Next Line", group = "Navigation"}, {key = "t", group = "example"})
function M.addToWhichKey(mappings, group)
if group then
whichkey.add({ group.key, group = group.group })
end
if not mappings and not group then
vim.notify("Error: Mappings is nil", "error")
return
elseif not mappings and group then
return
end
local wk_mappings = {}
for _, mapping in ipairs(mappings) do
wk_mappings = {}
if not mapping.key or mapping.key == "" then
vim.notify("Error: Key is empty or nil", "error")
return
end
if not mapping.cmd or mapping.cmd == "" then
vim.notify("Error: Command is empty or nil for key: " .. mapping.key, "error")
return
end
if not mapping.mode or mapping.mode == "" then
vim.notify("Error: Mode is empty or nil for key: " .. mapping.key, "error")
return
end
wk_mappings[1] = mapping.key
wk_mappings[2] = mapping.cmd
wk_mappings.mode = mapping.mode or "n"
wk_mappings.desc = mapping.desc
if mapping.group then
wk_mappings.group = mapping.group
end
whichkey.add(wk_mappings)
end
end
return M
-4
View File
@@ -1,4 +0,0 @@
return {
require('utils.keymaps.converters.whichkey'),
require('utils.keymaps.converters.from_table'),
}
@@ -1,50 +0,0 @@
local telescope = require("telescope")
local telescopeConfig = require("telescope.config")
local actions = require("telescope.actions")
local M = {}
function M.find_and_paste_image()
local telescope = require("telescope.builtin")
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
telescope.find_files({
attach_mappings = function(_, map)
local function embed_image(prompt_bufnr)
local entry = action_state.get_selected_entry()
local filepath = entry[1]
actions.close(prompt_bufnr)
local img_clip = require("img-clip")
img_clip.paste_image(nil, filepath)
end
map("i", "<CR>", embed_image)
map("n", "<CR>", embed_image)
return true
end,
})
end
function M.setup()
-- Clone the default Telescope configuration
local vimgrep_arguments = { unpack(telescopeConfig.values.vimgrep_arguments) }
-- I want to search in hidden/dot files.
table.insert(vimgrep_arguments, "--hidden")
-- I don't want to search in the `.git` directory.
table.insert(vimgrep_arguments, "--glob")
table.insert(vimgrep_arguments, "!**/.git/*")
vim.tbl_deep_extend("force", telescopeConfig.values, {
mappings = {
i = {
["<C-h>"] = actions.results_scrolling_left,
["<C-l>"] = actions.results_scrolling_right,
},
},
})
end
return M
-6
View File
@@ -1,6 +0,0 @@
local M = {}
M.term_factory = require("utils.terminal.term_factory").term_factory
M.term_toggle = require("utils.terminal.toggle").term_toggle
return M
@@ -1,12 +0,0 @@
local Terminal = require("toggleterm.terminal").Terminal
local notify = require("notify")
local M = {}
function M.term_factory(cfg)
cfg["on_stderr"] = function(_, job, data, name)
notify(name .. " encountered an error on job: " .. job .. "\nData: " .. data, "error")
end
return Terminal:new(cfg)
end
return M
@@ -1,9 +0,0 @@
local M = {}
function M.term_toggle(term)
if term then
term:toggle()
end
end
return M
@@ -1,15 +0,0 @@
local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
parser_config.hyprlang = {
install_info = {
url = "~/github/tree-sitter-hyprlang", -- local path or git repo
files = { "src/parser.c" }, -- note that some parsers also require src/scanner.c or src/scanner.cc
-- optional entries:
branch = "main", -- default branch in case of git repo if different from master
generate_requires_npm = false, -- if stand-alone parser without npm dependencies
requires_generate_from_grammar = true, -- if folder contains pre-generated src/parser.c
},
filetype = "conf", -- if filetype does not match the parser name
}
vim.filetype.add({
pattern = { [".*/hypr/.*%.conf"] = "hyprlang" },
})
+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>
@@ -22,11 +22,14 @@ context.modules = [
capture.props = { capture.props = {
node.name = "capture.rnnoise_source" node.name = "capture.rnnoise_source"
node.passive = true node.passive = true
target.object = "alsa_input.pci-0000_0d_00.4.analog-stereo"
audio.rate = 48000 audio.rate = 48000
} }
playback.props = { playback.props = {
node.name = "rnnoise_source" node.name = "rnnoise_source"
node.description = "Noise-cancelled wired earbuds"
media.class = "Audio/Source" media.class = "Audio/Source"
priority.session = 3000
audio.rate = 48000 audio.rate = 48000
} }
audio.channels = 1 audio.channels = 1
File diff suppressed because it is too large Load Diff
@@ -1,16 +1,17 @@
# AirPods-specific Bluetooth policy override. # AirPods-specific Bluetooth policy override.
# Keep the device on A2DP/AAC and avoid restoring stale device state. # Keep the device on A2DP/AAC and restore its last volume.
wireplumber.settings = { wireplumber.settings = {
bluetooth.autoswitch-to-headset-profile = false
device.restore-profile = false device.restore-profile = false
device.restore-routes = false device.restore-routes = true
} }
monitor.bluez.rules = [ monitor.bluez.rules = [
{ {
matches = [ matches = [
{ {
device.name = "bluez_card.18_3F_70_4E_02_CC" device.description = "~AirPods.*"
} }
] ]
actions = { actions = {