Compare commits

..

No commits in common. "master" and "no-tree" have entirely different histories.

731 changed files with 27 additions and 6724 deletions

View File

@ -1,27 +0,0 @@
# Neovim Config - AI Agent Instructions
**All agent instructions are in `AGENTS.md` at the repository root.**
This file exists to satisfy GitHub Copilot's convention of looking for instructions in `.github/copilot-instructions.md`.
## Quick Reference
- **Primary instructions**: See `/home/ray/.config/nvim/AGENTS.md`
- **Migration plan**: See `MIGRATION_PLAN.md`
- **Decision log**: See `LOG.md`
- **Keymap reference**: See `README.md`
## Key Rules (Summary)
1. **Read `AGENTS.md` first** - it contains all coding rules, architecture decisions, and what NOT to reintroduce
2. **Update docs after changes**:
- Config changes → update `MIGRATION_PLAN.md`
- Decisions → update `LOG.md`
- Keymap changes → update `README.md`
3. **Modern Lua-first**: Use `vim.opt`, `vim.keymap.set`, `vim.api` - avoid Vimscript
4. **LSP (Neovim 0.11+)**: Use `vim.lsp.config()` + `vim.lsp.enable()`, NOT lspconfig `.setup()`
5. **Project config**: Native `exrc` + `secure`, no neoconf plugin
6. **No new plugins** without explicit user approval
For complete details, workflow requirements, architecture decisions, and troubleshooting, see `AGENTS.md`.

3
.gitignore vendored
View File

@ -4,6 +4,3 @@ undodir/*
.vimtmp/* .vimtmp/*
view/* view/*
tmpdir tmpdir
WORKSPACE_TEST/
EXTERNAL_TEST/
WORKSPACE_SIMPLE/

209
AGENTS.md
View File

@ -1,209 +0,0 @@
# AGENTS: Neovim Config Migration
Scope: applies to the entire `~/.config/nvim` directory tree.
This repository is being migrated to a modern, minimal Neovim setup driven by Lua and lazy.nvim. Follow these rules when making changes.
## Goals
- Modern Lua-first config; avoid Vimscript unless explicitly requested.
- Keep it minimal, fast, and maintainable.
- Base plugin management on `lazy.nvim` with modular plugin specs.
- Support Telescope navigation, netrw for file browsing/preview, Oil.nvim for file operations, LSP, Treesitter, Copilot, and sane UX defaults.
- Native project-local configuration via `exrc` + `secure` (no external config plugins).
## Do Not Reintroduce
- Custom Vimscript tabline/statusline, foldtext, UI hacks.
- Legacy autocommands toggling cursorline/column per window.
- CoC or CoC-specific config.
- Neo-tree (skipped in favor of netrw for navigation).
- Providers for ruby/perl/node (disable unless required by a plugin).
- Auto-reload vimrc-on-write templates.
- `cursorcolumn` and old folding logic not related to UFO.
- neoconf plugin (incompatible with Neovim 0.11+, use native exrc instead).
- Motion plugins like leap.nvim or flash.nvim (not needed).
- Markdown rendering plugins (skipped entirely).
## Repository Structure (target)
```
~/.config/nvim/
├── init.lua -- entrypoint; bootstraps lazy.nvim and loads Lua modules
└── lua/
├── settings.lua -- non-plugin options
├── keymaps.lua -- non-plugin keymaps
├── autocmds.lua -- non-plugin autocommands
├── abbreviations.lua -- insert-mode abbreviations
├── netrw-config.lua -- netrw configuration
├── utils.lua -- helpers (only if needed)
└── plugins/ -- one file per plugin or domain
├── telescope.lua
├── treesitter.lua
├── cmp.lua
├── lsp.lua
├── copilot.lua
├── oil.lua
├── ufo.lua
├── gitsigns.lua
├── conform.lua
├── nvim-lint.lua
├── mason.lua
├── mason-lspconfig.lua
├── mason-tool-installer.lua
├── comment.lua
├── surround.lua
├── autopairs.lua
└── indent-blankline.lua
├── after/
│ ├── ftplugin/
│ │ └── php.lua -- PHP-specific settings (includeexpr for gf)
│ └── queries/ -- Custom Treesitter queries
│ ├── css/
│ │ └── highlights.scm
│ └── html/
│ └── highlights.scm
├── templates/
│ └── template.sh -- Shell script template
└── legacy/ -- Archived Vimscript config (reference only)
```
## Coding Conventions
- Use Lua APIs: `vim.opt`, `vim.api.nvim_create_autocmd`, `vim.keymap.set`.
- No inline Vimscript or `vim.cmd` blocks unless strictly necessary.
- Keep plugin configuration self-contained in its plugin spec file.
- Prefer small, readable modules and minimal configuration over heavy customization.
- Keymaps: use `vim.keymap.set` with `{ silent = true, noremap = true }` defaults unless otherwise required.
- Autocommands: group via `vim.api.nvim_create_augroup` and create specific, narrowly scoped autocmds.
- Avoid global state; return tables from modules and plugin specs.
## Plugin Management (lazy.nvim)
- Each plugin lives in `lua/plugins/<name>.lua` and returns a spec table.
- Use `opts = {}` for default options and `config = function(_, opts) ... end` for setup.
- Do not install any plugin not listed without explicit user confirmation (proposal and rationale are welcome).
- Rejected: `folke/neoconf.nvim` (incompatible with Neovim 0.11+ vim.lsp.config API).
## Required Plugin Categories
- Core: `nvim-lspconfig`, `nvim-cmp`, `cmp-nvim-lsp`, `cmp-buffer`, `cmp-path`, `LuaSnip`.
- Navigation: `telescope.nvim` + `telescope-fzf-native.nvim`, `oil.nvim`.
- Treesitter: `nvim-treesitter`, `nvim-treesitter-textobjects`, `nvim-ts-autotag`.
- UX/Editing: `Comment.nvim`, `nvim-surround`, `nvim-autopairs`, `indent-blankline.nvim`, `nvim-ufo`, `undotree`.
- Git: `gitsigns.nvim`.
- Copilot: `copilot.lua`, `copilot-cmp`.
- Formatting/Linting: `conform.nvim`, `nvim-lint`.
- LSP Management: `mason.nvim`, `mason-lspconfig.nvim`, `mason-tool-installer.nvim`.
## Workflow Requirements to Preserve
- Netrw for visual context and project structure browsing (tree view, preview splits).
- Telescope for fuzzy finding (files, grep, buffers, LSP symbols).
- Oil.nvim for file manipulation (handles buffer sync on rename/move/delete).
- LSP for navigation (gd, gr, K, etc.).
- Heavy HTML/PHP/JS/Markdown usage (WordPress plugin dev) — prioritize these languages in LSP/Treesitter.
- Format-on-save with toggle capability.
- Project-local configuration via `.nvim.lua` files.
## Behaviours to Keep (modernized)
- Abbreviations: `adn→and`, `waht→what`, `tehn→then`, `functin→function`, `positin→position`.
- Templates: auto-populate `.sh` from template.
- Whitespace highlighting (use modern alternatives, e.g., `listchars`, plugins if needed).
- Persistent folds (prefer `nvim-ufo`).
- Spell & text: `spelllang=en_gb`, custom `listchars`, `showbreak`.
- Keyword characters: `iskeyword+=$` (for PHP/shell variables), `iskeyword+=-` (for CSS/HTML/config files).
## Current Status (Phase 10 Complete)
- ✅ Phase 1-2: Archive legacy, bootstrap lazy.nvim
- ✅ Phase 3: Core editing & LSP (native exrc + vim.lsp.config migration complete)
- ✅ Phase 4: Navigation (Telescope, netrw, Oil.nvim)
- ✅ Phase 5: Treesitter (parsers, textobjects, autotag)
- ✅ Phase 6: UX/Editing (Comment, surround, autopairs, indent guides, UFO, undotree)
- ✅ Phase 7: Git integration (Gitsigns)
- ✅ Phase 8: Copilot integration (copilot.lua + copilot-cmp)
- ✅ Phase 9: Formatting & Linting (conform.nvim, nvim-lint, Mason tool installer)
- ✅ Phase 10: Migrate kept behaviors (abbreviations, templates, custom Treesitter queries)
- ⏸️ Phase 11: Cleanup & validation (pending)
## Migration Notes
- Do not edit legacy Vimscript files except to extract settings to Lua. Keep them intact until migration completes.
- Introduce `init.lua` and bootstrap `lazy.nvim` before adding plugins.
- Disable unnecessary providers early: `vim.g.loaded_ruby_provider = 0`, etc.
- Keep startup fast; avoid unnecessary autoloading or heavy defaults.
- When cleaning, archive existing files into a `legacy/` folder within this config instead of deleting. Maintain a brief index if helpful.
## Validation
- Ensure Neovim starts without errors and with minimal startup time.
- Verify Telescope navigation, netrw browsing, Oil.nvim file operations, LSP basics, and completion.
- Keep plugin count tight; remove anything unused.
## Key Decisions & Architecture
### LSP Configuration
- **Modern API (Neovim 0.11+)**: Uses `vim.lsp.config()` + `vim.lsp.enable()`, NOT `require('lspconfig')[server].setup()`
- **Project-local config**: Native `exrc` + `secure` (no neoconf - incompatible with 0.11+)
- **Config format**: `.nvim.lua` files return `{ lsp = { [server_name] = { settings = {...} } } }`
- **Security**: `secure` mode prompts user to trust `.nvim.lua` files (one-time per file hash)
- **Settings loading**: `on_new_config` hook loads from actual `root_dir` (not cwd)
- **PHP**: intelephense with `single_file_support = false`, uses `environment.includePaths` for external libs
### Navigation Strategy
- **netrw**: Visual context, tree view, preview splits (horizontal 50/50 below)
- **Telescope**: Fuzzy finding (files, grep, buffers, LSP symbols)
- **Oil.nvim**: File manipulation (handles buffer sync on rename/move/delete)
- **PHP gf enhancement**: Custom `includeexpr` in `after/ftplugin/php.lua` for WordPress/PHP patterns
### Formatting & Linting
- **Formatters**: prettier, phpcbf, stylua, black (project-local → Mason → global)
- **Linters**: eslint_d, phpcs, markdownlint, ruff (via nvim-lint)
- **Format-on-save**: Enabled by default, toggle with `<leader>lt`
- **Philosophy**: Formatters are source of truth; Neovim settings match formatter rules per filetype
### Treesitter
- **Parsers**: lua, vim, vimdoc, php, html, javascript, typescript, tsx, css, scss, json, markdown, bash, regex
- **Features**: Incremental selection (CR/BS), textobjects (functions, classes, parameters), autotag
- **Custom queries**: `after/queries/css/highlights.scm`, `after/queries/html/highlights.scm`
### Session Management
- **Auto-load**: On `VimEnter`, loads `Session.vim` if it exists (unless files specified on command line)
- **Auto-save**: On `VimLeavePre`, saves to `Session.vim` if it already exists
- **Manual control**: Create with `:mksession` to enable; delete to disable
- **Per-directory**: Sessions are opt-in and workspace-specific
## Developer Workflows
### Testing Changes
```bash
# Quick syntax check
nvim --headless -c 'lua print("Config loads")' -c 'quitall'
# Check LSP status in test workspace
cd ~/.config/nvim/WORKSPACE_TEST
nvim site/external.php # Then :LspInfo
# Validate goto-definition for external libs
# In external.php, cursor on Util::greetExternal, press gd
```
### Validation Workspaces
- `WORKSPACE_TEST/`: PHP project with `.nvim.lua` and `.git`
- `EXTERNAL_TEST/`: External library referenced via includePaths
- Test that `gd` resolves symbols in EXTERNAL_TEST from WORKSPACE_TEST
## Common Issues
### lua_ls Won't Start
Mason-installed lua_ls may fail with missing `libbfd-2.38-system.so`. Install via system package manager instead.
### LSP Root Detection
If parent repo picked as root, create empty `.git` or `.nvimroot` marker in intended workspace root.
### Duplicate gd Results (Fixed)
Was caused by settings loading at wrong time; fixed by using `LspAttach` autocmd with actual `root_dir`.
## Process
- Before large changes, update the task plan via the CLI `update_plan` tool.
- Keep the live checklist in `MIGRATION_PLAN.md:1` up to date and in sync with changes.
- After any config or plugin change, immediately update `MIGRATION_PLAN.md` (check off items, add notes, or adjust next steps).
- After any change or decision, also update `LOG.md:1` (decisions log and design docs).
- **After adding/removing/editing any keymaps**, immediately update `README.md` with the change (add new entries, remove deleted mappings, or update descriptions).
- **After changing formatter/linter configuration, standards, or tool resolution**, immediately update the "Configuration" section in `README.md` (update tool lists, override instructions, or behavior descriptions).
- At the start of each phase, confirm scope and priorities for that phase.
- Execute phases via subphases (`N.x`), where each bullet under a phase is its own implement-and-test step (e.g., Phase 3.1, 3.2, 3.3, 3.4).
- Record decisions and rationale in `LOG.md:1` as they're made.
- Keep PR-sized patches; avoid broad unrelated edits.
- Document non-obvious choices in commit messages or short comments near the code that needs it.

View File

@ -1,72 +0,0 @@
# Migration Plan: none-ls → conform.nvim
**Date:** 2026-01-12
**Status:** ✅ **COMPLETED**
**Reason:** none-ls's phpcbf formatter is buggy (adds blank lines). conform.nvim is more modern, doesn't use LSP overhead, better maintained.
## Current State
**Formatting (conform.nvim):**
- ✅ prettier (JS/TS/CSS/JSON/HTML/Markdown)
- ✅ stylua (Lua)
- ✅ phpcbf (PHP) with WordPress standard
**Linting (nvim-lint):**
- phpcs, eslint_d, markdownlint
**LSP (lspconfig):**
- Language features (autocomplete, goto-def, hover, etc.)
## Migration Checklist
### Phase 1: Setup conform.nvim
- [x] Create `lua/plugins/conform.lua`
- [x] Configure formatters:
- [x] prettier (JS/TS/CSS/JSON/HTML/Markdown)
- [x] stylua (Lua)
- [x] phpcbf (PHP) with WordPress standard support
- [x] Configure format-on-save behavior
- [x] Set up project-local executable resolution (vendor/bin, node_modules/.bin, Mason, global)
- [x] Add keymaps for manual formatting (`<leader>lf`, `<leader>lt`)
### Phase 2: Remove none-ls
- [x] Delete `lua/plugins/none-ls.lua`
- [x] Remove none-ls from lazy-lock.json (happens automatically on `:Lazy sync`)
- [x] Remove the custom phpcbf autocmd workaround (no longer needed)
### Phase 3: Testing
- [x] Test prettier formatting (JS/TS/CSS files)
- [x] Test stylua formatting (Lua files)
- [x] Test phpcbf formatting (PHP files) - **verified NO blank lines added**
- [x] Test format-on-save toggle (`<leader>lt`)
- [x] Test manual format (`<leader>lf`)
- [x] Test project-local formatter detection
- [x] Verify phpcs.xml is respected when present
### Phase 4: Documentation
- [x] Update README.md Configuration section
- [x] Update AGENTS.md Process section (plugin list, formatting tools)
- [x] Update MIGRATION_PLAN.md status (Phase 9.2)
- [x] Update LOG.md with decision and rationale
## Expected Benefits
1. **No more blank line bug** - conform calls phpcbf cleanly without none-ls's buggy preprocessing
2. **No LSP overhead** - formatters run as simple shell commands
3. **Better maintained** - conform.nvim is actively developed
4. **Cleaner architecture** - clear separation: LSP (features), nvim-lint (diagnostics), conform (formatting)
5. **Easier debugging** - simpler execution path, better error messages
## Rollback Plan
If conform.nvim has issues:
1. Restore `lua/plugins/none-ls.lua` from git
2. Keep the custom phpcbf autocmd for PHP
3. Run `:Lazy sync` to reinstall none-ls
## Notes
- conform.nvim has built-in support for all our formatters (prettier, stylua, phpcbf)
- It handles executable resolution similarly to our `find_executable()` helper
- Format-on-save can be toggled per-buffer or globally
- Visual range formatting supported out of the box

275
LOG.md
View File

@ -1,275 +0,0 @@
# Neovim Rebuild — Decisions & Reference
Authoritative notes for the Neovim migration. Use this alongside `MIGRATION_PLAN.md` and `neovim-migration-guide.md`.
- Migration plan: MIGRATION_PLAN.md
- Guide and requirements: neovim-migration-guide.md
## Decisions Log
Record every decision here with a short rationale. Append new entries; do not rewrite history.
- 2025-01-13: **Popup borders for visibility**: Added rounded borders to all floating windows to prevent them from blending into the background. Changed `NormalFloat` and `FloatBorder` backgrounds from transparent (`c.NONE`) to `c.bg_ui` (slightly off-white) in colorscheme. Configured LSP handlers for hover and signature help to use `border = "rounded"`. Diagnostics already had borders configured. This provides clear visual separation between popups and main editor content.
- 2025-01-13: **Git files to quickfix**: Added `<leader>gg` keymap to send all modified, deleted, and untracked Git files to quickfix list with status indicators. Uses `git status --porcelain` to parse file status (Modified, Deleted, Added, Untracked, etc.) and displays it in the quickfix text column. Implementation in `utils.git_changed_files()`. Complements existing `<leader>hq` (Gitsigns hunks to quickfix) with file-level Git status workflow.
- 2025-01-12: **Custom Tabline Function**: Implemented configurable custom tabline to replace Neovim's hardcoded path shortening (e.g., `a/p/file.txt`). Function in `lua/utils.lua` allows controlling: (1) number of full parent directories via `utils.tabline_full_parents` (default: 1), and (2) shortening length via `utils.tabline_shorten_length` (default: 3 characters). Example: with defaults, `/path/to/my/project/src/file.txt` becomes `pat/to/my/project/src/file.txt`. User preference for seeing enough context in shortened paths while keeping tab width manageable.
- 2025-12-06: PHP LSP = `intelephense` (good PHP ecosystem support; integrates includePaths).
- 2025-12-06: Enable Markdown LSP = `marksman` (lightweight, good MD features).
- 2025-12-06: Use legacy `listchars` and `showbreak` values (preserve muscle memory).
- 2025-12-06: No direnv fallback for local config (keep setup simple and repo-driven).
- 2025-12-06: Project-local config = layered JSON `.nvim.json` + `.nvim.local.json` with `.nvimroot` as workspace boundary marker (multi-repo friendly).
- 2025-12-06: Switch to `folke/neoconf.nvim` for project-local configuration (supersedes custom layered JSON loader); use `.neoconf.json` at workspace root.
- 2025-12-06: Use `mason.nvim` + `mason-lspconfig.nvim` to install/manage LSP servers; keep custom lspconfig setup; include `.neoconf.json` in root detection.
- For PHP validation inside this repo: we require `.neoconf.json` to attach `intelephense` to avoid the repos `.git` being chosen as the LSP root.
- 2025-12-06: Plugin approval policy — other plugins are allowed, but do not install any plugin not listed without explicit confirmation.
- 2025-12-07: **CRITICAL REGRESSION FIX**: Neovim 0.11+ does NOT support `on_new_config` callback in `vim.lsp.config()`. Project-local settings (`.nvim.lua`) must be loaded via `LspAttach` autocmd instead. The `on_new_config` hook was silently ignored, causing empty settings and breaking goto-definition for external includes. Solution: Use `LspAttach` to load project settings, merge into `client.settings`, and send `workspace/didChangeConfiguration` notification.
- 2025-12-07: Native `exrc` + `.nvim.lua` finalized as project-local config approach (replaces neoconf, which is incompatible with Neovim 0.11+ native LSP API). Security via `vim.opt.secure = true`.
- 2025-12-07: Navigation Phase 4 decisions:
- Skip Neo-tree in favor of netrw for project visualization
- Use Telescope as primary "find file" tool with fuzzy finding
- Add Oil.nvim for file manipulation (rename/move/delete with buffer sync)
- netrw for tree view and preview splits; Oil for operations that would break buffer names
- PHP `gf` enhancement via `includeexpr` for WordPress/PHP path resolution
- **Working directory locked to project root**: Disabled `autochdir`, set `netrw_keepdir = 1`, captured initial cwd in `vim.g.project_root`, configured Telescope to always search from project root regardless of current buffer
- 2025-12-07: Treesitter Phase 5 decisions:
- Focus on core languages: PHP, HTML, JavaScript, TypeScript, CSS, Markdown, Lua, Bash, JSON
- Enable syntax highlighting with performance safeguard (disable for files >100KB)
- Incremental selection: CR to expand, BS to shrink, S-CR for scope expansion
- Textobjects for functions (`af`/`if`), classes (`ac`/`ic`), parameters (`aa`/`ia`), conditionals, loops, comments
- Movement keymaps: `]f`/`[f` for next/prev function, `]c`/`[c` for classes, `]a`/`[a` for parameters
- Parameter swapping: `<leader>a`/`<leader>A` to swap with next/prev parameter
- Auto-tag for HTML/PHP/JS/React/TS/Vue files (auto-close, auto-rename, close-on-slash)
- Indent disabled initially (experimental); can enable per-filetype if stable
- 2025-12-07: UX/Editing Phase 6 decisions:
- Enable signcolumn=yes (always show for LSP diagnostics, git signs)
- Enable cursorline (highlight current line)
- Enable colorcolumn=80,120 (visual guides at 80 and 120 chars)
- NO cursorcolumn (as per AGENTS.md)
- NO auto-window toggling behavior (keep settings static, no per-window autocmds)
- NO motion plugin (skip leap/flash for simplicity)
- Undotree moved to separate phase (6.8)
- Comment.nvim for commenting (gcc/gc/gbc/gb keymaps)
- nvim-surround for text objects (ys/ds/cs operators)
- nvim-autopairs for auto-closing brackets/quotes with Treesitter and cmp integration
- indent-blankline for visual indent guides with scope highlighting
- nvim-ufo for enhanced folding with Treesitter/LSP providers (zR/zM/K for peek)
- undotree for visual undo history (<leader>u to toggle)
- 2025-12-07: Git integration Phase 7:
- Gitsigns with minimal config for signs in gutter (add, change, delete markers)
- Hunk navigation: `]h`/`[h` for next/prev hunk
- Hunk actions: stage, reset, preview
- NO inline blame or advanced features (keep minimal)
- 2025-12-07: Copilot Phase 8:
- Integrated via copilot.lua + copilot-cmp (completion source)
- Auto-trigger suggestions as you type
- Copilot suggestions appear before LSP in completion menu (higher priority)
- Enabled for all filetypes
- No specific Copilot keymaps (use existing cmp keymaps)
- Node.js v22.21.1 confirmed working
- 2025-12-07: Formatting & Linting Phase 9:
- **Philosophy**: Formatters are authoritative; Neovim settings match formatter output
- **Formatters**: prettier (JS/TS/CSS/JSON/MD/HTML), phpcbf (PHP/WordPress), stylua (Lua), black (Python)
- **Linters**: eslint_d (JS/TS), phpcs (PHP/WordPress), markdownlint (Markdown), ruff (Python)
- **Strategy**: Project-local executables first (node_modules/.bin/, vendor/bin/), then Mason, then system PATH
- **WordPress**: phpcs/phpcbf already installed globally; use phpcs.xml or --standard=WordPress
- **Format-on-save**: Enabled by default, toggle with `<leader>lt`
- **Manual format**: `<leader>lf` (buffer), `<leader>lf` (visual range)
- **Linting split**: conform.nvim for formatting, nvim-lint for diagnostics (replaced none-ls after discovering phpcbf preprocessing bug)
- **Python support**: pyright LSP, black formatter, ruff linter, treesitter parser
- **Per-filetype indentation**: Explicit settings per filetype to match formatters
- PHP: tabs, 2-space display (WordPress standards)
- JS/TS/CSS/JSON/HTML: 2 spaces (Prettier)
- Lua: 2 spaces (common convention)
- Markdown: 2 spaces (Prettier)
- Python: 4 spaces (Black/PEP 8)
- **Global defaults**: 4 spaces (reasonable baseline for other filetypes)
- 2026-01-12: **conform.nvim migration**:
- **Trigger**: none-ls phpcbf builtin had preprocessing bug that added blank lines before formatting
- **Root cause**: none-ls uses LSP protocol for formatters, adds preprocessing step that corrupted input
- **Investigation**: Created debug wrappers, confirmed phpcbf CLI works correctly, traced issue to none-ls preprocessing
- **Solution**: Migrated to conform.nvim (modern, actively maintained, no LSP overhead)
- **Configuration**: Simplified config using conform's built-in formatters, only customized phpcbf for WordPress standard
- **Benefits**: Simpler code, no custom executable resolution needed, proper stdin/tmpfile handling per formatter
- **Removed**: none-ls.lua deleted (was renamed to .disabled during migration)
- 2025-12-07: Kept Behaviors Phase 10:
- **Abbreviations**: Common typo corrections (`adn→and`, `waht→what`, `tehn→then`, `functin→function`, `positin→position`) in dedicated `lua/abbreviations.lua` file for modularity
- **Templates**: Shell script template (template.sh) auto-loaded via BufNewFile autocmd for `*.sh` files
- **Whitespace highlighting**: Already handled via `listchars` in Phase 3.2 (settings.lua)
- **Persistent folds**: Not needed; UFO handles folding without explicit persistence mechanism
- **Persistent undo**: Re-enabled 2025-01-06 via `undofile=true` and `undodir` in settings.lua (preserves undo history across sessions)
- 2025-12-11: Colorscheme Phase 11.2:
- **Color palette extraction**: Extracted all 45 color definitions from original Paper Tonic colorscheme
- **Structure**: Created `lua/paper-tonic-modern/colors.lua` with comprehensive documentation
- **Categories**: Background colors (main, UI, highlights, status), foreground colors (text grays, exceptions), alerts/diagnostics, primary accents (brownish-red), and secondary accents (green/blue/cyan/magenta palettes)
- **Format**: Each color defined as `{hex, 256-color, ansi}` tuple for broad terminal compatibility
- **Philosophy**: Light, paper-like theme with subtle colors for long reading sessions
- **Next steps**: Phase 11.3 will create colorscheme structure and entry point
- 2025-12-11: Colorscheme Phase 11.3:
- **Colorscheme structure**: Created complete modular colorscheme architecture
- **Entry point**: `colors/paper-tonic-modern.lua` properly resets and loads colorscheme
- **Main module**: `lua/paper-tonic-modern/init.lua` with helper functions for setting highlights
- **Helper functions**: `M.highlight()` for single groups, `M.highlight_all()` for batch operations
- **Color conversion**: Smart conversion between gui/cterm/ansi formats
- **Group modules**: Created stub files for editor, syntax, treesitter, semantic, lsp, and plugins
- **Loading order**: Structured to load groups in logical sequence (editor → syntax → treesitter → semantic → lsp → plugins)
- **Validation**: Colorscheme loads successfully, highlights applied correctly
- **Language color consistency**: All documentation updated to reflect that each language maintains its color identity everywhere (PHP=primary, HTML=blue, CSS=green, templates=magenta)
- 2025-12-11: Colorscheme Phase 11.4:
- **Editor highlights**: Comprehensive UI element highlighting (Normal, StatusLine, CursorLine, Visual, Search, Pmenu, Folds, Diffs, Spelling, Messages)
- **Syntax highlights**: Traditional Vim syntax groups (Comment, String, Function, Keyword, Type, etc.) with language-specific overrides
- **Language support**: Added specific highlighting for CSS (green/c2), HTML (blue/c3), Lua, Markdown, JSON, JavaScript/TypeScript, PHP
- **Color verification**: All key groups tested and verified (Normal=#8c8c8c on #ffffff, htmlTag=#005faf, cssClassName=#008700)
- **CSS classes**: Green tones (c2) for CSS syntax elements
- **HTML tags**: Blue tones (c3) for HTML structure
- **PHP syntax**: Primary brownish-red tones for PHP keywords/functions
- **Test file**: Created test-colorscheme.lua for visual verification
- 2025-12-11: Colorscheme Phase 11.5:
- **TreeSitter highlights**: Comprehensive modern `@*` highlight groups implemented
- **Core groups**: @variable, @function, @keyword, @string, @number, @boolean, @comment, @type, @property, @operator, @punctuation
- **Language constructs**: @keyword.function, @keyword.return, @keyword.conditional, @keyword.repeat, @keyword.exception
- **Markup**: @markup.strong, @markup.italic, @markup.heading (1-6), @markup.link, @markup.raw, @markup.list
- **Template languages**: @punctuation.special.twig, @keyword.jinja (magenta c5 for template syntax)
- **Diagnostics**: @diff.plus, @diff.minus, @diff.delta
- **Debug tool**: Added `<leader>hi` keymap to show highlight group and resolved colors under cursor
- **Validation**: All TreeSitter groups tested and verified with correct palette colors
- 2025-12-11: Colorscheme Phase 11.6:
- **Language-specific TreeSitter groups**: Enhanced with comprehensive per-language overrides
- **CSS/SCSS**: Properties, variables ($var, --custom), functions, tag selectors (green c2 tones)
- **HTML**: Tags, attributes, delimiters (blue c3 throughout)
- **PHP**: Variables ($var), functions, methods, built-ins ($this, $GLOBALS), properties (primary brownish-red)
- **JavaScript/TypeScript**: Variables, functions, properties, built-ins (this, arguments), JSX/TSX tags (primary + blue for JSX)
- **Lua**: Variables, functions, built-ins (_G, _VERSION), properties (primary)
- **Markdown**: Headings (1-6), links, code blocks, inline code (primary for structure)
- **JSON**: Properties, strings, numbers, booleans (neutral colors)
- **Bash/Shell**: Variables, functions, built-ins, strings (primary)
- **Color consistency verified**: HTML=#005faf (blue), CSS properties=#7c6666 (primary_weak), PHP variables=#7c6666
- **Test files**: Created test-html.html, test-css.css, test-colors.php for validation
- 2025-12-11: Colorscheme Phase 11.7:
- **Semantic highlighting**: Custom TreeSitter captures for cross-language consistency
- **CSS semantic captures**: @CssClassName (green c2), @CssIdentifier (strong green c2_strong, bold), @cssPseudoClass (bold brownish-red primary_strong - distinguishes from green selectors), @cssPseudoElement (bold brownish-red), @cssNestingSelector (strong green), @CssUniversalSelector
- **CSS properties**: @CssProp (primary_weak), @CssPropertyValue (bold fg), @CssUnit (delimiter)
- **CSS media queries**: @cssMediaFeatureName, @cssMediaQuery, @cssMediaQueryValue
- **HTML attributes**: @ClassNameAttribute, @IdAttribute, @DataAttribute (all fg_weak for attribute names)
- **HTML data attributes**: @DataAttributeValue (Function color)
- **Cross-language consistency**: Critical feature - class="my-class" in HTML uses same color (@CssClassName, green c2) as .my-class in CSS; id="my-id" in HTML uses same color (@CssIdentifier, green c2_strong) as #my-id in CSS
- **Visual benefits**: Easy to visually match selectors between HTML and CSS files; consistent color coding across languages
- **Custom queries**: Leverages existing after/queries/css/highlights.scm and after/queries/html/highlights.scm
- **Priority fix**: Added `(#set! priority 200)` to all custom captures to override built-in captures (@string priority 99, @constant default priority)
- **Technical note**: Custom queries require `;extends` directive and higher priority than built-in captures to work correctly
- **Pseudo-class design**: Uses bold brownish-red (primary_strong) instead of green to distinguish behavioral modifiers (:hover, :focus) from structural selectors (.class, #id)
- 2025-12-11: Colorscheme Phase 11.8:
- **LSP highlights**: Comprehensive diagnostic and UI highlights
- **LSP diagnostics**: Error (red #d70000), Warn (orange #d75f00), Info (blue #8989af), Hint (green #89af89)
- **LSP diagnostic UI**: Underlines (undercurl for errors/warnings), signs, floating windows, virtual text with tinted backgrounds
- **LSP references**: Cyan background gradients - Text (#e0eaff subtle), Read (#d4f0ff medium), Write (#a3e0ff strong)
- **LSP semantic tokens**: Fallback groups linking to TreeSitter equivalents for consistency
- **Telescope highlights**: Neutral base with primary accent for matching, selection, and titles
- **Gitsigns highlights**: Status colors - Add (green), Change (blue), Delete (red); inline diff backgrounds
- **nvim-cmp highlights**: Primary tones for item kinds, bold for match highlighting, italic for menu
- **Oil.nvim highlights**: Bold directories, status colors for permissions (read=green, write=orange, execute=blue), operation colors
- **indent-blankline**: Subtle grays for guides and scope
- **nvim-ufo**: Subtle backgrounds for folds with preview support
- 2025-12-11: Colorscheme Phase 11.9:
- **Plugin configuration updated**: `lua/plugins/colorscheme.lua` loads `paper-tonic-modern`
- **Cleanup**: Moved old `paper-tonic/` directory to `legacy/paper-tonic-original/`
- **Validation**: Full config reload tested - all highlight groups load correctly
- **Colorscheme integration**: Native Neovim colorscheme at `colors/paper-tonic-modern.lua`
- **No overrides needed**: Modern implementation doesn't require separate override file
- 2025-12-12: Added keymap reference documentation (now `README.md`) + `:KeymapsGuide` floating viewer to document every configured keymap (global + plugin-specific) grouped by source file.
- 2025-12-12: nvim-cmp updated to disable auto-selection; confirm requires explicit item selection.
## Project-Local Configuration (design)
Context: Projects can contain multiple git repositories (e.g., WordPress site with theme + multiple plugins). We need a way to set per-project `intelephense.includePaths` and similar without globalizing settings.
Options considered
- Layered JSON files (recommended): `.nvim.json` (committed) + `.nvim.local.json` (gitignored)
- Walk upward from current file; collect and merge configs until reaching a boundary marker or filesystem root.
- Boundary marker: `.nvimroot` to define a workspace that can encompass multiple repos.
- Merge order: top → bottom; nearest wins on conflicts; arrays replace by default unless we later add a merge strategy.
- Pros: safe (data-only), portable, reviewable in PRs.
- Cons: less dynamic than Lua.
- Layered Lua files: `.nvim.lua` + `.nvim.local.lua`
- Pros: maximum flexibility (computed paths).
- Cons: executes code from the repo; security gates required.
- Env/direnv: rejected (decision 2025-12-06).
- Central registry file in `~/.config/nvim/`: possible later for private overrides, but not primary.
Chosen approach (confirmed)
- Use `folke/neoconf.nvim` with `.neoconf.json` at the workspace root (or project roots) to supply LSP/plugin settings. Multi-repo: keep a single `.neoconf.json` at the parent workspace folder and open Neovim from there (or use a rooter later if needed).
Example `.neoconf.json`
```
{
"lspconfig": {
"intelephense": {
"settings": {
"intelephense": {
"environment": {
"includePaths": [
"wp-content/themes/my-theme",
"wp-content/plugins/custom-plugin"
]
}
}
}
}
}
}
```
- Files recognized per directory level:
- `.nvim.json`: checked into VCS; shared per-project defaults.
- `.nvim.local.json`: gitignored; machine-specific overrides.
- Loader behavior:
1. Start at the buffers directory; ascend toward root.
2. Stop at directory containing `.nvimroot` (if found) or at filesystem root.
3. At each level, if `.nvim.json` or `.nvim.local.json` exists, parse and stage for merge.
4. Merge staged configs in ascending order; nearest directory applies last.
- Path resolution:
- Relative paths resolve against the topmost boundary (i.e., `.nvimroot` directory if present; otherwise the highest directory in which a config was found). This allows referencing sibling repos (e.g., theme and plugins) from a single top-level site folder.
Example `.nvim.json`
```
{
"lsp": {
"php": {
"intelephense": {
"includePaths": [
"wp-content/themes/my-theme",
"wp-content/plugins/custom-plugin",
"vendor/some-package/src"
]
}
}
}
}
```
Intended integration
- A helper module `lua/local_config.lua` will expose:
- `load(start_dir) -> table` collecting and merging configs
- `get(path, default)` for reads, e.g., `get({"lsp","php","intelephense","includePaths"}, {})`
- LSP wiring (in Phase 3.5):
- Read includePaths via the helper and pass to `settings.intelephense.environment.includePaths` in `lspconfig.intelephense.setup{}`.
Open items to confirm
- Confirm JSON as the format (vs Lua) for project-local config.
- Confirm `.nvimroot` as the workspace boundary marker name.
- Confirm array merge behavior (replace vs concatenate). Initial proposal: replace.
## LSP Scope (initial)
- Core servers: `lua_ls`, `tsserver`, `html`, `cssls`, `jsonls`, `bashls`
- PHP: `intelephense` (decision)
- Markdown: `marksman` (decision)
- Keep configuration minimal; defer language-specific tuning.
## Text & Editing Settings
- `spelllang = en_gb`
- `listchars` and `showbreak` will reuse legacy values.
- `completeopt = menu,menuone,noselect` for `nvim-cmp`.
## Process Reminders
- After any change or decision, update this README and MIGRATION_PLAN.md.
- Keep subphases small and verify each step in isolation.

View File

@ -1,514 +0,0 @@
# Neovim Migration Checklist
Source of truth for the step-by-step rebuild. Keep this concise and up to date. See details in `neovim-migration-guide.md`.
## Phase 0 — Ground Rules
- [x] Create AGENTS.md with rules
## Phase 1 — Clean & Archive Legacy Config
## Phase 1.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
## Phase 1.2 — Inventory legacy files
- [x] Inventory existing Vimscript, plugin files, and directories
## Phase 1.3 — Create archive location
- [x] Create `legacy/` archive folder within this config
## Phase 1.4 — Move legacy files into archive
- [x] Move legacy init files and plugin directories into `legacy/` (do not delete)
## Phase 1.5 — Document archived contents
- [x] Optionally add `legacy/README.md` noting what was archived
## Phase 1.6 — Preserve selected directories
- [x] Keep `undodir`, `spell`, `view`, `UltiSnips`, `templates` as-is for now (review later)
- [x] Re-enabled persistent undo in settings.lua (2025-01-06)
## Phase 2 — Bootstrap
## Phase 2.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
## Phase 2.2 — Scaffold Lua config skeleton
- [x] Scaffold Lua config skeleton (`init.lua`, `lua/settings.lua`, `lua/keymaps.lua`, `lua/autocmds.lua`, `lua/utils.lua`, `lua/plugins/`)
## Phase 2.3 — Bootstrap lazy.nvim
- [x] Bootstrap `lazy.nvim` plugin manager
## Phase 2.4 — Disable unused providers
- [x] Disable unused providers (ruby, perl, node)
## Phase 2.5 — Ensure lazy boots without specs
- [x] Ensure lazy boots without specs (add empty `lua/plugins/init.lua`)
## Phase 3 — Core Editing & LSP
## Phase 3.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: PHP LSP = `intelephense`
- [x] Decision: Enable Markdown LSP = `marksman`
- [x] Decision: Use legacy `listchars` and `showbreak` values
- [x] Decision: Support perproject config for `intelephense.includePaths`
- [x] Decision: No direnv fallback for local config
## Phase 3.2 — Non-plugin settings to Lua
- [x] Port non-plugin settings to Lua (options, listchars, showbreak, spelllang=en_gb)
## Phase 3.3 — Core completion stack
- [x] Add core completion stack: `nvim-cmp`, `cmp-nvim-lsp`, `cmp-buffer`, `cmp-path`, `LuaSnip`
- [x] Disable auto-selection in nvim-cmp (require explicit confirm)
## Phase 3.4 — Projectlocal configuration (native exrc)
- [x] Confirm scope and priorities for this subphase
- [x] Decision: Use native `exrc` + `secure` instead of neoconf (neoconf incompatible with Neovim 0.11+ vim.lsp.config API)
- [x] Enable `exrc` and `secure` in settings.lua
- [x] Implement `.nvim.lua` loader in LSP config using `on_new_config` hook
- [x] Wire `intelephense.environment.includePaths` via `.nvim.lua`
- [x] Create validation workspaces: `WORKSPACE_TEST/` and `EXTERNAL_TEST/` with sample PHP files
- [x] Migrate to `vim.lsp.config()` and `vim.lsp.enable()` (Neovim 0.11+ native API)
- [x] Verify project settings load correctly from actual root_dir
- [x] Clean up debug notifications and temporary code
## Phase 3.7 — Clean LSP config
- [x] Remove debug/helper logic from LSP config
- [x] Migrate from deprecated `require('lspconfig')[server].setup()` to `vim.lsp.config()` + `vim.lsp.enable()`
- [x] Use `on_new_config` hook for project-local settings loading (loads from actual root_dir, not cwd)
## Phase 3.8 — Validate project-local LSP settings
- [x] Create test workspaces with `.nvim.lua` files
- [x] Validate settings load from actual root_dir via `on_new_config` hook
- [x] Verify `gd` works for external library references via `intelephense.environment.includePaths`
- [x] Clean up debug code and notifications
## Phase 3.9 — Native exrc + vim.lsp.config migration (RESOLVED)
- [x] Discovered neoconf incompatible with Neovim 0.11+ (GitHub issue #116, unresolved since May 2024)
- [x] Migrated to native `exrc` + `secure` approach (zero dependencies, aligned with minimal philosophy)
- [x] Implemented `.nvim.lua` loader using `on_new_config` hook (loads from actual root_dir)
- [x] Migrated from deprecated `require('lspconfig')` framework to `vim.lsp.config()` + `vim.lsp.enable()`
- [x] Root detection handled by nvim-lspconfig's native configs (no custom root_dir needed)
- [x] Validated `gd` works for external library symbols via `intelephense.environment.includePaths`
- [x] Settings schema: `.nvim.lua` returns `{ lsp = { [server_name] = { settings = {...} } } }`
- [x] Security: `secure` mode prompts user to trust `.nvim.lua` files before execution
- [x] Kept `single_file_support = false` for `intelephense`
- [x] Fixed duplicate `gd` results issue
## Phase 3.5 — LSP minimal defaults
- [x] Add `nvim-lspconfig` with minimal defaults (no over-configuration)
- [x] Add minimal LSP on-attach keymaps (gd, gr, K, gD, gI)
- [x] Add global LSP keymaps with fallback in `lua/keymaps.lua`
- [x] Intelephense: set `single_file_support=false`, root detection via nvim-lspconfig defaults
- [x] Project settings loaded via `on_new_config` hook from `.nvim.lua` files
## Phase 3.6 — LSP server management (Mason)
- [x] Confirm scope and priorities for this subphase
- [x] Add `williamboman/mason.nvim` and `williamboman/mason-lspconfig.nvim`
- [x] Ensure servers installed: `lua_ls`, `tsserver`, `html`, `cssls`, `jsonls`, `bashls`, `marksman`, `intelephense`
- [x] Keep our custom per-server setup; use Mason only for installation
## Phase 4 — Navigation
## Phase 4.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Skip Neo-tree in favor of netrw for navigation/visual context
- [x] Decision: Use netrw for project structure visualization and file preview
- [x] Decision: Telescope as primary "find file" tool
- [x] Decision: Add Oil.nvim for file manipulation (handles buffer sync on rename/move/delete)
- [x] Note: Oil.nvim is for evaluation; alternative is mini.files if too heavy
## Phase 4.2 — Configure netrw
- [x] Configure netrw with tree view, preview split, and basic settings (no banner, sensible defaults)
- [x] Created `lua/netrw-config.lua` with settings: tree view, horizontal preview split below, 50/50 split, human-readable sizes
- [x] Add netrw keymaps: `<leader>te` (new tab at current file's directory), `<leader>tE` (new tab at project root)
- [x] Decision: No `<leader>e` or `<leader>v` keymaps - use `:Ex`, `:Vex` directly when needed
- [x] Preview behavior: Enter opens file in netrw window, `p` opens 50/50 horizontal split below
## Phase 4.2.1 — PHP gf enhancement
- [x] Add PHP `includeexpr` for intelligent `gf` behavior (handles `__DIR__`, `__FILE__`, `dirname(__FILE__)` patterns)
- [x] Created `after/ftplugin/php.lua` with path resolution for WordPress/PHP patterns
- [x] Tested and validated with test-gf.php
## Phase 4.3 — Telescope setup
- [x] Add `telescope.nvim` + `telescope-fzf-native.nvim` for fuzzy finding
- [x] Configure basic pickers: `find_files`, `live_grep`, `buffers`, `help_tags`, `oldfiles`, `current_buffer_fuzzy_find`
- [x] Add LSP pickers: `lsp_document_symbols`, `lsp_workspace_symbols`
- [x] Keymaps configured:
- `<leader>ff` - Find files
- `<leader>fg` - Live grep (search text)
- `<leader>fb` - Find buffers (with `<C-d>` to delete)
- `<leader>fh` - Find help
- `<leader>fr` - Recent files
- `<leader>/` - Search current buffer
- `<leader>fs` - Document symbols (LSP)
- `<leader>fS` - Workspace symbols (LSP)
- [x] Minimal UI: dropdown theme for files/buffers, no previewer for quick selection
## Phase 4.4 — Oil.nvim for file manipulation
- [x] Add `stevearc/oil.nvim` for filesystem operations (rename, create, delete, copy, move)
- [x] Configure to handle buffer name sync on file operations
- [x] Keep minimal - use only when shell operations would cause buffer issues
- [x] Keymaps configured:
- `<leader>fo` - Open Oil file browser (regular buffer)
- `<leader>fO` - Open Oil in floating window (with floating previews)
- `<C-p>` in Oil - Preview files (vertical split to right for regular, floating window for float mode)
- [x] Preview splits open to the right via `vertical = true` and `split = 'belowright'`
- [x] Floating window preview direction: `preview_split = "right"`
- [x] Note: Evaluate practicality; can switch to `mini.files` if preferred
## Phase 5 — Treesitter
## Phase 5.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Focus on languages: PHP, HTML, JavaScript, TypeScript, CSS, Markdown, Lua, Bash, JSON
- [x] Decision: Enable syntax highlighting and incremental selection (CR to expand, BS to shrink)
- [x] Decision: Enable textobjects for functions, classes, parameters, conditionals, loops
- [x] Decision: Enable autotag for HTML/PHP/JS/React files
- [x] Decision: Disable indent (experimental) to start; can enable per-filetype if stable
## Phase 5.2 — Base Treesitter
- [x] Add `nvim-treesitter` with incremental selection, highlighting
- [x] Configure parsers: lua, vim, vimdoc, php, html, javascript, typescript, tsx, css, scss, json, markdown, markdown_inline, bash, regex
- [x] Enable auto-install for missing parsers
- [x] Incremental selection keymaps: CR (init/expand), S-CR (scope expand), BS (shrink)
- [x] Disable for large files (>100KB) to maintain performance
## Phase 5.3 — Treesitter textobjects
- [x] Add `nvim-treesitter-textobjects`
- [x] Select keymaps: `af`/`if` (function), `ac`/`ic` (class), `aa`/`ia` (parameter), `ai`/`ii` (conditional), `al`/`il` (loop), `a/` (comment)
- [x] Move keymaps: `]f`/`[f` (next/prev function start), `]F`/`[F` (function end), similar for classes and parameters
- [x] Swap keymaps: `<leader>a`/`<leader>A` (swap parameter with next/prev)
## Phase 5.4 — Treesitter autotag
- [x] Add `nvim-ts-autotag`
- [x] Enable auto-close, auto-rename, and close-on-slash for HTML/PHP/JS/React/TS/Vue files
## Phase 6 — UX / Editing
## Phase 6.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: No cursorcolumn (as per AGENTS.md)
- [x] Decision: No auto-window toggling behavior (keep settings static)
- [x] Decision: No motion plugin (skip leap/flash)
- [x] Decision: Move undotree to separate Phase 6.8
- [x] UX settings to add: signcolumn, cursorline, colorcolumn
## Phase 6.2 — Core UX settings
- [x] Add signcolumn=yes (always show gutter for LSP/git)
- [x] Add cursorline (highlight current line)
- [x] Add colorcolumn=80,120 (visual guides)
- [x] No cursorcolumn (excluded as per AGENTS.md)
- [x] No per-window autocommands (keep simple)
## Phase 6.3 — Comment.nvim
- [x] Add `numToStr/Comment.nvim`
- [x] Configure for gcc/gc commenting
- [x] Add keymaps if needed
## Phase 6.4 — Surround
- [x] Add `kylechui/nvim-surround`
- [x] Minimal config with default keymaps (ys, ds, cs)
## Phase 6.5 — Autopairs
- [x] Add `windwp/nvim-autopairs`
- [x] Integrate with nvim-cmp
## Phase 6.6 — Indent guides
- [x] Add `lukas-reineke/indent-blankline.nvim`
- [x] Configure scope highlighting
## Phase 6.7 — Folding (UFO)
- [x] Add `kevinhwang91/nvim-ufo` for folding
- [x] Configure with Treesitter and LSP providers
- [x] Add fold keymaps (za, zR, zM, etc.)
## Phase 6.8 — Undotree
- [x] Add `mbbill/undotree`
- [x] Add keymap to toggle undotree
- [x] Configure minimal settings
## Phase 7 — Git Integration
## Phase 7.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Gitsigns with minimal config - signs in gutter, hunk navigation/actions only
- [x] Decision: Skip markdown rendering plugins entirely
## Phase 7.2 — Gitsigns
- [x] Add `lewis6991/gitsigns.nvim`
- [x] Configure signs in gutter (add, change, delete, topdelete, changedelete)
- [x] Add hunk navigation keymaps (]h/[h for next/prev hunk)
- [x] Add hunk action keymaps (stage, reset, preview)
- [x] Keep minimal - no inline blame, no advanced features
## Phase 8 — Copilot
## Phase 8.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Integrate Copilot into nvim-cmp as completion source
- [x] Decision: Auto-trigger suggestions as you type
- [x] Decision: No specific Copilot keymaps needed (use existing cmp keymaps)
- [x] Decision: Enable for all filetypes
- [x] Decision: Copilot suggestions appear before LSP suggestions in completion menu
- [x] Node.js v22.21.1 upgraded and confirmed working
## Phase 8.2 — Copilot setup
- [x] Add `zbirenbaum/copilot.lua` + `zbirenbaum/copilot-cmp`
- [x] Configure copilot.lua with auto-trigger and all filetypes
- [x] Add copilot-cmp as source to nvim-cmp
- [x] Set copilot-cmp priority higher than LSP in completion menu
- [x] Note: Copilot initializes on first InsertEnter; enter/exit insert mode once before using `:Copilot auth`
## Phase 9 — Formatting & Linting
## Phase 9.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Formatters - prettier (JS/TS/CSS/JSON/MD/HTML), phpcbf (PHP), stylua (Lua), black (Python)
- [x] Decision: Linters - eslint (JS/TS), phpcs (PHP), markdownlint (Markdown), ruff (Python)
- [x] Decision: Project-local executables preferred, fallback to global (node_modules/.bin/, vendor/bin/)
- [x] Decision: phpcs/phpcbf already installed globally with WordPress coding standards - no installation needed
- [x] Decision: Format-on-save enabled by default with toggle capability
- [x] Decision: Manual format keymaps: `<leader>lf` (normal), `<leader>lf` (visual range)
- [x] Decision: Respect project config files (.prettierrc, phpcs.xml, .eslintrc, pyproject.toml, etc.)
- [x] Strategy: Helper function detects project-local executables first, then global
- [x] WordPress: phpcs.xml in project root or --standard=WordPress flag for WordPress projects
- [x] Philosophy: Formatters are authoritative source of truth; Neovim settings should match formatter rules per filetype
- [x] Note: Phase 9.4 will align Neovim editor settings (tabstop, shiftwidth, expandtab) with formatter configurations
## Phase 9.2 — conform.nvim setup with project-aware executables
- [x] Add `stevearc/conform.nvim` (replaced none-ls due to phpcbf preprocessing bug)
- [x] Configure formatters (conform has built-in support for all):
- [x] prettier (auto-detects project-local, Mason, global)
- [x] phpcbf (auto-detects project-local, global; customized for WordPress standard)
- [x] stylua (auto-detects project-local, Mason, global)
- [x] Add `mfussenegger/nvim-lint` for linting (separate from formatting)
- [x] Configure linters via nvim-lint:
- [x] eslint_d (project-local first, then Mason, then global - daemon version for speed)
- [x] phpcs (project-local first, then global system install - already available)
- [x] markdownlint (Mason installed)
- [x] Add format-on-save autocommand with toggle capability (`<leader>lt` to toggle)
- [x] Add manual format keymaps: `<leader>lf` (buffer), `<leader>lf` (visual range)
- [x] Ensure WordPress coding standards work via phpcs.xml or --standard flag
- [x] Search order handled by conform.nvim automatically: project → Mason → system PATH
- [x] Note: Linting runs on BufEnter, BufWritePost, InsertLeave events
- [x] Removed none-ls.lua (bug caused blank lines in PHP formatting)
## Phase 9.3 — Mason formatter/linter installation
- [x] Add `WhoIsSethDaniel/mason-tool-installer.nvim` for automated installation
- [x] Install via Mason: prettier, eslint_d, markdownlint, stylua, black, ruff
- [x] Note: phpcs/phpcbf already installed globally, skip Mason installation
- [x] Verify all tools available: Tools installed to ~/.local/share/nvim/mason/bin/
## Phase 9.4 — Align Neovim settings with formatter rules
- [x] Configure per-filetype settings to match formatter defaults
- [x] PHP: tabs, 2-width display (WordPress standards)
- [x] JS/TS/CSS/JSON/HTML: spaces, 2-width (Prettier defaults)
- [x] Lua: spaces, 2-width (common Neovim convention)
- [x] Markdown: spaces, 2-width (Prettier defaults)
- [x] Python: spaces, 4-width (Black/PEP 8 defaults)
- [x] Use autocmds in lua/autocmds.lua for filetype-specific tabstop, shiftwidth, expandtab
- [x] Set global defaults: 4-width, spaces (reasonable baseline)
- [x] Goal: Manual editing feels natural and matches what formatter will produce on save
- [x] Note: All settings kept explicit per-filetype for clarity and maintainability
## Phase 10 — Migrate Kept Behaviours
## Phase 10.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Port abbreviations to Lua via vim.cmd.iabbrev
- [x] Decision: Port shell template auto-loading via autocmd + BufReadPost
- [x] Decision: Whitespace highlighting already handled via listchars (Phase 3.2)
- [x] Decision: Skip persistent folds (UFO handles folding, no explicit persistence needed)
## Phase 10.2 — Abbreviations
- [x] Abbreviations: `adn→and`, `waht→what`, `tehn→then`, `functin→function`, `positin→position`
- [x] Created dedicated `lua/abbreviations.lua` for modularity and easier maintenance
## Phase 10.3 — Templates
- [x] Templates: auto-populate new `.sh` from template
- [x] Added BufNewFile autocmd for *.sh in lua/autocmds.lua
## Phase 10.4 — Whitespace highlighting
- [x] Whitespace highlighting (modern approach)
- [x] Note: Already handled via listchars in Phase 3.2 (settings.lua)
## Phase 10.5 — Persistent folds
- [x] Persistent folds (via UFO) -- no need, this is no longer required.
- [x] Note: UFO handles folding; no explicit persistence mechanism needed
## Phase 10.6 — Custom tabline function
- [x] Implement custom tabline with configurable path shortening (`lua/utils.lua`)
- [x] Configure tabline in `lua/settings.lua` with `utils.tabline_full_parents`
- [x] Document tabline configuration in `README.md`
- [x] Show N parent directories in full, shorten earlier paths to first letter
## Phase 11 — Colorscheme: Modern Paper Tonic
## Phase 11.1 — Confirm scope and priorities
- [x] Confirm scope and priorities for this phase
- [x] Decision: Create modern Paper Tonic fork from scratch
- [x] Decision: Name = "paper-tonic-modern"
- [x] Decision: Location = inside nvim config initially, move to repo later
- [x] Decision: Structure = modular Lua colorscheme (colors.lua, groups/*.lua)
- [x] Phased approach:
- Phase 11.2-11.4: Extract palette and base groups
- Phase 11.5-11.6: General TreeSitter highlights (modern @* groups)
- Phase 11.7: Semantic highlighting (custom CSS/HTML captures)
- Phase 11.8: Plugin support (LSP, Telescope, Gitsigns, etc.)
## Phase 11.2 — Extract Paper Tonic color palette
- [x] Create `lua/paper-tonic-modern/colors.lua`
- [x] Extract all `c_*` color variables from Paper Tonic
- [x] Document what each color represents (foreground, background, accent colors)
- [x] Convert to modern format (keep hex, 256-color, ANSI mappings)
## Phase 11.3 — Create colorscheme structure
- [x] Create directory structure:
- `colors/paper-tonic-modern.lua` (entry point)
- `lua/paper-tonic-modern/init.lua` (main logic)
- `lua/paper-tonic-modern/colors.lua` (palette)
- `lua/paper-tonic-modern/groups/` (highlight group definitions)
- [x] Create helper function for setting highlights
- [x] Set up colorscheme loading mechanism
## Phase 11.4 — Base vim highlight groups
- [x] Create `lua/paper-tonic-modern/groups/editor.lua`
- [x] Map Paper Tonic colors to base groups: Normal, Comment, LineNr, CursorLine, etc.
- [x] Create `lua/paper-tonic-modern/groups/syntax.lua`
- [x] Map to syntax groups: Function, String, Keyword, Identifier, etc.
- [x] Test with simple Lua file to verify base colors work
## Phase 11.5 — Modern TreeSitter highlight groups
- [x] Create `lua/paper-tonic-modern/groups/treesitter.lua`
- [x] Map modern `@*` groups to Paper Tonic colors:
- Core: @variable, @function, @keyword, @string, @number, @boolean, @comment
- Types: @type, @property, @field, @parameter
- Operators: @operator, @punctuation.delimiter, @punctuation.bracket
- Language constructs: @keyword.function, @keyword.return, @keyword.conditional
- [x] Use `:help treesitter-highlight-groups` as reference
- [x] Test with multiple languages: Lua, PHP, JavaScript, HTML, CSS
## Phase 11.6 — Language-specific TreeSitter groups
- [x] Add language-specific overrides (e.g., @variable.php, @tag.html)
- [x] Ensure HTML tags, CSS selectors, PHP variables have appropriate colors
- [x] Test across primary languages (HTML, PHP, JS, CSS, Markdown)
- [x] Added comprehensive language-specific groups:
- CSS/SCSS: properties, variables, functions (green c2 tones)
- HTML: tags, attributes, delimiters (blue c3)
- PHP: variables, functions, methods, built-ins (primary brownish-red)
- JavaScript/TypeScript: variables, functions, properties, JSX tags (primary + blue for JSX)
- Lua: variables, functions, built-ins (primary)
- Markdown: headings, links, code blocks (primary for structure)
- JSON: properties, strings, numbers (neutral)
- Bash: variables, functions, built-ins (primary)
## Phase 11.7 — Semantic highlighting (custom captures)
- [x] Create `lua/paper-tonic-modern/groups/semantic.lua`
- [x] Define your custom semantic captures:
- @CssClassName (for CSS .class and HTML class="" - same color)
- @CssIdentifier (for CSS #id and HTML id="" - same color)
- @DataAttribute, @DataAttributeValue (HTML data-* attributes)
- @cssPseudoClass, @cssNestingSelector, @CssUniversalSelector (CSS-specific)
- [x] Verify cross-language consistency: class names in HTML match CSS
- [x] Implemented semantic highlights:
- CSS: @CssClassName (green c2), @CssIdentifier (strong green c2_strong), @cssPseudoClass (weak green c2_weak)
- CSS: @cssNestingSelector (strong green), @CssUniversalSelector (SpecialChar)
- CSS: @CssProp (primary_weak), @CssPropertyValue (bold), @CssUnit (Delimiter)
- HTML: @ClassNameAttribute, @IdAttribute, @DataAttribute (all fg_weak for attribute names)
- HTML: @DataAttributeValue (Function)
- Cross-language: class values and #id values in HTML use same colors as CSS selectors
- [ ] Test with existing custom queries (after/queries/css/highlights.scm, after/queries/html/highlights.scm)
## Phase 11.8 — Plugin highlight groups
- [x] Create `lua/paper-tonic-modern/groups/lsp.lua`
- [x] Define LSP diagnostics: DiagnosticError, DiagnosticWarn, DiagnosticHint, DiagnosticInfo
- [x] Define LSP UI: LspReferenceText, LspReferenceRead, LspReferenceWrite
- [x] Create `lua/paper-tonic-modern/groups/plugins.lua`
- [x] Add Telescope highlights (TelescopeNormal, TelescopeBorder, TelescopeSelection)
- [x] Add Gitsigns highlights (GitSignsAdd, GitSignsChange, GitSignsDelete)
- [x] Add nvim-cmp highlights (CmpItemKind*, CmpItemMenu, etc.)
- [x] Add Oil.nvim highlights if needed
**Notes**:
- LSP diagnostics use red (error), orange (warn), blue (info), green (hint)
- LSP references use cyan backgrounds with varying intensity (text/read/write)
- LSP semantic tokens link to TreeSitter groups for consistency
- Telescope uses neutral colors with primary accent for matching and selection
- Gitsigns uses status colors: green (add), blue (change), red (delete)
- nvim-cmp uses primary tones for kinds, bold for matching text
- Oil.nvim uses bold for directories, status colors for permissions and operations
- indent-blankline and ufo use subtle grays for non-intrusive UI
## Phase 11.9 — Update plugin configuration
- [x] Update `lua/plugins/colorscheme.lua` to use paper-tonic-modern
- [x] Remove old Paper Tonic reference
- [x] Remove colorscheme-overrides.lua (no longer needed)
- [x] Test full config reload
**Notes**:
- `lua/plugins/colorscheme.lua` already updated to load `paper-tonic-modern`
- Old `paper-tonic/` directory moved to `legacy/paper-tonic-original/` (archived)
- No `colorscheme-overrides.lua` existed (not needed with modern implementation)
- Full config reload tested: Normal ✓, DiagnosticError ✓, TelescopeNormal ✓, GitSignsAdd ✓
- Colorscheme loads correctly: `vim.g.colors_name = 'paper-tonic-modern'`
## Phase 11.10 — Validation and polish
- [ ] Test all primary languages (Lua, PHP, HTML, CSS, JS, Markdown)
- [ ] Verify semantic consistency (CSS classes in HTML match CSS files)
- [ ] Check plugin UI colors (Telescope, Gitsigns, nvim-cmp)
- [ ] Verify LSP diagnostics are visible and distinct
- [ ] Document color scheme in README
## Phase 11.11 — Extract to separate repo (later)
- [ ] Move to `~/projects/paper-tonic-modern/`
- [ ] Update plugin config to point to new location
- [ ] Add proper README, LICENSE, screenshots
- [ ] Consider publishing to GitHub
## Phase 12 — Cleanup & Validation
## Phase 12.1 — Confirm scope and priorities
- [ ] Confirm scope and priorities for this phase
## Phase 12.2 — Retire legacy files
- [ ] Retire legacy Vimscript files (keep for reference until verified)
## Phase 12.3 — Startup performance
- [ ] Validate startup performance (no errors, fast launch)
## Phase 12.4 — Navigation validation
- [ ] Validate Telescope navigation + LSP jumps
- [ ] Validate netrw browsing and preview splits
- [ ] Validate Oil.nvim file operations
- [x] Fix working directory behavior (disabled autochdir, locked Telescope to project root)
## Phase 12.5 — Language tooling validation
- [ ] Validate HTML/PHP/JS/Markdown tooling
- [ ] Verify LSP (gd, gr, K, diagnostics)
- [ ] Verify formatting (prettier, phpcbf, stylua)
- [ ] Verify linting (eslint_d, phpcs, markdownlint)
## Phase 12.6 — Final polish
- [x] Review all keymaps and ensure they're documented (README.md + :KeymapsGuide)
- [ ] Update LOG.md with final architecture and usage
- [ ] Clean up any remaining debug code or temporary comments
---
Notes:
- Keep plugin specs minimal; configure in their own `lua/plugins/*.lua` files.
- Avoid adding plugins not listed in the guide unless explicitly requested.
- Prefer simple defaults; only add settings that clearly improve workflow.
- Plugin approval policy: unlisted plugins may be proposed, but must be explicitly confirmed before installation.
Known Issues / Follow-ups:
- lua-language-server (lua_ls) from Mason failed to start due to missing shared library `libbfd-2.38-system.so`. **RESOLVED**: Installed lua-language-server via system package manager (pacman) instead of Mason. Removed `lua_ls` from Mason's ensure_installed list in `lua/plugins/mason-lspconfig.lua`.
- LSP root detection: In some cases, the parent repository is picked as the root (e.g., when a workspace lives inside another repo). Workaround: create an empty `.git` directory (or a marker like `.nvimroot`) in the intended workspace root to pin the project root for LSPs.
Decisions & Changes:
- **Neoconf dropped**: folke/neoconf.nvim incompatible with Neovim 0.11+ vim.lsp.config API (GitHub issue #116, open since May 2024, no resolution). Migrated to native `exrc` + `secure` approach.
- **vim.lsp.config migration**: Migrated from deprecated `require('lspconfig')[server].setup()` to `vim.lsp.config()` + `vim.lsp.enable()` (Neovim 0.11+ native API).
- **Project config format**: `.nvim.lua` files return `{ lsp = { [server_name] = { settings = {...} } } }`.
- **Security model**: `secure` mode prompts user to trust `.nvim.lua` files before execution (one-time per file hash).
- **Settings loading**: `on_new_config` hook loads project settings from actual `root_dir` (not cwd), ensuring correct behavior across different workspace structures.
- **Phase 4 Navigation Strategy**: Skipped Neo-tree in favor of built-in netrw for visual context and project structure browsing. Telescope for fuzzy finding. Oil.nvim for file manipulation (evaluation phase; alternative is mini.files). Rationale: User needs visual context and preview, not per-tab roots or complex tree features. File manipulation primarily done in shell, but Oil.nvim handles buffer sync issues when renaming/moving files.

View File

@ -1,519 +0,0 @@
# Navigation Enhancement Plans
**Status**: Two complementary approaches being considered (not mutually exclusive)
Both approaches aim to reduce repetitive typing when navigating with Neovim's bracket-based pairs (`[c`, `]d`, etc.).
---
## Option 1: Repeat/Reverse Last Bracket Navigation
### Concept
Two simple commands that remember and replay the last `[x` or `]x` navigation:
- **Repeat**: Execute the same navigation again (e.g., `]d``.``.``.`)
- **Reverse**: Execute the opposite direction (e.g., after `]d`, press `,``[d`)
Similar to `;` and `,` for repeating/reversing `f/F/t/T` motions.
### User Experience
```vim
" Example 1: Scanning diagnostics
]d " Next diagnostic
. " Next diagnostic (repeat)
. " Next diagnostic (repeat)
. " Next diagnostic (repeat)
, " Previous diagnostic (reverse, oops went too far)
" Example 2: Checking spelling
]s " Next misspelling
. " Next misspelling
. " Next misspelling
" Example 3: Quickfix workflow
]q " Next quickfix item
. " Next quickfix
. " Next quickfix
```
### When This Works Best
- Repeatedly navigating **same type**: diagnostics, spelling, quickfix, location list
- Linear scanning through items of one kind
- Quick corrections when you overshoot (reverse)
### Implementation
**Module**: `lua/bracket-repeat.lua`
```lua
local M = {}
-- Track last bracket navigation
local last_nav = {
prefix = nil, -- '[' or ']'
key = nil, -- 'c', 'd', 'm', 's', 'q', etc.
}
--- Setup wrapper mappings to track bracket navigation
function M.setup()
-- Common bracket pairs to track
local pairs = {
'c', -- Git hunks (gitsigns)
'd', -- Diagnostics
's', -- Spelling
'q', -- Quickfix
'l', -- Location list
't', -- Tags
'm', -- Methods (treesitter)
'f', -- Functions (treesitter)
'p', -- Parameters (treesitter)
}
for _, key in ipairs(pairs) do
-- Wrap [x to track
vim.keymap.set('n', '[' .. key, function()
last_nav.prefix = '['
last_nav.key = key
return '[' .. key
end, { expr = true, silent = true })
-- Wrap ]x to track
vim.keymap.set('n', ']' .. key, function()
last_nav.prefix = ']'
last_nav.key = key
return ']' .. key
end, { expr = true, silent = true })
end
end
--- Repeat last bracket navigation
function M.repeat_last()
if not last_nav.prefix or not last_nav.key then
vim.notify('No bracket navigation to repeat', vim.log.levels.WARN)
return
end
vim.cmd('normal! ' .. last_nav.prefix .. last_nav.key)
end
--- Reverse last bracket navigation (flip direction)
function M.reverse_last()
if not last_nav.prefix or not last_nav.key then
vim.notify('No bracket navigation to reverse', vim.log.levels.WARN)
return
end
local opposite = last_nav.prefix == '[' and ']' or '['
vim.cmd('normal! ' .. opposite .. last_nav.key)
-- Update tracking to reflect the reversal
last_nav.prefix = opposite
end
return M
```
**Integration**: `init.lua` or `lua/keymaps.lua`
```lua
-- Setup tracking
require('bracket-repeat').setup()
-- Keybindings (choose one option)
-- Option A: Override ; and , (loses f/F/t/T repeat, but very ergonomic)
vim.keymap.set('n', ';', function() require('bracket-repeat').repeat_last() end,
{ desc = 'Repeat bracket navigation' })
vim.keymap.set('n', ',', function() require('bracket-repeat').reverse_last() end,
{ desc = 'Reverse bracket navigation' })
-- Option B: Use z prefix (keeps ; and , for f/t motions)
vim.keymap.set('n', 'z.', function() require('bracket-repeat').repeat_last() end,
{ desc = 'Repeat bracket navigation' })
vim.keymap.set('n', 'z,', function() require('bracket-repeat').reverse_last() end,
{ desc = 'Reverse bracket navigation' })
-- Option C: Use leader
vim.keymap.set('n', '<leader>.', function() require('bracket-repeat').repeat_last() end,
{ desc = 'Repeat bracket navigation' })
vim.keymap.set('n', '<leader>,', function() require('bracket-repeat').reverse_last() end,
{ desc = 'Reverse bracket navigation' })
```
### Pros & Cons
**Pros:**
- ✅ Very simple (~40 lines)
- ✅ No mode switching, stays in normal mode
- ✅ Familiar pattern (like `;`/`,` for `f`/`t`)
- ✅ Works with natural workflow
- ✅ Easy to add more tracked pairs
**Cons:**
- ⚠️ Only handles one "type" at a time (can't easily switch between `]d` and `]c`)
- ⚠️ Requires calling `.setup()` to track pairs
- ⚠️ Might want `;`/`,` for `f`/`t` repeat (depends on keybinding choice)
### Estimated Complexity
- **Total**: ~40 lines
- **Time**: 15-30 minutes
---
## Option 2: Navigation Mode (getchar Loop)
## User Experience
### Entry
- `z[` - Enter navigation mode with `[` prefix active (backward)
- `z]` - Enter navigation mode with `]` prefix active (forward)
### In Mode
- All letter keys (`a-z`, `A-Z`) get prefixed with current bracket
- Examples:
- Press `c` → executes `[c` or `]c` (git hunks)
- Press `d` → executes `[d` or `]d` (diagnostics)
- Press `m` → executes `[m` or `]m` (methods)
- Press `f` → executes `[f` or `]f` (functions)
- Press `b` → executes `[b` or `]b` (buffers)
- Press `q` → executes `[q` or `]q` (quickfix)
### Toggle Prefix
- `[` - Switch to `[` prefix (backward navigation)
- `]` - Switch to `]` prefix (forward navigation)
### Exit
- `<Esc>` - Exit navigation mode and return to normal editing
### When This Works Best
- Switching between **different types** of navigation (`]d` → `]c``]m`)
- Want visual feedback showing current direction
- Prefer a dedicated "navigation state"
- Mixed workflow: `]q` to quickfix, then multiple `]c` for hunks (stay in mode, switch keys)
### Example Workflow
```vim
" Mixed navigation scenario
z] " Enter forward navigation mode (shows: NAV ] →:)
q " Execute ]q (next quickfix)
c " Execute ]c (next hunk)
c " Execute ]c (next hunk)
[ " Toggle to backward (shows: NAV [ ←:)
c " Execute [c (previous hunk)
d " Execute [d (previous diagnostic)
<Esc> " Exit mode
```
## Technical Implementation
### Approach: getchar() Loop (No Remapping)
Instead of remapping keys, use a `while` loop with `vim.fn.getchar()` to read keypresses and manually execute the prefixed commands.
**Why this approach:**
- ✅ Zero mapping conflicts (never touches existing keymaps)
- ✅ Simpler implementation (~50-80 lines vs ~150-250)
- ✅ Built-in visual feedback via `vim.api.nvim_echo()`
- ✅ Impossible to leak state (no cleanup needed if crashed)
- ✅ Predictable behavior (each keypress isolated)
- ✅ Naturally handles special keys
**The "blocking" behavior is actually desired** - you're in a focused navigation mode, press `<Esc>` to exit anytime.
### Common Bracket Navigation Pairs
- `[c`/`]c` - Previous/next git hunk (gitsigns)
- `[d`/`]d` - Previous/next diagnostic
- `[b`/`]b` - Previous/next buffer (custom mapping)
- `[q`/`]q` - Previous/next quickfix item
- `[l`/`]l` - Previous/next location list item
- `[m`/`]m` - Previous/next method (treesitter textobjects)
- `[f`/`]f` - Previous/next function (treesitter textobjects)
- `[p`/`]p` - Previous/next parameter (treesitter textobjects)
- `[t`/`]t` - Previous/next tag
## Implementation Details
### Module Structure
Create `lua/navigation-mode.lua`:
```lua
local M = {}
--- Enter navigation mode with getchar() loop
--- @param prefix string Either '[' or ']'
function M.enter(prefix)
prefix = prefix or '['
while true do
-- Display prompt
local hl = prefix == '[' and 'DiagnosticInfo' or 'DiagnosticHint'
local arrow = prefix == '[' and '←' or '→'
vim.api.nvim_echo({{string.format('NAV %s %s: ', prefix, arrow), hl}}, false, {})
-- Get next keypress
local ok, char = pcall(vim.fn.getchar)
if not ok then break end -- Handle <C-c> gracefully
-- Convert to string
local key = type(char) == 'number' and vim.fn.nr2char(char) or char
-- Handle special keys
if key == '\27' then -- ESC
break
elseif key == '[' then
prefix = '['
elseif key == ']' then
prefix = ']'
else
-- Execute prefix + key as normal mode command
local cmd = prefix .. key
vim.cmd('normal! ' .. vim.api.nvim_replace_termcodes(cmd, true, false, true))
end
end
-- Clear prompt
vim.api.nvim_echo({{'', 'Normal'}}, false, {})
end
return M
```
**Key Points:**
- No state management needed (self-contained loop)
- `pcall(vim.fn.getchar)` handles `<C-c>` interrupts gracefully
- `vim.api.nvim_replace_termcodes()` ensures special key sequences work
- Visual feedback built into the loop (shows `NAV [ ←:` or `NAV ] →:`)
- Press `[` or `]` to toggle prefix without exiting
- Press `<Esc>` (or `<C-c>`) to exit
### Integration in keymaps.lua
```lua
-- Navigation mode
vim.keymap.set('n', 'z[', function()
require('navigation-mode').enter('[')
end, { desc = 'Enter navigation mode (backward)' })
vim.keymap.set('n', 'z]', function()
require('navigation-mode').enter(']')
end, { desc = 'Enter navigation mode (forward)' })
```
## Visual Feedback
Built into the getchar() loop:
- Shows `NAV [ ←:` (in blue) when in backward mode
- Shows `NAV ] →:` (in teal) when in forward mode
- Prompt updates immediately when toggling with `[` or `]`
- Clears when exiting with `<Esc>`
No additional statusline integration needed - the command line prompt is clear and non-intrusive.
## Design Decisions
### Mapping Conflicts: Solved
No mapping conflicts possible - getchar() reads raw input without touching keymaps.
### Buffer-local Mappings: Not Applicable
Loop executes `normal! [key` which uses whatever mappings exist naturally.
### Visual Feedback: Built-in
Command line prompt is sufficient and non-intrusive.
### Number Keys
Currently not prefixed - this allows using counts if a prefixed command accepts them.
Example: `3c` → executes `[3c` or `]3c` (may not be useful, but won't break anything)
Could add special handling if needed:
```lua
if key:match('^%d$') then
-- Handle numbers specially
end
```
### Operators (d, c, y, etc.)
In navigation mode, `d` executes `[d` (diagnostic navigation), not delete operator.
This is desired behavior - use `<Esc>` to exit and edit normally.
### Error Handling
If a command doesn't exist (e.g., `[z`), Vim will show an error but mode continues.
User can press `<Esc>` to exit or try another key.
### Pros & Cons
**Pros:**
- ✅ Switch between different navigation types easily (`c` → `d``m`)
- ✅ Visual feedback (command line prompt)
- ✅ No mapping conflicts
- ✅ Clean state (nothing to cleanup if interrupted)
- ✅ Natural for rapid mixed navigation
**Cons:**
- ⚠️ Requires mode switching (mental overhead)
- ⚠️ Blocking loop (though this is by design)
- ⚠️ Need to remember entry/exit keys
### Estimated Complexity
- **Total**: ~50-80 lines
- **Time**: 30-60 minutes
---
## Comparison & Recommendation
| Aspect | Repeat/Reverse | Navigation Mode |
|--------|---------------|----------------|
| **Best for** | Same-type scanning | Mixed navigation |
| **Complexity** | ~40 lines | ~50-80 lines |
| **Mental model** | Like `;`/`,` | New mode |
| **Typing (4× same nav)** | `]d ...` (4 keys) | `z] dddd <Esc>` (9 keys) |
| **Typing (mixed nav)** | `]d ]d ]c ]c` (8 keys) | `z] ddcc <Esc>` (9 keys) |
| **Mode switching** | No | Yes |
| **Visual feedback** | No (unless added) | Yes (built-in) |
### Use Cases
**Repeat/Reverse excels at:**
- Scanning diagnostics: `]d . . . .`
- Spell checking: `]s . . . .`
- Reviewing quickfix: `]q . . . .`
- Going back: `. . . , , ,`
**Navigation Mode excels at:**
- Mixed workflow: `]q` → multiple `]c` hunks → check `]d` diagnostic
- When you want visual confirmation of direction
- Exploring unfamiliar code (trying different navigation types)
### Implementation Strategy
**Both can coexist!** They solve slightly different problems:
1. **Start with Repeat/Reverse** (simpler, covers 80% of cases)
- Use for linear scanning (diagnostics, spelling, quickfix)
- Bind to `z.` and `z,` (or `;`/`,` if you don't use `f`/`t` repeat often)
2. **Add Navigation Mode later** (optional, for mixed navigation)
- Use when you need to rapidly switch types
- Bind to `z[` and `z]`
You'll naturally reach for whichever fits the situation better.
---
## Common Bracket Pairs Reference
- `[c`/`]c` - Previous/next git hunk (gitsigns)
- `[d`/`]d` - Previous/next diagnostic
- `[s`/`]s` - Previous/next misspelling
- `[q`/`]q` - Previous/next quickfix item
- `[l`/`]l` - Previous/next location list item
- `[m`/`]m` - Previous/next method (treesitter textobjects)
- `[f`/`]f` - Previous/next function (treesitter textobjects)
- `[p`/`]p` - Previous/next parameter (treesitter textobjects)
- `[t`/`]t` - Previous/next tag
- `[b`/`]b` - Previous/next buffer (if custom mapping exists)
---
## Testing Checklists
### Repeat/Reverse Testing
- [ ] Setup completes without errors
- [ ] `]d` followed by `.` repeats diagnostic navigation
- [ ] `]s` followed by `.` repeats spelling navigation
- [ ] `]q` followed by `.` repeats quickfix navigation
- [ ] `,` reverses last navigation direction
- [ ] Warning shown when no navigation to repeat/reverse
- [ ] Works across different buffers
- [ ] Multiple reverses work: `. . . , , ,`
### Navigation Mode Testing
- [ ] Enter mode with `z[` and `z]`
- [ ] Verify visual prompt shows `NAV [ ←:` or `NAV ] →:`
- [ ] Press `c` → navigates to previous/next git hunk
- [ ] Press `d` → navigates to previous/next diagnostic
- [ ] Press `m` → navigates to previous/next method
- [ ] Press `f` → navigates to previous/next function
- [ ] Toggle with `[` → prompt updates to `NAV [ ←:`
- [ ] Toggle with `]` → prompt updates to `NAV ] →:`
- [ ] Exit with `<Esc>` → prompt clears, back to normal mode
- [ ] Exit with `<C-c>` → handles gracefully
- [ ] Works across different buffers
- [ ] Invalid keys (e.g., `z`) show error but don't crash
- [ ] Existing keymaps still work after exit
---
## Implementation Priority
**Recommended order:**
1. **Phase 1: Repeat/Reverse** (start here)
- Simpler, faster to implement
- Covers most common use cases
- Get immediate value
2. **Phase 2: Navigation Mode** (optional, evaluate need)
- Implement only if you find yourself wanting mixed navigation
- Can be added anytime without conflicts
- Both features work together
**Time estimate:**
- Phase 1: 15-30 minutes
- Phase 2: 30-60 minutes
- Total: 45-90 minutes if both implemented
---
## Implementation Steps
### For Repeat/Reverse (Start Here)
1. **Create `lua/bracket-repeat.lua`** (~40 lines)
- Implement tracking state
- Implement `setup()`, `repeat_last()`, `reverse_last()`
2. **Add to `init.lua`**
```lua
require('bracket-repeat').setup()
```
3. **Add keymaps** (`lua/keymaps.lua`)
```lua
vim.keymap.set('n', 'z.', function() require('bracket-repeat').repeat_last() end)
vim.keymap.set('n', 'z,', function() require('bracket-repeat').reverse_last() end)
```
4. **Test** with diagnostics, spelling, quickfix
5. **Document** in README.md
### For Navigation Mode (Optional Later)
1. **Create `lua/navigation-mode.lua`** (~50 lines)
- Implement `M.enter(prefix)` with getchar() loop
- Handle ESC, `[`, `]`, and general keys
- Add visual feedback via `nvim_echo`
2. **Add keymaps** (`lua/keymaps.lua`)
```lua
vim.keymap.set('n', 'z[', function()
require('navigation-mode').enter('[')
end, { desc = 'Navigation mode (backward)' })
vim.keymap.set('n', 'z]', function()
require('navigation-mode').enter(']')
end, { desc = 'Navigation mode (forward)' })
```
3. **Test basic functionality**
- Enter with `z[` / `z]`
- Navigate with `c`, `d`, `m`, `f`, `q`
- Toggle prefix with `[` / `]`
- Exit with `<Esc>`
4. **Polish** (optional)
- Add help text on first use
- Handle `<C-c>` gracefully (already done with `pcall`)
- Consider adding common navigation cheatsheet
5. **Document**
- Update `README.md` with new keymaps
- Add navigation pairs reference

507
README.md
View File

@ -1,507 +0,0 @@
# Neovim Config Reference
Living reference for session management, keymaps, commands, and plugin-specific features in this config.
## Session Management
Session support is automatic but user-controlled:
**Manual session creation** (one-time):
```vim
:mksession # Creates Session.vim in current directory
```
**Auto-load/save** (via `lua/autocmds.lua`):
- On startup: auto-loads `Session.vim` if it exists (unless files specified on command line)
- On exit: auto-saves to `Session.vim` if it already exists
- Sessions are per-directory and opt-in (just create one to start using)
**Stop using a session**:
```bash
rm Session.vim # Won't auto-create a new one
```
## Keymaps
### `:KeymapsGuide` (`lua/keymaps_reference.lua`)
| Command | Description |
| --- | --- |
| `:KeymapsGuide` | Open a floating window with a dynamically generated list of all user-defined keymaps |
### `lua/keymaps.lua`
Core keymaps available globally (not plugin-specific). These provide fallbacks for LSP features and diagnostic navigation.
| Mode | Key | Description | Notes |
| --- | --- | --- | --- |
| n | `gd` | Vim goto-definition fallback | Executes `normal! gd` for tags/include jumps when no LSP |
| n | `gD` | Vim goto-declaration fallback | Executes `normal! gD` |
| n | `gr` | LSP references placeholder | `<Nop>` so LSP buffers can override cleanly |
| n | `gI` | LSP implementation placeholder | `<Nop>` so LSP buffers can override cleanly |
| n | `K` | Keyword help fallback | Uses `keywordprg` (e.g., `man`) when LSP hover is unavailable |
| n | `<leader>co` | Quickfix: Open | Opens quickfix window |
| n | `<leader>cc` | Quickfix: Close | Closes quickfix window |
| n | `<leader>lo` | Location list: Open | Opens location list window |
| n | `<leader>lc` | Location list: Close | Closes location list window |
| n | `<leader>xx` | Diagnostics → location list | Populates current buffer diagnostics |
| n | `<leader>xX` | Diagnostics → quickfix | Populates project-wide diagnostics |
| n | `<leader>xe` | Diagnostics → buffer errors | Location list filtered to errors |
| n | `<leader>xE` | Diagnostics → all errors | Quickfix filtered to errors |
| n | `[d` | Diagnostics: previous item | Uses `vim.diagnostic.goto_prev` |
| n | `]d` | Diagnostics: next item | Uses `vim.diagnostic.goto_next` |
| n | `<leader>xd` | Diagnostic float | Opens hover window for cursor diagnostic |
| n | `<leader>xt` | Toggle diagnostics | Flips `vim.diagnostic.enable()` |
| n | `<leader>gg` | Git: Changed files → quickfix | Lists all modified/deleted/untracked files with status |
| n | `<leader>hi` | Highlight inspector | Shows highlight/capture stack under cursor |
| n | `<leader>bg` | Toggle background (light/dark) | Switches between light and dark colorscheme modes |
### `lua/netrw-config.lua`
#### `:Ex` Command Variants
| Command | Opens netrw at |
| --- | --- |
| `:Ex` | Current working directory (`:pwd`) |
| `:Ex .` | Current file's directory |
| `:Ex %:h` | Current file's directory (explicit) |
| `:Ex /path/to/dir` | Specific path |
#### Custom Keymaps
| Mode | Key | Description |
| --- | --- | --- |
| n | `<leader>nt` | Open netrw in new tab at current file's directory |
| n | `<leader>nT` | Open netrw in new tab at project root |
| n | `<leader>nr` | Open netrw in current window at project root |
**Note:** Project root is stored as the initial working directory when Neovim starts. This allows navigation back to the project root even after following symlinks to external directories.
#### Default Netrw Keymaps
##### Navigation & View
| Key | Description |
| --- | --- |
| `<CR>` | Open file/directory under cursor |
| `-` | Go up to parent directory |
| `gh` | Toggle hidden files visibility (dotfiles) |
| `i` | Cycle through view types (thin/long/wide/tree) |
| `s` | Cycle sort order (name/time/size/extension) |
| `r` | Reverse current sort order |
| `I` | Toggle netrw banner (help text) |
##### Preview & Splits
| Key | Description |
| --- | --- |
| `p` | Preview file in horizontal split (50% window size) |
| `P` | Open file in previous window |
| `o` | Open file in horizontal split below |
| `v` | Open file in vertical split |
##### File Operations
| Key | Description |
| --- | --- |
| `%` | Create new file (prompts for name) |
| `d` | Create new directory (prompts for name) |
| `D` | Delete file/directory under cursor |
| `R` | Rename/move file under cursor |
##### Marking & Batch Operations
| Key | Description |
| --- | --- |
| `mf` | Mark file (for batch operations) |
| `mr` | Mark files using regex pattern |
| `mu` | Unmark all marked files |
| `mt` | Set mark target directory (for move/copy destination) |
| `mc` | Copy marked files to target directory |
| `mm` | Move marked files to target directory |
| `qf` | Display file info |
## Plugin Reference
### LSP `lua/plugins/lsp.lua`
#### Keymaps
Buffer-local keymaps available when an LSP client attaches:
| Mode | Key | Description |
| --- | --- | --- |
| n | `gd` | LSP: go to definition |
| n | `gr` | LSP: references |
| n | `gD` | LSP: declaration |
| n | `gI` | LSP: implementation |
| n | `K` | LSP hover |
### Conform (Formatting) `lua/plugins/conform.lua`
#### Keymaps
| Mode | Key | Description |
| --- | --- | --- |
| n | `<leader>lt` | Toggle format-on-save flag |
| n | `<leader>lf` | Format current buffer (synchronous) |
| v | `<leader>lf` | Format visual selection |
### Nvim-lint `lua/plugins/nvim-lint.lua`
#### Commands
| Command | Description |
| --- | --- |
| `:MarkdownLintEnable` | Enable automatic markdown linting (runs on BufEnter, BufWritePost, InsertLeave) |
| `:MarkdownLintDisable` | Disable automatic markdown linting and clear diagnostics |
**Note:** Markdown linting is disabled by default. Spellcheck is always enabled for markdown files.
### Gitsigns `lua/plugins/gitsigns.lua`
#### Keymaps
Buffer-local keymaps available when inside a git repository:
| Mode | Key | Description |
| --- | --- | --- |
| n | `]h` | Next hunk (`expr` mapping that respects `diff` windows) |
| n | `[h` | Previous hunk |
| n | `<leader>hs` | Stage current hunk |
| n | `<leader>hr` | Reset current hunk |
| v | `<leader>hs` | Stage visually selected range as hunk |
| v | `<leader>hr` | Reset visually selected range |
| n | `<leader>hS` | Stage entire buffer |
| n | `<leader>hu` | Undo last staged hunk |
| n | `<leader>hR` | Reset entire buffer |
| n | `<leader>hp` | Preview hunk |
| n | `<leader>hd` | Diff against index |
| n | `<leader>hD` | Diff against previous commit (`~`) |
| n | `<leader>hq` | Send all hunks to quickfix list |
| o/x | `ih` | Text object: select git hunk |
### Telescope `lua/plugins/telescope.lua`
#### Keymaps
##### Launcher mappings (normal mode)
| Key | Description |
| --- | --- |
| `<leader>ff` | Find files |
| `<leader>fg` | Live grep |
| `<leader>fb` | Open buffers picker (`<C-d>` deletes buffers in picker) |
| `<leader>fh` | Help tags |
| `<leader>fr` | Recent files (oldfiles) |
| `<leader>/` | Fuzzy search current buffer |
| `<leader>fk` / `<leader>?` | Telescope keymaps picker |
| `<leader>fc` | Commands picker |
| `<leader>fs` | LSP document symbols |
| `<leader>fS` | LSP workspace symbols |
##### Telescope prompt mappings (`defaults.mappings`)
| Mode | Key | Description |
| --- | --- | --- |
| i | `<C-n>` / `<C-j>` | Move selection down |
| i | `<C-p>` / `<C-k>` | Move selection up |
| i | `<C-c>` | Close picker |
| i | `<CR>` | Accept selection |
| i | `<C-x>` | Open selection in horizontal split |
| i | `<C-v>` | Open selection in vertical split |
| i | `<C-t>` | Open selection in tab |
| n | `<Esc>` | Close picker |
| n | `<CR>` | Accept selection |
| n | `<C-x>` | Horizontal split |
| n | `<C-v>` | Vertical split |
| n | `<C-t>` | New tab |
| n | `j` | Move selection down |
| n | `k` | Move selection up |
##### Picker-specific overrides
| Context | Mode | Key | Description |
| --- | --- | --- | --- |
| `buffers` picker | i | `<C-d>` | Delete highlighted buffer |
### Oil `lua/plugins/oil.lua`
#### Keymaps
##### Global launcher mappings
| Mode | Key | Description |
| --- | --- | --- |
| n | `<leader>fo` | Open Oil in current window |
| n | `<leader>fO` | Open Oil in floating window |
##### Oil buffer mappings (`opts.keymaps`)
| Key | Description |
| --- | --- |
| `g?` | Show Oil help |
| `<CR>` | Open entry in current window |
| `<C-s>` | Open entry in vertical split |
| `<C-h>` | Open entry in horizontal split |
| `<C-t>` | Open entry in new tab |
| `<C-p>` | Preview entry (vertical split belowright) |
| `<C-c>` | Close Oil |
| `<C-l>` | Refresh |
| `-` | Go to parent directory |
| `_` | Open Neovim cwd |
| `` ` `` | `:cd` into entry |
| `~` | `:tcd` into entry |
| `gs` | Change sort mode |
| `gx` | Open entry externally |
| `g.` | Toggle hidden files |
| `g\` | Toggle trash visibility |
### UFO (Folding) `lua/plugins/ufo.lua`
#### Keymaps
| Mode | Key | Description |
| --- | --- | --- |
| n | `zR` | Open all folds |
| n | `zM` | Close all folds |
| n | `zr` | Open folds except configured kinds |
| n | `zm` | Close folds with configured kinds |
| n | `K` | Peek fold under cursor; falls back to LSP hover |
| preview window | `<C-u>` / `<C-d>` | Scroll within UFO preview |
| preview window | `[` / `]` | Jump to top/bottom of preview |
### Undotree `lua/plugins/undotree.lua`
#### Keymaps
| Mode | Key | Description |
| --- | --- | --- |
| n | `<leader>u` | Toggle Undotree panel |
### Treesitter `lua/plugins/treesitter.lua`
#### Keymaps
##### Incremental selection
| Key | Description |
| --- | --- |
| `+` | Initialize/expand selection |
| `g+` | Expand to scope |
| `-` | Shrink selection |
##### Textobject selection (`select.keymaps`)
| Key | Target |
| --- | --- |
| `af` / `if` | Function outer / inner |
| `ac` / `ic` | Class outer / inner |
| `aa` / `ia` | Parameter outer / inner |
| `ai` / `ii` | Conditional outer / inner |
| `al` / `il` | Loop outer / inner |
| `a/` | Comment outer |
##### Textobject movement (`move` mappings)
| Key | Action |
| --- | --- |
| `]f` / `]F` | Next function start / end |
| `[f` / `[F` | Previous function start / end |
| `]c` / `]C` | Next class start / end |
| `[c` / `[C` | Previous class start / end |
| `]a` / `]A` | Next parameter start / end |
| `[a` / `[A` | Previous parameter start / end |
##### Parameter swapping
| Key | Description |
| --- | --- |
| `<leader>a` | Swap parameter with next |
| `<leader>A` | Swap parameter with previous |
### Comment.nvim `lua/plugins/comment.lua`
#### Keymaps
| Mapping | Description |
| --- | --- |
| `gcc` | Toggle line comment |
| `gbc` | Toggle block comment |
| `gc` | Operator-pending line comment |
| `gb` | Operator-pending block comment |
| `gcO` / `gco` / `gcA` | Insert comment above / below / end-of-line |
### Nvim-surround `lua/plugins/surround.lua`
#### Keymaps
| Mode | Mapping | Description |
| --- | --- | --- |
| normal | `ys{motion}{char}` | Add surround to motion |
| normal | `yss` | Surround current line |
| normal | `yS` | Surround motion with linewise behavior |
| normal | `ySS` | Surround current line linewise |
| insert | `<C-g>s` | Surround following text object |
| insert | `<C-g>S` | Surround current line |
| visual | `S` | Surround selection |
| visual line | `gS` | Surround visual-line selection |
| normal | `ds{char}` | Delete surround |
| normal | `cs{old}{new}` | Change surround |
| normal | `cS{old}{new}` | Change surround (linewise) |
### Nvim-cmp (Completion) `lua/plugins/cmp.lua`
#### Keymaps
| Mode | Key | Description |
| --- | --- | --- |
| insert | `<C-Space>` | Manually trigger completion |
| insert | `<CR>` | Confirm selection (selects first item by default) |
| insert | `<C-n>` | Next completion item |
| insert | `<C-p>` | Previous completion item |
| insert | `<C-e>` | Abort completion menu |
### Nvim-autopairs `lua/plugins/autopairs.lua`
#### Keymaps
| Mode | Key | Description |
| --- | --- | --- |
| insert | `<M-e>` | Fast wrap the previous text with a pair |
## Configuration
### Formatting & Linting
This config uses **conform.nvim** for formatting and **nvim-lint** for diagnostics. Both prefer project-local tools over global installations.
#### Executable Resolution Order
When looking for formatters/linters, the config searches in this priority:
1. **Project-local** (e.g., `node_modules/.bin/`, `vendor/bin/`)
2. **Mason-managed** (`~/.local/share/nvim/mason/bin/`)
3. **System PATH** (globally installed tools)
Conform.nvim handles this resolution automatically via its built-in `util.find_executable()`. nvim-lint uses a custom `find_executable()` helper in `lua/plugins/nvim-lint.lua`.
#### Configured Tools
**Formatters** (`lua/plugins/conform.lua`):
- **JavaScript/TypeScript/CSS/JSON/HTML/Markdown**: `prettier`
- **PHP**: `phpcbf` (WordPress standard by default)
- **Lua**: `stylua`
- **Python**: handled by `ruff` LSP (not conform)
**Linters** (`lua/plugins/nvim-lint.lua`):
- **JavaScript/TypeScript**: `eslint_d`
- **PHP**: `phpcs` (WordPress standard by default)
- **Markdown**: `markdownlint` (disabled by default; use `:MarkdownLintEnable`)
- **Python**: handled by `ruff` LSP (not nvim-lint)
#### Project-Specific Overrides
Both formatters and linters respect project-level configuration files:
**PHP** (phpcbf + phpcs):
- Create `phpcs.xml` or `phpcs.xml.dist` in your project root
- When present, this file controls the coding standard (e.g., PSR-12, WordPress, custom)
- When absent, defaults to `--standard=WordPress`
Example `phpcs.xml`:
```xml
<?xml version="1.0"?>
<ruleset name="Custom">
<description>Project-specific PHP rules</description>
<rule ref="PSR12"/>
<!-- or: <rule ref="WordPress-Core"/> -->
<!-- Customize rules -->
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
</properties>
</rule>
</ruleset>
```
**JavaScript/TypeScript** (prettier + eslint):
- Project config files: `.prettierrc`, `.eslintrc.json`, `eslint.config.js`, etc.
- Global fallback: `~/.eslintrc.json` (when no project config exists)
**Lua** (stylua):
- Project config: `stylua.toml` or `.stylua.toml`
- Falls back to stylua defaults if not present
**Markdown** (markdownlint):
- Project config: `.markdownlint.json`, `.markdownlintrc`
#### Changing Standards Per-Project
To switch a PHP project from WordPress to PSR-12:
1. Create `phpcs.xml` in project root:
```xml
<?xml version="1.0"?>
<ruleset name="PSR-12">
<rule ref="PSR12"/>
</ruleset>
```
2. Restart Neovim (or reload config: `:source $MYVIMRC`)
Both `phpcs` and `phpcbf` will now use PSR-12 rules in that project.
#### Format-on-Save
- **Enabled by default** for all supported filetypes
- **Toggle**: `<leader>lt` (see keymaps below)
- **Manual format**: `<leader>lf` (buffer or visual selection)
#### Linting Behavior
- **Automatic**: Runs on `BufEnter`, `BufWritePost`, `InsertLeave`
- **Per-filetype**: Configured in `lint.linters_by_ft` table
- **Markdown**: Opt-in only (`:MarkdownLintEnable` / `:MarkdownLintDisable`)
## Configuration Options
### Tabline Display
Custom tabline shows intelligent path shortening with two configurable options.
**Location:** `lua/settings.lua` (configured via `utils.tabline_full_parents` and `utils.tabline_shorten_length`)
**Configuration Options:**
1. **`utils.tabline_full_parents`** (default: `1`) - Number of parent directories to show in full
2. **`utils.tabline_shorten_length`** (default: `3`) - Number of characters to show for shortened directories
**Examples:**
With `full_parents = 1, shorten_length = 3`:
- `/path/to/my/project/src/file.txt``pat/to/my/project/src/file.txt`
With `full_parents = 2, shorten_length = 3`:
- `/path/to/my/project/src/file.txt``pat/to/my/project/src/file.txt`
With `full_parents = 1, shorten_length = 1`:
- `/path/to/my/project/src/file.txt``p/t/m/project/src/file.txt`
With `full_parents = 0, shorten_length = 3`:
- `/path/to/my/project/src/file.txt``pat/to/my/pro/src/file.txt`
The last N parent directories are shown in full, earlier directories are shortened to the specified number of characters. The filename itself is always shown in full.
**To customize:** Edit `utils.tabline_full_parents` and `utils.tabline_shorten_length` values in `lua/settings.lua`
## Working Directory Behavior
The working directory (`:pwd`) is locked to the directory where you opened Neovim and does NOT change when switching files:
- `autochdir` is disabled (working directory stays fixed at project root)
- `netrw_keepdir = 1` prevents netrw from changing directory when browsing
- Project root is captured at startup in `vim.g.project_root` (used by Telescope and netrw)
- Telescope searches from the initial project root regardless of current buffer
- Use `:cd <path>` to manually change directory if needed (affects Telescope search scope)

View File

@ -1,129 +0,0 @@
# Critical Regression Fix: LSP Project Settings Not Loading (2025-12-07)
## Summary
`gd` (goto definition) stopped working for external library references in `WORKSPACE_TEST/site/external.php`. Even restoring to a git commit where it previously worked didn't fix it, indicating an **environment change** rather than a code regression.
## Root Cause
**Neovim 0.11+ removed support for the `on_new_config` callback in `vim.lsp.config()`.**
The previous implementation used `on_new_config` to load project-local settings from `.nvim.lua`:
```lua
vim.lsp.config(server_name, {
on_new_config = function(new_config, root_dir)
-- Load project settings and merge into new_config.settings
local project_settings = load_project_lsp_settings(server_name, root_dir)
if project_settings.settings then
new_config.settings = vim.tbl_deep_extend("force",
new_config.settings or {},
project_settings.settings)
end
end,
})
```
**Problem**: This callback was **silently ignored** in Neovim 0.11+, resulting in:
- Empty `client.settings` (confirmed via `:LspInfo` showing `Settings: {}`)
- No `includePaths` sent to intelephense
- `gd` failing with "No locations found" for external library symbols
## Symptoms
1. `:LspInfo` showed `Settings: {}` for intelephense
2. `gd` on `\ExtLib\Src\Util::greetExternal()` returned "No locations found"
3. Local (workspace) symbol navigation worked fine
4. Git history restore didn't help (environment issue, not code issue)
## Solution
Use the `LspAttach` autocmd instead of `on_new_config` to load and apply project settings:
```lua
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('lsp_project_settings', { clear = true }),
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if not client then return end
-- Load project-specific settings from .nvim.lua at client.root_dir
local project_settings = load_project_lsp_settings(client.name, client.root_dir)
if project_settings.settings then
-- Merge into client.settings
client.settings = vim.tbl_deep_extend("force",
client.settings or {},
project_settings.settings)
-- Notify server of updated settings
vim.schedule(function()
client.notify("workspace/didChangeConfiguration",
{ settings = client.settings })
end)
end
end,
})
```
## Validation
After fix:
```bash
cd WORKSPACE_TEST
nvim --headless +'e site/external.php' +'lua vim.wait(3000); local clients = vim.lsp.get_clients({name="intelephense"}); if clients[1] then print("Settings:", vim.inspect(clients[1].settings)) end' +quit
```
Output:
```lua
Settings: {
intelephense = {
environment = {
includePaths = { "/home/ray/.config/nvim/EXTERNAL_TEST" }
}
}
}
```
✅ Settings now correctly loaded and `gd` works for external symbols.
## Key Differences: Neovim 0.10 vs 0.11 LSP API
### Old API (lspconfig plugin, Neovim < 0.11)
```lua
require('lspconfig').intelephense.setup({
on_new_config = function(new_config, root_dir)
-- Called when creating new client
end,
})
```
### New API (native vim.lsp.config, Neovim 0.11+)
```lua
-- Define config
vim.lsp.config('intelephense', { ... })
-- Enable server
vim.lsp.enable('intelephense')
-- Handle dynamic settings via autocmd
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
-- Access client, load settings, notify server
end,
})
```
## Documentation Updated
- `LOG.md`: Added decision log entry for 2025-12-07
- `.github/copilot-instructions.md`: Now a simple pointer to AGENTS.md
- `AGENTS.md`: Already notes "Native exrc + vim.lsp.config migration done"
## Prevention
- Always test LSP functionality after Neovim version upgrades
- Check `:help news` for API changes
- Validate `:LspInfo` settings are populated correctly
- Test with external library references, not just workspace files
## Files Modified
- `lua/plugins/lsp.lua`: Replaced `on_new_config` with `LspAttach` autocmd
## Timeline
- **Before 2025-12-07**: `on_new_config` worked in older Neovim version
- **2025-12-07**: Neovim upgraded to 0.11.5, `on_new_config` silently ignored
- **2025-12-07**: Diagnosed via `:LspInfo` showing empty settings
- **2025-12-07**: Fixed with `LspAttach` pattern, verified working

View File

@ -1,93 +0,0 @@
# LSP Diagnostic Test Files
These files are designed to trigger LSP diagnostics of all types to test the Paper Tonic Modern colorscheme's diagnostic virtual text colors.
## Test Files
### test-diagnostics.lua
Lua file with intentional errors for lua_ls (Lua Language Server).
**Expected diagnostics:**
- **Errors** (red): Undefined variables/functions, type mismatches, invalid operations, wrong argument counts
- **Warnings** (orange): Unused variables, shadowing, unreachable code, lowercase globals
- **Hints** (green): Redundant code, style issues
**Note**: lua_ls provides the best mix of diagnostic levels.
### test-diagnostics.php
PHP file with intentional errors for Intelephense (PHP Language Server).
**Expected diagnostics:**
- **Errors** (red): Most issues appear as errors by default in Intelephense
- Undefined functions/variables
- Type mismatches
- Wrong argument counts
- Invalid method calls
**Note**: Intelephense is strict and reports most issues as errors. To see warnings/hints, configure in `.nvim.lua`:
```lua
return {
lsp = {
intelephense = {
settings = {
intelephense = {
diagnostics = {
undefinedSymbols = true,
undefinedVariables = true,
unusedSymbols = "hint", -- Show unused as hints
}
}
}
}
}
}
```
### test-diagnostics.js
JavaScript file with intentional errors for ts_ls (TypeScript Language Server).
**Expected diagnostics:**
- **Errors** (red): Undefined variables/functions, invalid property access, missing parameters
- **Warnings** (orange): Unreachable code, type comparison issues, duplicate cases
- **Hints** (green): const vs let suggestions
- **Info** (blue): Unnecessary operations
## How to Use
1. Open any test file in Neovim:
```bash
nvim test-diagnostics.lua
# or
nvim test-diagnostics.php
# or
nvim test-diagnostics.js
```
2. Wait for LSP to attach and analyze the file (1-2 seconds)
3. Check the virtual text diagnostics at the end of lines with issues
4. Use `:LspInfo` to verify the LSP server is attached
## Color Reference
The Paper Tonic Modern colorscheme uses these colors for diagnostics:
- **DiagnosticError** (virtual text): `#d70000` (bright red) on `#ffefef` background
- **DiagnosticWarn** (virtual text): `#d75f00` (orange) on `#ece0e0` background
- **DiagnosticInfo** (virtual text): `#8989af` (blue) on `#e0e0ec` background
- **DiagnosticHint** (virtual text): `#89af89` (green) on `#e0ece0` background
## Testing Checklist
- [ ] Errors show in red with red undercurl
- [ ] Warnings show in orange with orange undercurl
- [ ] Hints show in green
- [ ] Info messages show in blue
- [ ] Virtual text is readable against light background
- [ ] Sign column icons show correct colors
- [ ] Diagnostic floating windows display correctly
## Debug Command
Use the `<leader>hi` keymap to inspect highlight info under cursor and verify diagnostic colors are applied correctly.

View File

@ -1,62 +0,0 @@
-- PHP-specific settings and enhancements
-- Loaded automatically for PHP files
-- Enable includeexpr for intelligent gf (goto file) behavior
-- Handles common WordPress/PHP path patterns like:
-- require_once __DIR__ . '/../path/to/file.php';
-- include __FILE__ . '/relative/path.php';
vim.opt_local.includeexpr = "v:lua.resolve_php_gf_path(v:fname)"
-- Global function to resolve PHP file paths for gf command
function _G.resolve_php_gf_path(fname)
local line = vim.api.nvim_get_current_line()
local current_dir = vim.fn.expand('%:p:h')
local current_file = vim.fn.expand('%:p')
-- Pattern 1: __DIR__ . '/path' or __DIR__ . '/../path'
-- Most common in modern PHP/WordPress
local path = line:match("__DIR__%s*%.%s*['\"]([^'\"]+)['\"]")
if path then
path = current_dir .. path
return vim.fn.simplify(path)
end
-- Pattern 2: __FILE__ . '/path'
-- Less common but appears in legacy WordPress code
-- __FILE__ resolves to the current file's full path, so we use its directory
path = line:match("__FILE__%s*%.%s*['\"]([^'\"]+)['\"]")
if path then
-- __FILE__ is the file itself, but when concatenated with a path,
-- it's typically used like __DIR__, so we use the directory
path = current_dir .. path
return vim.fn.simplify(path)
end
-- Pattern 3: dirname(__FILE__) . '/path'
-- Old-style equivalent to __DIR__
path = line:match("dirname%s*%(%s*__FILE__%s*%)%s*%.%s*['\"]([^'\"]+)['\"]")
if path then
path = current_dir .. path
return vim.fn.simplify(path)
end
-- Pattern 4: Simple quoted path in require/include statements
-- Fallback for basic relative paths
path = line:match("require[_%w]*%s*['\"]([^'\"]+)['\"]")
if path then
-- Try relative to current file's directory
path = current_dir .. '/' .. path
return vim.fn.simplify(path)
end
path = line:match("include[_%w]*%s*['\"]([^'\"]+)['\"]")
if path then
-- Try relative to current file's directory
path = current_dir .. '/' .. path
return vim.fn.simplify(path)
end
-- Fallback: return as-is (normal gf behavior)
-- This allows standard gf to work for simple filenames
return fname
end

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,42 +1,18 @@
;extends (id_selector (id_name) @CssIdentifier)
(id_selector (id_name)) @CssIdSelector
; CSS Selectors - ID selectors (#my-id)
; Priority: Must override default @constant capture
(id_selector "#" @punctuation.delimiter)
(id_selector (id_name) @CssIdentifier (#set! priority 200))
; CSS Selectors - Class selectors (.my-class)
(class_selector "." @punctuation.delimiter)
(class_selector (class_name) @CssClassName (#set! priority 200))
; CSS Selectors - Pseudo-class selectors (:hover, :focus, etc.)
(pseudo_class_selector ":" @punctuation.delimiter)
(pseudo_class_selector (class_name) @cssPseudoClass (#set! priority 200))
; CSS Selectors - Pseudo-element selectors (::before, ::after)
(pseudo_element_selector "::" @punctuation.delimiter)
(pseudo_element_selector (tag_name) @cssPseudoElement (#set! priority 200))
; CSS Selectors - Nesting selector (&)
(nesting_selector) @cssNestingSelector
; CSS Selectors - Universal selector (*)
(universal_selector) @CssUniversalSelector
; CSS Selectors - Tag/element selectors (div, p, etc.)
(tag_name) @HtmlTagName (tag_name) @HtmlTagName
; CSS Properties (class_selector (class_name) @CssClassName)
((property_name) @CssProp) (selectors (pseudo_class_selector (class_name) @cssPseudoClass))
(nesting_selector) @cssNestingSelector
; CSS Property values ; need to find out how to make this more specific?
(declaration (property_name) (_) @CssPropertyValue) (universal_selector) @CssUniversalSelector
((property_name) (_)) @CssProp
; CSS Units (px, em, rem, %, etc.)
(unit) @CssUnit (unit) @CssUnit
; CSS Media queries (declaration (property_name) (_) @CssPropertyValue)
(media_statement
(feature_query (media_statement (feature_query (feature_name) @cssMediaFeatureName (_ (unit) @cssMediaQueryValueUnit) @cssMediaQueryValue) @cssMediaQuery)
(feature_name) @cssMediaFeatureName
(_ (unit) @cssMediaQueryValueUnit) @cssMediaQueryValue) @cssMediaQuery)

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,20 +1,17 @@
;extends (start_tag
(attribute
(attribute_name) @ClassNameAttribute (#eq? @ClassNameAttribute "class")
(quoted_attribute_value
(attribute_value) @CssClassName )))
; HTML class attribute values - should match CSS .class-name color (start_tag
; Priority: Must override default @string capture (priority 99) (attribute
(attribute (attribute_name) @IdAttribute (#eq? @IdAttribute "id")
(attribute_name) @ClassNameAttribute (#eq? @ClassNameAttribute "class") (quoted_attribute_value
(quoted_attribute_value (attribute_value) @CssIdentifier )))
(attribute_value) @CssClassName (#set! priority 200)))
; HTML id attribute values - should match CSS #id-name color (start_tag
(attribute (attribute
(attribute_name) @IdAttribute (#eq? @IdAttribute "id") (attribute_name) @DataAttribute (#match? @DataAttribute "^data-")
(quoted_attribute_value (quoted_attribute_value
(attribute_value) @CssIdentifier (#set! priority 200))) (attribute_value) @DataAttributeValue )))
; HTML data-* attributes
(attribute
(attribute_name) @DataAttribute (#match? @DataAttribute "^data-")
(quoted_attribute_value
(attribute_value) @DataAttributeValue (#set! priority 200)))

View File

@ -1,8 +0,0 @@
;; extends
;; Fold comment blocks (including consecutive single-line comments)
(comment) @fold
;; Fold consecutive single-line comments as a block
((comment) @fold
(#match? @fold "^//"))

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks (including /** */ docblocks)
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,4 +0,0 @@
;; extends
;; Fold comment blocks
(comment) @fold

View File

@ -1,20 +0,0 @@
-- Paper Tonic Modern
-- A paper-like colorscheme for Neovim supporting both light and dark modes
-- Forked from the original Paper Tonic with modern Lua implementation
-- Reset highlights and syntax
vim.cmd('highlight clear')
if vim.fn.exists('syntax_on') then
vim.cmd('syntax reset')
end
-- Set colorscheme name
vim.g.colors_name = 'paper-tonic-modern'
-- Set default background if not already set
if vim.o.background == '' then
vim.o.background = 'light'
end
-- Load the colorscheme
require('paper-tonic-modern').load()

View File

@ -1,44 +0,0 @@
// ESLint 9+ flat config format (global default)
// See: https://eslint.org/docs/latest/use/configure/configuration-files
export default [
{
files: ["**/*.js", "**/*.mjs", "**/*.cjs"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
// Browser globals
console: "readonly",
window: "readonly",
document: "readonly",
navigator: "readonly",
// Node.js globals
process: "readonly",
__dirname: "readonly",
__filename: "readonly",
require: "readonly",
module: "readonly",
exports: "readonly",
global: "readonly",
Buffer: "readonly",
},
},
rules: {
// Recommended rules
"no-undef": "error",
"no-unused-vars": "warn",
"no-unreachable": "warn",
"no-constant-condition": "error",
"no-duplicate-case": "error",
// Best practices
"eqeqeq": ["warn", "always"],
"no-implicit-globals": "warn",
// Style (minimal)
"semi": ["warn", "always"],
"quotes": ["warn", "double", { "avoidEscape": true }],
},
},
];

View File

@ -1,28 +0,0 @@
-- Leader keys early
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '
-- Load basic settings, keymaps, autocmds (kept minimal for now)
require('settings')
require('netrw-config')
require('keymaps')
require('autocmds')
require('abbreviations')
require('keymaps_reference')
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
'git', 'clone', '--filter=blob:none', '--single-branch',
'https://github.com/folke/lazy.nvim.git', lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
-- Setup plugin system (plugins will be added incrementally)
require('lazy').setup({
spec = {
{ import = 'plugins' },
},
})

View File

@ -1,31 +0,0 @@
{
"Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" },
"LuaSnip": { "branch": "master", "commit": "3732756842a2f7e0e76a7b0487e9692072857277" },
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
"cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
"conform.nvim": { "branch": "master", "commit": "238f542a118984a88124fc915d5b981680418707" },
"copilot-cmp": { "branch": "master", "commit": "15fc12af3d0109fa76b60b5cffa1373697e261d1" },
"copilot.lua": { "branch": "master", "commit": "5ace9ecd0db9a7a6c14064e4ce4ede5b800325f3" },
"gitsigns.nvim": { "branch": "main", "commit": "42d6aed4e94e0f0bbced16bbdcc42f57673bd75e" },
"indent-blankline.nvim": { "branch": "master", "commit": "005b56001b2cb30bfa61b7986bc50657816ba4ba" },
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "fe661093f4b05136437b531e7f959af2a2ae66c8" },
"mason-tool-installer.nvim": { "branch": "main", "commit": "517ef5994ef9d6b738322664d5fdd948f0fdeb46" },
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
"nvim-autopairs": { "branch": "master", "commit": "c2a0dd0d931d0fb07665e1fedb1ea688da3b80b4" },
"nvim-cmp": { "branch": "main", "commit": "85bbfad83f804f11688d1ab9486b459e699292d6" },
"nvim-lint": { "branch": "master", "commit": "ca6ea12daf0a4d92dc24c5c9ae22a1f0418ade37" },
"nvim-lspconfig": { "branch": "master", "commit": "92ee7d42320edfbb81f3cad851314ab197fa324a" },
"nvim-surround": { "branch": "main", "commit": "1098d7b3c34adcfa7feb3289ee434529abd4afd1" },
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" },
"nvim-treesitter-textobjects": { "branch": "master", "commit": "5ca4aaa6efdcc59be46b95a3e876300cfead05ef" },
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" },
"nvim-ufo": { "branch": "main", "commit": "ab3eb124062422d276fae49e0dd63b3ad1062cfc" },
"oil.nvim": { "branch": "master", "commit": "d278dc40f9de9980868a0a55fa666fba5e6aeacb" },
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
"promise-async": { "branch": "main", "commit": "119e8961014c9bfaf1487bf3c2a393d254f337e2" },
"telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" },
"telescope.nvim": { "branch": "0.1.x", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" },
"undotree": { "branch": "master", "commit": "178d19e00a643f825ea11d581b1684745d0c4eda" }
}

Some files were not shown because too many files have changed in this diff Show More