Compare commits

..

No commits in common. "master" and "modern-restart" have entirely different histories.

41 changed files with 466 additions and 1804 deletions

View File

@ -43,7 +43,7 @@ This repository is being migrated to a modern, minimal Neovim setup driven by Lu
├── oil.lua
├── ufo.lua
├── gitsigns.lua
├── conform.lua
├── none-ls.lua
├── nvim-lint.lua
├── mason.lua
├── mason-lspconfig.lua
@ -87,7 +87,7 @@ This repository is being migrated to a modern, minimal Neovim setup driven by Lu
- 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`.
- Formatting/Linting: `none-ls.nvim`, `nvim-lint`.
- LSP Management: `mason.nvim`, `mason-lspconfig.nvim`, `mason-tool-installer.nvim`.
## Workflow Requirements to Preserve
@ -115,7 +115,7 @@ This repository is being migrated to a modern, minimal Neovim setup driven by Lu
- ✅ 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 9: Formatting & Linting (none-ls, nvim-lint, Mason tool installer)
- ✅ Phase 10: Migrate kept behaviors (abbreviations, templates, custom Treesitter queries)
- ⏸️ Phase 11: Cleanup & validation (pending)
@ -201,7 +201,6 @@ Was caused by settings loading at wrong time; fixed by using `LspAttach` autocmd
- 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.

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

15
LOG.md
View File

@ -9,9 +9,6 @@ Authoritative notes for the Neovim migration. Use this alongside `MIGRATION_PLAN
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).
@ -29,7 +26,6 @@ Record every decision here with a short rationale. Append new entries; do not re
- 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)
@ -73,7 +69,7 @@ Record every decision here with a short rationale. Append new entries; do not re
- **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)
- **Linting split**: none-ls for formatting only, nvim-lint for diagnostics (none-ls removed linters)
- **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)
@ -82,20 +78,11 @@ Record every decision here with a short rationale. Append new entries; do not re
- 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

View File

@ -24,7 +24,6 @@ Source of truth for the step-by-step rebuild. Keep this concise and up to date.
## 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
@ -275,13 +274,14 @@ Source of truth for the step-by-step rebuild. Keep this concise and up to date.
- [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)
## Phase 9.2 — none-ls setup with project-aware executables
- [x] Add `nvimtools/none-ls.nvim`
- [x] Create helper function to detect project-local executables (node_modules/.bin/, vendor/bin/, Mason bin)
- [x] Configure formatters:
- [x] prettier (project-local first, then Mason, then global)
- [x] phpcbf (project-local first, then global system install - already available)
- [x] stylua (Mason installed)
- [x] Add `mfussenegger/nvim-lint` for linting (none-ls removed most linters from builtins)
- [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)
@ -289,9 +289,8 @@ Source of truth for the step-by-step rebuild. Keep this concise and up to date.
- [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] Search order: project node_modules/.bin/ → project vendor/bin/ → Mason bin → 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
@ -336,12 +335,6 @@ Source of truth for the step-by-step rebuild. Keep this concise and up to date.
- [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
@ -480,7 +473,6 @@ Source of truth for the step-by-step rebuild. Keep this concise and up to date.
- [ ] 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

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

207
README.md
View File

@ -40,10 +40,6 @@ Core keymaps available globally (not plugin-specific). These provide fallbacks f
| 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 |
@ -52,74 +48,14 @@ Core keymaps available globally (not plugin-specific). These provide fallbacks f
| 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 |
| n | `<leader>te` | Open netrw in a new tab rooted at current file directory |
| n | `<leader>tE` | Open netrw in a new tab rooted at project cwd |
## Plugin Reference
@ -137,7 +73,7 @@ Buffer-local keymaps available when an LSP client attaches:
| n | `gI` | LSP: implementation |
| n | `K` | LSP hover |
### Conform (Formatting) `lua/plugins/conform.lua`
### None-ls (Formatting) `lua/plugins/none-ls.lua`
#### Keymaps
@ -178,7 +114,6 @@ Buffer-local keymaps available when inside a git repository:
| 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`
@ -369,139 +304,3 @@ Buffer-local keymaps available when inside a git repository:
| 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,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,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,5 +1,5 @@
-- Paper Tonic Modern
-- A paper-like colorscheme for Neovim supporting both light and dark modes
-- A light, paper-like colorscheme for Neovim
-- Forked from the original Paper Tonic with modern Lua implementation
-- Reset highlights and syntax
@ -11,10 +11,8 @@ 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
-- Set background to light
vim.o.background = 'light'
-- Load the colorscheme
require('paper-tonic-modern').load()

View File

@ -4,28 +4,28 @@
"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" },
"copilot.lua": { "branch": "master", "commit": "efe563802a550b7f1b7743b007987e97cba22718" },
"gitsigns.nvim": { "branch": "main", "commit": "5813e4878748805f1518cee7abb50fd7205a3a48" },
"indent-blankline.nvim": { "branch": "master", "commit": "005b56001b2cb30bfa61b7986bc50657816ba4ba" },
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "fe661093f4b05136437b531e7f959af2a2ae66c8" },
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "0b9bb925c000ae649ff7e7149c8cd00031f4b539" },
"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" },
"mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" },
"none-ls.nvim": { "branch": "main", "commit": "5abf61927023ea83031753504adb19630ba80eef" },
"nvim-autopairs": { "branch": "master", "commit": "7a2c97cccd60abc559344042fefb1d5a85b3e33b" },
"nvim-cmp": { "branch": "main", "commit": "d97d85e01339f01b842e6ec1502f639b080cb0fc" },
"nvim-lint": { "branch": "master", "commit": "ebe535956106c60405b02220246e135910f6853d" },
"nvim-lspconfig": { "branch": "master", "commit": "9c923997123ff9071198ea3b594d4c1931fab169" },
"nvim-surround": { "branch": "main", "commit": "fcfa7e02323d57bfacc3a141f8a74498e1522064" },
"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" },
"nvim-ufo": { "branch": "main", "commit": "72d54c31079d38d8dfc5456131b1d0fb5c0264b0" },
"oil.nvim": { "branch": "master", "commit": "cbcb3f997f6f261c577b943ec94e4ef55108dd95" },
"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" }
"undotree": { "branch": "master", "commit": "0f1c9816975b5d7f87d5003a19c53c6fd2ff6f7f" }
}

View File

@ -82,9 +82,6 @@ vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = "qf", -- Applies to both quickfix and location list
callback = function()
-- Disable spell checking in quickfix/location list windows
vim.opt_local.spell = false
-- Use matchadd() for higher priority highlighting
-- This will override the default qf syntax
vim.fn.matchadd("qfError", "\\<error\\>", 10)
@ -109,34 +106,18 @@ local session_aug = vim.api.nvim_create_augroup("SessionManagement", { clear = t
vim.api.nvim_create_autocmd("VimEnter", {
group = session_aug,
pattern = "*",
once = true,
nested = true, -- Allow other autocmds to fire after loading session
callback = function()
-- Only auto-load if:
-- 1. Session.vim exists in cwd
-- 2. No files were specified on command line (vim.fn.argc() == 0)
-- 3. Not started with nvim -S (check if we already loaded a session)
if vim.fn.argc() == 0 and vim.fn.filereadable("Session.vim") == 1 and vim.v.this_session == "" then
vim.cmd("silent! source Session.vim")
vim.cmd("source Session.vim")
end
end,
})
-- Ensure filetype is detected for current buffer after session load
vim.api.nvim_create_autocmd("SessionLoadPost", {
group = session_aug,
pattern = "*",
callback = function()
-- Trigger BufReadPost for all loaded buffers to ensure Treesitter/LSP attach
vim.schedule(function()
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == "" then
vim.api.nvim_exec_autocmds("BufReadPost", { buffer = buf })
end
end
end)
end,
})
-- Auto-save session on exit, but ONLY if Session.vim already exists
vim.api.nvim_create_autocmd("VimLeavePre", {
group = session_aug,

View File

@ -34,12 +34,6 @@ map('n', 'K', function()
end
end, { desc = 'Vim: Hover/Help (keywordprg fallback)', silent = true })
-- Quickfix and Location list keymaps
map('n', '<leader>co', '<cmd>copen<cr>', { desc = 'Quickfix: Open', silent = true })
map('n', '<leader>cc', '<cmd>cclose<cr>', { desc = 'Quickfix: Close', silent = true })
map('n', '<leader>lo', '<cmd>lopen<cr>', { desc = 'Location list: Open', silent = true })
map('n', '<leader>lc', '<cmd>lclose<cr>', { desc = 'Location list: Close', silent = true })
-- Diagnostic keymaps
map('n', '<leader>xx', vim.diagnostic.setloclist, { desc = 'Diagnostics: Buffer diagnostics (location list)', silent = true })
map('n', '<leader>xX', vim.diagnostic.setqflist, { desc = 'Diagnostics: All diagnostics (quickfix)', silent = true })
@ -49,39 +43,144 @@ end, { desc = 'Diagnostics: Buffer errors (location list)', silent = true })
map('n', '<leader>xE', function()
vim.diagnostic.setqflist({ severity = vim.diagnostic.severity.ERROR })
end, { desc = 'Diagnostics: All errors (quickfix)', silent = true })
map('n', '[d', function()
vim.diagnostic.goto_prev({ float = false })
end, { desc = 'Diagnostics: Previous diagnostic', silent = true })
map('n', ']d', function()
vim.diagnostic.goto_next({ float = false })
end, { desc = 'Diagnostics: Next diagnostic', silent = true })
map('n', '<leader>xd', function()
pcall(vim.diagnostic.open_float)
end, { desc = 'Diagnostics: Show diagnostic under cursor', silent = true })
map('n', '[d', vim.diagnostic.goto_prev, { desc = 'Diagnostics: Previous diagnostic', silent = true })
map('n', ']d', vim.diagnostic.goto_next, { desc = 'Diagnostics: Next diagnostic', silent = true })
map('n', '<leader>xd', vim.diagnostic.open_float, { desc = 'Diagnostics: Show diagnostic under cursor', silent = true })
map('n', '<leader>xt', function()
vim.diagnostic.enable(not vim.diagnostic.is_enabled())
end, { desc = 'Diagnostics: Toggle display', silent = true })
-- Git: Modified, deleted, and untracked files to quickfix
map('n', '<leader>gg', function()
local utils = require('utils')
local qf_list = utils.git_changed_files()
if qf_list then
vim.fn.setqflist(qf_list, 'r')
vim.cmd('copen')
end
end, { desc = 'Git: Changed files to quickfix (with status)', silent = true })
-- Debug: Show highlight group and color under cursor
map('n', '<leader>hi', function()
require('utils').show_highlight_info()
end, { desc = 'Debug: Show highlight group and color under cursor', silent = true })
local cursor_pos = vim.api.nvim_win_get_cursor(0)
local row, col = cursor_pos[1] - 1, cursor_pos[2]
-- UI: Toggle background (light/dark)
map('n', '<leader>bg', function()
if vim.o.background == 'dark' then
vim.o.background = 'light'
else
vim.o.background = 'dark'
-- Get all highlight groups at cursor position
local ts_hl = vim.treesitter.get_captures_at_pos(0, row, col)
local synID = vim.fn.synID(row + 1, col + 1, 1)
local synName = vim.fn.synIDattr(synID, 'name')
local synTrans = vim.fn.synIDattr(vim.fn.synIDtrans(synID), 'name')
-- Helper to resolve highlight links
local function resolve_hl(name)
local hl = vim.api.nvim_get_hl(0, { name = name })
local max_depth = 10
local depth = 0
while hl.link and depth < max_depth do
name = hl.link
hl = vim.api.nvim_get_hl(0, { name = name })
depth = depth + 1
end
return hl, name
end
end, { desc = 'UI: Toggle background (light/dark)', silent = true })
local lines = {
'=== Highlight Info Under Cursor ===',
'',
'Position: row=' .. row .. ' col=' .. col,
'',
}
-- TreeSitter captures
if #ts_hl > 0 then
table.insert(lines, 'TreeSitter Captures:')
for _, capture in ipairs(ts_hl) do
local cap_name = '@' .. capture.capture
local hl, resolved_name = resolve_hl(cap_name)
table.insert(lines, string.format(' %s', cap_name))
if resolved_name ~= cap_name then
table.insert(lines, string.format(' → resolves to: %s', resolved_name))
end
if hl.fg then
table.insert(lines, string.format(' fg: #%06x', hl.fg))
end
if hl.bg then
table.insert(lines, string.format(' bg: #%06x', hl.bg))
end
local styles = {}
if hl.bold then table.insert(styles, 'bold') end
if hl.italic then table.insert(styles, 'italic') end
if hl.underline then table.insert(styles, 'underline') end
if #styles > 0 then
table.insert(lines, ' style: ' .. table.concat(styles, ', '))
end
end
table.insert(lines, '')
end
-- Syntax group
if synName ~= '' then
table.insert(lines, 'Syntax Group: ' .. synName)
if synTrans ~= synName and synTrans ~= '' then
table.insert(lines, 'Translates to: ' .. synTrans)
end
local hl, resolved_name = resolve_hl(synTrans ~= '' and synTrans or synName)
if hl.fg then
table.insert(lines, string.format(' fg: #%06x', hl.fg))
end
if hl.bg then
table.insert(lines, string.format(' bg: #%06x', hl.bg))
end
table.insert(lines, '')
end
-- Final applied highlight (use TreeSitter if available, otherwise syntax)
local final_hl_name = nil
if #ts_hl > 0 then
final_hl_name = '@' .. ts_hl[1].capture
elseif synTrans ~= '' then
final_hl_name = synTrans
elseif synName ~= '' then
final_hl_name = synName
end
if final_hl_name then
local final_hl, final_resolved = resolve_hl(final_hl_name)
table.insert(lines, 'Applied Highlight: ' .. final_resolved)
if final_hl.fg then
table.insert(lines, string.format(' fg: #%06x', final_hl.fg))
else
table.insert(lines, ' fg: NONE')
end
if final_hl.bg then
table.insert(lines, string.format(' bg: #%06x', final_hl.bg))
else
table.insert(lines, ' bg: NONE')
end
local styles = {}
if final_hl.bold then table.insert(styles, 'bold') end
if final_hl.italic then table.insert(styles, 'italic') end
if final_hl.underline then table.insert(styles, 'underline') end
if final_hl.undercurl then table.insert(styles, 'undercurl') end
if #styles > 0 then
table.insert(lines, ' style: ' .. table.concat(styles, ', '))
end
end
-- Show in a floating window
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
local width = 0
for _, line in ipairs(lines) do
width = math.max(width, #line)
end
width = math.min(width + 2, vim.o.columns - 4)
local height = #lines
local opts = {
relative = 'cursor',
width = width,
height = height,
row = 1,
col = 0,
style = 'minimal',
border = 'rounded',
}
vim.api.nvim_open_win(buf, false, opts)
end, { desc = 'Debug: Show highlight group and color under cursor', silent = true })

View File

@ -20,9 +20,9 @@ vim.g.netrw_alto = 0
-- 50% split when pressing 'p'
vim.g.netrw_winsize = 50
-- Keep working directory unchanged when browsing (1 = don't change directory)
-- Setting to 1 prevents netrw from changing vim's working directory
vim.g.netrw_keepdir = 1
-- Keep the current directory and browsing directory synced
-- This makes netrw respect your current working directory
vim.g.netrw_keepdir = 0
-- Open files in the same window (replace netrw buffer)
-- Options: 0=same window, 1=horizontal split, 2=vertical split, 3=new tab, 4=previous window
@ -40,22 +40,17 @@ vim.g.netrw_sizestyle = 'H'
-- Netrw file explorer keymaps
-- Open netrw in new tab at current file's directory
map('n', '<leader>nt', function()
map('n', '<leader>te', function()
local current_file_dir = vim.fn.expand('%:p:h')
vim.cmd('tabnew')
vim.cmd('Explore ' .. vim.fn.fnameescape(current_file_dir))
end, { desc = 'Netrw: Tab at current file dir', silent = true })
end, { desc = 'Netrw: Tab explore (current file dir)', silent = true })
-- Open netrw in new tab at project root (stored at startup)
map('n', '<leader>nT', function()
-- Open netrw in new tab at project root (cwd)
map('n', '<leader>tE', function()
vim.cmd('tabnew')
vim.cmd('Explore ' .. vim.fn.fnameescape(vim.g.project_root))
end, { desc = 'Netrw: Tab at project root', silent = true })
-- Open netrw in current window at project root (stored at startup)
map('n', '<leader>nr', function()
vim.cmd('Explore ' .. vim.fn.fnameescape(vim.g.project_root))
end, { desc = 'Netrw: Open at project root', silent = true })
vim.cmd('Explore ' .. vim.fn.fnameescape(vim.fn.getcwd()))
end, { desc = 'Netrw: Tab explore (project root)', silent = true })
-- Enable line numbers in netrw buffers
vim.api.nvim_create_autocmd('FileType', {
pattern = 'netrw',

View File

@ -1,6 +1,6 @@
-- Paper Tonic Modern - Color Palette
-- Extracted from original Paper Tonic colorscheme
-- Supports both light (paper-like) and dark modes
-- Light, paper-like theme with subtle colors
local M = {}
@ -9,177 +9,107 @@ local M = {}
-- 256: Integer 0-255 for 256-color terminals
-- ansi: ANSI color name for basic 16-color terminals
-- Detect current background mode
local is_dark = vim.o.background == 'dark'
-- ============================================================================
-- Background Colors
-- ============================================================================
if is_dark then
-- Dark mode: soft dark backgrounds
M.bg = {'#27292c', 234, 'black'}
M.bg_darkest = {'#ffffff', 255, 'white'} -- Inverted: lightest for strong contrast
M.bg_ui = {'#353535', 236, 'darkgray'}
else
-- Light mode: pure white paper
M.bg = {'#ffffff', 255, 'white'}
M.bg_darkest = {'#505050', 244, 'gray'}
M.bg_ui = {'#efefef', 0, 'darkgray'}
end
-- Main background: pure white paper
M.bg = {'#ffffff', 255, 'white'}
-- Darkest background: used for very strong contrast elements
M.bg_darkest = {'#505050', 244, 'gray'}
-- UI background: slightly off-white for UI elements (statusline, etc.)
M.bg_ui = {'#efefef', 0, 'darkgray'}
-- ============================================================================
-- Highlight Backgrounds (general)
-- ============================================================================
if is_dark then
-- Dark mode: lighter backgrounds for highlights
M.bg_hl_strong = {"#3a3a3a", 237, "darkgray"}
M.bg_hl = {"#2a2a2a", 236, "darkgray"}
M.bg_hl_weak = {"#252525", 235, "darkgray"}
-- Selection/highlight backgrounds (neutral gray tones)
M.bg_hl_strong = {"#dddddd", 17, "white"}
M.bg_hl = {"#eeeeee", 250, "white"}
M.bg_hl_weak = {"#f7f2f2", 250, "white"}
-- Special highlight backgrounds (cyan/blue tones)
M.bg_hl_special_strong = {"#1a4a5a", 24, "darkblue"}
M.bg_hl_special = {"#0a3a4a", 23, "darkblue"}
M.bg_hl_special_weak = {"#1a3545", 23, "darkblue"}
-- Special highlight backgrounds (cyan/blue tones - for LSP references, search, etc.)
M.bg_hl_special_strong = {"#a3e0ff", 17, "cyan"}
M.bg_hl_special = {"#d4f0ff", 250, "cyan"}
M.bg_hl_special_weak = {"#e0eaff", 250, "cyan"}
-- Alternative special highlight (green tones)
M.bg_hl_special_alt_strong = {"#1a5a2a", 28, "darkgreen"}
M.bg_hl_special_alt = {"#0a4a1a", 22, "darkgreen"}
else
-- Light mode: neutral gray tones
M.bg_hl_strong = {"#dddddd", 17, "white"}
M.bg_hl = {"#eeeeee", 250, "white"}
M.bg_hl_weak = {"#f7f2f2", 250, "white"}
-- Special highlight backgrounds (cyan/blue tones)
M.bg_hl_special_strong = {"#a3e0ff", 17, "cyan"}
M.bg_hl_special = {"#d4f0ff", 250, "cyan"}
M.bg_hl_special_weak = {"#e0eaff", 250, "cyan"}
-- Alternative special highlight (green tones)
M.bg_hl_special_alt_strong = {"#74f283", 17, "cyan"}
M.bg_hl_special_alt = {"#bff2cd", 250, "cyan"}
end
-- Alternative special highlight (green tones - for diff additions, etc.)
M.bg_hl_special_alt_strong = {"#74f283", 17, "cyan"}
M.bg_hl_special_alt = {"#bff2cd", 250, "cyan"}
-- ============================================================================
-- Status Backgrounds (error, success, modified, fail)
-- ============================================================================
if is_dark then
-- Dark mode: darker tinted backgrounds
M.bg_error = {'#4a2020', 52, 'darkred'}
M.bg_error_weak = {'#3a1a1a', 52, 'darkred'}
M.bg_success = {'#1a3a1a', 22, 'darkgreen'}
M.bg_modified = {'#1a1a3a', 17, 'darkblue'}
M.bg_fail = {'#3a1a1a', 52, 'darkred'}
else
-- Light mode: light tinted backgrounds
M.bg_error = {'#ffd7d7', 196, 'white'}
M.bg_error_weak = {'#ffefef', 196, 'white'}
M.bg_success = {'#e0ece0', 196, 'white'}
M.bg_modified = {'#e0e0ec', 196, 'white'}
M.bg_fail = {'#ece0e0', 196, 'white'}
end
-- Error backgrounds (red/pink tones)
M.bg_error = {'#ffd7d7', 196, 'white'}
M.bg_error_weak = {'#ffefef', 196, 'white'}
-- Success background (green tone)
M.bg_success = {'#e0ece0', 196, 'white'}
-- Modified background (blue tone)
M.bg_modified = {'#e0e0ec', 196, 'white'}
-- Fail/warning background (red tone)
M.bg_fail = {'#ece0e0', 196, 'white'}
-- ============================================================================
-- Foreground Colors (text)
-- ============================================================================
if is_dark then
-- Dark mode: light text (inverted from light mode)
M.fg_stronger = {'#e0e0e0', 253, 'white'} -- Lightest text
M.fg_strong = {'#c0c0c0', 250, 'white'} -- Light text
M.fg = {'#a0a0a0', 248, 'gray'} -- Normal text (default)
M.fg_weak = {'#808080', 244, 'gray'} -- Dimmer text (comments, less important)
M.fg_weaker = {'#606060', 241, 'darkgray'} -- Dimmest text (very subtle)
M.fg_exception = {'#d08080', 174, 'red'} -- Light reddish for errors/exceptions
else
-- Light mode: dark text on paper
M.fg_stronger = {'#444444', 236, 'darkgrey'} -- Darkest text
M.fg_strong = {'#666666', 236, 'darkgrey'} -- Dark text
M.fg = {'#8c8c8c', 244, 'gray'} -- Normal text (default)
M.fg_weak = {'#9d9d9d', 251, 'gray'} -- Light text (comments, less important)
M.fg_weaker = {'#bbbbbb', 251, 'gray'} -- Lightest text (very subtle)
M.fg_exception = {'#7c4444', 251, 'gray'} -- Reddish-brown for errors/exceptions
end
-- Main text colors (gray scale, strongest to weakest)
M.fg_stronger = {'#444444', 236, 'darkgrey'} -- Darkest text
M.fg_strong = {'#666666', 236, 'darkgrey'} -- Dark text
M.fg = {'#8c8c8c', 244, 'gray'} -- Normal text (default)
M.fg_weak = {'#9d9d9d', 251, 'gray'} -- Light text (comments, less important)
M.fg_weaker = {'#bbbbbb', 251, 'gray'} -- Lightest text (very subtle)
-- Exception foreground (reddish-brown for errors/exceptions)
M.fg_exception = {'#7c4444', 251, 'gray'}
-- ============================================================================
-- Status Foreground Colors (Git/Diff - muted natural tones)
-- ============================================================================
if is_dark then
-- Dark mode: lighter muted tones
M.success = {'#a0d0a0', 114, 'green'} -- Success/addition (muted green)
M.modified = {'#a0a0d0', 110, 'blue'} -- Modified/change (muted blue)
M.fail = {'#d0a0a0', 174, 'red'} -- Fail/deletion (muted red)
else
-- Light mode: natural muted colors
M.success = {'#89af89', 196, 'white'} -- Success/addition (muted green)
M.modified = {'#8989af', 196, 'white'} -- Modified/change (muted blue)
M.fail = {'#af8989', 196, 'white'} -- Fail/deletion (muted red)
end
-- Git/diff status indicators (used in statusline, signs, etc.)
-- These are muted, natural colors for git changes
M.success = {'#89af89', 196, 'white'} -- Success/addition (muted green)
M.modified = {'#8989af', 196, 'white'} -- Modified/change (muted blue)
M.fail = {'#af8989', 196, 'white'} -- Fail/deletion (muted red)
-- ============================================================================
-- Quickfix/Location List Diagnostic Colors (Muted tones like git/diff)
-- ============================================================================
if is_dark then
-- Dark mode: lighter muted tones
M.qf_error = {'#d07070', 167, 'red'} -- Error (lighter muted red)
M.qf_warn = {'#d0a080', 173, 'yellow'} -- Warning (muted orange)
M.qf_info = {'#80b0d0', 110, 'blue'} -- Info (muted blue)
M.qf_hint = {'#80d0d0', 116, 'cyan'} -- Hint (lighter muted cyan)
else
-- Light mode: darker muted tones
M.qf_error = {'#af3f3f', 196, 'white'} -- Error (darker muted red for more contrast)
M.qf_warn = {'#af7f5f', 196, 'white'} -- Warning (muted orange)
M.qf_info = {'#5f8faf', 196, 'white'} -- Info (muted blue)
M.qf_hint = {'#5fafaf', 196, 'white'} -- Hint (lighter muted blue)
end
-- These are for quickfix/location list only - similar muted tone to git/diff
M.qf_error = {'#af3f3f', 196, 'white'} -- Error (darker muted red for more contrast)
M.qf_warn = {'#af7f5f', 196, 'white'} -- Warning (muted orange)
M.qf_info = {'#5f8faf', 196, 'white'} -- Info (muted blue)
M.qf_hint = {'#5fafaf', 196, 'white'} -- Hint (lighter muted blue)
-- ============================================================================
-- Diagnostic/Alert Colors (Fluorescent/Neon - intentionally jarring)
-- Alert/Diagnostic Colors (Fluorescent/Neon - intentionally jarring)
-- ============================================================================
-- These colors are designed to be NOTICED, not blend in with code
-- Fluorescent/neon aesthetic
-- In light mode: bright, synthetic, stands out on white paper ("highlighter marker")
-- In dark mode: slightly toned down but still prominent
-- Fluorescent/neon aesthetic similar to bg_hl_special_alt colors
-- Think "highlighter marker" - bright, synthetic, stands out on white paper
if is_dark then
-- Dark mode: still bright but not as harsh
M.diag_error = {'#ff4488', 204, 'red'} -- Bright pink-red
M.diag_warn = {'#ff8833', 208, 'yellow'} -- Bright orange
M.diag_info = {'#44ccff', 81, 'cyan'} -- Bright cyan
M.diag_hint = {'#88ddff', 117, 'cyan'} -- Softer cyan
M.diag_hint_dark = {'#44aacc', 74, 'cyan'} -- Medium cyan
M.diag_weak = {'#ffaa66', 215, 'yellow'} -- Lighter orange
-- LSP Diagnostics - Fluorescent colors for maximum visibility
M.diag_error = {'#ff0066', 197, 'red'} -- Hot pink-red (screams "error!")
M.diag_warn = {'#ff6600', 202, 'red'} -- Fluorescent orange (warnings)
M.diag_info = {'#00ccff', 45, 'cyan'} -- Bright fluorescent cyan (info - more prominent)
M.diag_hint = {'#66e0ff', 81, 'cyan'} -- Softer fluorescent cyan (hint - less prominent)
-- Diagnostic backgrounds
M.bg_diag_error = {'#3a1a25', 52, 'darkred'}
M.bg_diag_warn = {'#3a2a1a', 58, 'brown'}
M.bg_diag_info = {'#1a2a3a', 17, 'darkblue'}
M.bg_diag_hint = {'#1a2f3a', 23, 'darkblue'}
M.question = {'#44ccff', 81, 'cyan'}
else
-- Light mode: fluorescent colors for maximum visibility
M.diag_error = {'#ff0066', 197, 'red'} -- Hot pink-red (screams "error!")
M.diag_warn = {'#ff6600', 202, 'red'} -- Fluorescent orange (warnings)
M.diag_info = {'#00ccff', 45, 'cyan'} -- Bright fluorescent cyan (info)
M.diag_hint = {'#66e0ff', 81, 'cyan'} -- Softer fluorescent cyan (hint)
M.diag_hint_dark = {'#0099cc', 38, 'cyan'} -- Darker cyan for UI elements
M.diag_weak = {'#ff9933', 208, 'yellow'} -- Lighter orange (spelling, etc.)
-- Diagnostic backgrounds
M.bg_diag_error = {'#ffe6f0', 224, 'white'}
M.bg_diag_warn = {'#fff0e6', 223, 'white'}
M.bg_diag_info = {'#e6f9ff', 195, 'white'}
M.bg_diag_hint = {'#f0fcff', 195, 'white'}
M.question = {'#00ccff', 45, 'cyan'}
end
-- LSP Diagnostic backgrounds - Light tinted versions for highlighting code
M.bg_diag_error = {'#ffe6f0', 224, 'white'} -- Very light pink (for error backgrounds)
M.bg_diag_warn = {'#fff0e6', 223, 'white'} -- Very light orange (for warning backgrounds)
M.bg_diag_info = {'#e6f9ff', 195, 'white'} -- Very light cyan (for info backgrounds)
M.bg_diag_hint = {'#f0fcff', 195, 'white'} -- Very light cyan (for hint backgrounds)
-- ============================================================================
-- Primary Accent Colors (brownish-red tones)
@ -187,19 +117,15 @@ end
-- Primary accent: For "base" languages
-- Used for: PHP, JavaScript, Python, etc.
if is_dark then
-- Dark mode: lighter brownish-red tones
M.primary_stronger = {"#d0a0a0", 181, "white"}
M.primary_strong = {"#c09090", 181, "white"}
M.primary = {"#b08080", 138, "gray"}
M.primary_weak = {"#a07070", 131, "darkgray"}
else
-- Light mode: darker brownish-red tones
M.primary_stronger = {"#7f4b4b", 236, "black"}
M.primary_strong = {"#5a4444", 236, "black"}
M.primary = {"#6b5555", 244, "gray"}
M.primary_weak = {"#7c6666", 248, "darkgray"}
end
-- Languages using primary:
-- - PHP: always primary (whether standalone or in <?php ?> tags within HTML)
-- - JavaScript: always primary (standalone or embedded)
-- - Python: always primary
-- Note: Both PHP and JS use primary; acceptable since embedded JS in PHP is discouraged
M.primary_stronger = {"#7f4b4b", 236, "black"} -- Darkest accent
M.primary_strong = {"#5a4444", 236, "black"} -- Dark accent
M.primary = {"#6b5555", 244, "gray"} -- Normal accent
M.primary_weak = {"#7c6666", 248, "darkgray"} -- Light accent
-- ============================================================================
-- Language-Specific Color Palettes (for mixed-language contexts)
@ -214,49 +140,31 @@ end
-- - HTML tags: c3 (blue) - everywhere, whether in .html or embedded in PHP
-- - CSS rules: c2 (green) - everywhere, whether in .css or <style> tags
if is_dark then
-- Dark mode: lighter tones
-- Color 2: Green tones - CSS syntax
M.c2_weak = {"#90d090", 114, "green"}
M.c2 = {"#70c070", 77, "green"}
M.c2_strong = {"#50a050", 71, "darkgreen"}
-- Color 2: Green tones - CSS syntax
-- CSS maintains green coloring in <style> tags, CSS files, CSS-in-JS, etc.
M.c2_weak = {"#5e955e", 28, "darkgreen"}
M.c2 = {"#008700", 22, "darkgreen"}
M.c2_strong = {"#005a00", 22, "darkgreen"}
-- Color 3: Blue tones - HTML syntax
M.c3_weak = {"#80b0e0", 110, "blue"}
M.c3 = {"#60a0d0", 74, "blue"}
M.c3_strong = {"#4080b0", 67, "darkblue"}
-- Color 3: Blue tones - HTML syntax
-- HTML maintains blue coloring in .html files, within PHP, within templates, etc.
M.c3_weak = {"#2d78b7", 20, "blue"}
M.c3 = {"#005faf", 19, "blue"}
M.c3_strong = {"#004f92", 17, "darkblue"}
-- Color 4: Cyan tones - Reserved/Future use
M.c4_weak = {"#a0d0e0", 152, "cyan"}
M.c4 = {"#80c0d0", 116, "cyan"}
M.c4_strong = {"#60a0c0", 74, "darkcyan"}
-- Color 4: Cyan tones - Reserved/Future use
-- Available for additional language if needed (TypeScript? SQL?)
M.c4_weak = {"#78b7d5", 20, "blue"}
M.c4 = {"#56acd7", 19, "blue"}
M.c4_strong = {"#1596d7", 17, "darkblue"}
-- Color 5: Magenta/Pink tones - Template syntax
M.c5_weak = {"#e080c0", 213, "magenta"}
M.c5 = {"#d060a0", 170, "magenta"}
M.c5_strong = {"#b04080", 133, "darkmagenta"}
else
-- Light mode: darker tones
-- Color 2: Green tones - CSS syntax
M.c2_weak = {"#5e955e", 28, "darkgreen"}
M.c2 = {"#008700", 22, "darkgreen"}
M.c2_strong = {"#005a00", 22, "darkgreen"}
-- Color 3: Blue tones - HTML syntax
M.c3_weak = {"#2d78b7", 20, "blue"}
M.c3 = {"#005faf", 19, "blue"}
M.c3_strong = {"#004f92", 17, "darkblue"}
-- Color 4: Cyan tones - Reserved/Future use
M.c4_weak = {"#78b7d5", 20, "blue"}
M.c4 = {"#56acd7", 19, "blue"}
M.c4_strong = {"#1596d7", 17, "darkblue"}
-- Color 5: Magenta/Pink tones - Template syntax
M.c5_weak = {"#e846ac", 164, "magenta"}
M.c5 = {"#d70087", 164, "magenta"}
M.c5_strong = {"#ad006d", 164, "magenta"}
end
-- Color 5: Magenta/Pink tones - Template syntax
-- Used for: Django templates, Twig, Handlebars, Jinja
-- The base language keeps its color while template syntax uses c5
-- Example in Twig: HTML stays blue (c3), but {{ }}, {% %}, {# #} are magenta (c5)
M.c5_weak = {"#e846ac", 164, "magenta"}
M.c5 = {"#d70087", 164, "magenta"}
M.c5_strong = {"#ad006d", 164, "magenta"}
-- ============================================================================
-- Helper Constants

View File

@ -9,8 +9,8 @@ return {
-- ============================================================================
Normal = { fg = c.fg, bg = c.bg },
NormalFloat = { fg = c.fg, bg = c.bg },
FloatBorder = { fg = c.diag_hint_dark, bg = c.bg },
NormalFloat = { fg = c.fg, bg = c.NONE },
FloatBorder = { fg = c.fg_stronger, bg = c.NONE },
-- ============================================================================
-- Cursor & Line Highlighting
@ -66,19 +66,17 @@ return {
-- Popup Menu (Completion)
-- ============================================================================
Pmenu = { fg = c.diag_hint_dark, bg = c.bg },
PmenuSel = { fg = c.fg_strong, bg = c.bg_hl, bold = true },
PmenuSbar = { fg = c.NONE, bg = c.bg_hl },
PmenuThumb = { fg = c.NONE, bg = c.fg_weaker },
PmenuBorder = { fg = c.diag_hint_dark, bg = c.bg },
WildMenu = { fg = c.fg_strong, bg = c.bg_hl, bold = true },
Pmenu = { fg = c.fg, bg = c.bg_ui },
PmenuSel = { fg = c.fg_strong, bg = c.bg_ui, bold = true },
PmenuSbar = 'Pmenu',
PmenuThumb = 'Pmenu',
WildMenu = { fg = c.fg_strong, bg = c.bg_ui, bold = true },
-- ============================================================================
-- Folds
-- ============================================================================
Folded = { fg = c.diag_hint_dark, bg = c.NONE, bold = true },
CursorLineFold = { fg = c.diag_hint_dark, bg = c.bg_hl, bold = true },
Folded = { fg = c.fg_strong, bold = true },
FoldColumn = { fg = c.fg_weak },
-- ============================================================================
@ -94,19 +92,17 @@ return {
-- Spelling
-- ============================================================================
-- Spelling errors: normal text color with bright colored straight underline
-- sp (special) sets the underline color, separate from text foreground
SpellBad = { sp = c.diag_error, underline = true }, -- Serious error: hot pink-red underline
SpellCap = { sp = c.diag_warn, underline = true }, -- Capitalization: orange underline
SpellLocal = { sp = c.diag_weak, underline = true }, -- Wrong region: lighter orange underline
SpellRare = { sp = c.diag_weak, underline = true }, -- Rare word: lighter orange underline
SpellBad = { fg = c.alert_strong, bg = c.bg_error_weak },
SpellCap = { fg = c.alert, bg = c.bg_error_weak },
SpellLocal = { fg = c.alert_weak, bg = c.bg_error_weak },
SpellRare = { fg = c.alert_weak, bg = c.bg_error_weak },
-- ============================================================================
-- Messages & Prompts
-- ============================================================================
ErrorMsg = { fg = c.diag_error, bold = true },
WarningMsg = { fg = c.diag_warn, bold = true },
ErrorMsg = { fg = c.alert, bold = true },
WarningMsg = { fg = c.alert, bold = true },
Question = { fg = c.question, bold = true },
ModeMsg = { fg = c.question },
MoreMsg = { fg = c.question },

View File

@ -63,11 +63,10 @@ return {
-- ============================================================================
-- Menu
CmpItemAbbr = { fg = c.fg },
CmpItemMenu = { fg = c.fg_weak, italic = true },
CmpItemAbbrMatch = { fg = c.primary, bold = true },
CmpItemAbbrMatchFuzzy = { fg = c.primary_weak },
CmpItemAbbrDeprecated = { fg = c.fg_weak, strikethrough = true },
CmpItemMenu = { fg = c.fg_weak, italic = true },
-- Kind icons/labels
CmpItemKindDefault = { fg = c.fg },
@ -88,7 +87,7 @@ return {
CmpItemKindText = { fg = c.fg },
CmpItemKindFile = { fg = c.fg },
CmpItemKindFolder = { fg = c.fg_strong },
CmpItemKindColor = { fg = c.diag_warn },
CmpItemKindColor = { fg = c.alert },
CmpItemKindUnit = { fg = c.fg_weak },
CmpItemKindValue = { fg = c.fg_strong },
CmpItemKindConstant = { fg = c.fg_strong },
@ -109,7 +108,7 @@ return {
OilSize = { fg = c.fg_weak },
OilPermissionNone = { fg = c.fg_weaker },
OilPermissionRead = { fg = c.success },
OilPermissionWrite = { fg = c.diag_warn },
OilPermissionWrite = { fg = c.alert },
OilPermissionExecute = { fg = c.modified },
OilCopy = { fg = c.success, bold = true },
OilMove = { fg = c.modified, bold = true },
@ -117,7 +116,7 @@ return {
OilDelete = { fg = c.fail, bold = true },
OilChange = { fg = c.modified, bold = true },
OilRestore = { fg = c.success },
OilPurge = { fg = c.diag_error, bold = true },
OilPurge = { fg = c.alert_strong, bold = true },
OilTrash = { fg = c.fail },
OilTrashSourcePath = { fg = c.fg_weak, italic = true },
@ -151,8 +150,8 @@ return {
-- ============================================================================
UfoFoldedBg = { bg = c.bg_hl_weak },
UfoFoldedFg = { fg = c.diag_hint_dark },
UfoCursorFoldedLine = { bg = c.bg_hl, fg = c.diag_hint_dark },
UfoFoldedFg = { fg = c.fg_weak },
UfoCursorFoldedLine = { bg = c.bg_hl, fg = c.fg },
UfoPreviewBorder = { fg = c.fg_weak },
UfoPreviewNormal = { bg = c.bg_ui },
UfoPreviewCursorLine = { bg = c.bg_hl },

View File

@ -76,8 +76,8 @@ return {
Underlined = { underline = true },
Ignore = { fg = c.fg_weak },
Error = { fg = c.diag_error, bold = true },
Todo = { fg = c.diag_error },
Error = { fg = c.alert_strong, bold = true },
Todo = { fg = c.alert_strong },
-- ============================================================================
-- Language-Specific: CSS

View File

@ -101,10 +101,10 @@ return {
['@comment'] = { fg = c.fg_weaker, italic = true, bold = true },
['@comment.documentation'] = { fg = c.fg_weak, italic = true },
['@comment.error'] = { fg = c.diag_error, bold = true },
['@comment.warning'] = { fg = c.diag_warn, bold = true },
['@comment.todo'] = { fg = c.diag_error },
['@comment.note'] = { fg = c.diag_weak },
['@comment.error'] = { fg = c.alert_strong, bold = true },
['@comment.warning'] = { fg = c.alert, bold = true },
['@comment.todo'] = { fg = c.alert_strong },
['@comment.note'] = { fg = c.alert_weak },
-- ============================================================================
-- Markup (Markdown, etc.)
@ -292,6 +292,6 @@ return {
['@none'] = {},
['@conceal'] = { fg = c.fg_weaker },
-- @spell and @nospell should not define any styling
-- This allows vim.wo.spell to control spell highlighting via SpellBad/SpellCap/etc
['@spell'] = {},
['@nospell'] = {},
}

View File

@ -3,17 +3,13 @@
local M = {}
-- Import color palette
local colors = require('paper-tonic-modern.colors')
-- ============================================================================
-- Helper Functions
-- ============================================================================
-- Get fresh color palette (reload to respect background changes)
local function get_colors()
-- Clear cached module to force reload
package.loaded['paper-tonic-modern.colors'] = nil
return require('paper-tonic-modern.colors')
end
-- Convert color table {hex, 256, ansi} to usable formats
local function get_color(color_def, mode)
if type(color_def) == 'string' then
@ -96,17 +92,6 @@ end
-- ============================================================================
function M.load()
-- Get fresh colors for current background mode
local colors = get_colors()
-- Clear all cached group modules to force reload with new colors
package.loaded['paper-tonic-modern.groups.editor'] = nil
package.loaded['paper-tonic-modern.groups.syntax'] = nil
package.loaded['paper-tonic-modern.groups.treesitter'] = nil
package.loaded['paper-tonic-modern.groups.semantic'] = nil
package.loaded['paper-tonic-modern.groups.lsp'] = nil
package.loaded['paper-tonic-modern.groups.plugins'] = nil
-- Load highlight groups in order
-- Each module returns a table of { group_name = spec }
@ -133,8 +118,9 @@ function M.load()
-- Plugin highlights (Telescope, Gitsigns, cmp, etc.)
local plugins = require('paper-tonic-modern.groups.plugins')
M.highlight_all(plugins)
-- Export colors for external use
M.colors = colors
end
-- Export colors for use in other modules
M.colors = colors
return M

View File

@ -34,32 +34,8 @@ return {
preselect = cmp.PreselectMode.None,
completion = { completeopt = "menu,menuone,noinsert,noselect" },
window = {
completion = cmp.config.window.bordered({
border = {
{ "🭽", "FloatBorder" },
{ "", "FloatBorder" },
{ "🭾", "FloatBorder" },
{ "", "FloatBorder" },
{ "🭿", "FloatBorder" },
{ "", "FloatBorder" },
{ "🭼", "FloatBorder" },
{ "", "FloatBorder" },
},
winhighlight = "Normal:Normal,FloatBorder:FloatBorder,CursorLine:PmenuSel",
}),
documentation = cmp.config.window.bordered({
border = {
{ "🭽", "FloatBorder" },
{ "", "FloatBorder" },
{ "🭾", "FloatBorder" },
{ "", "FloatBorder" },
{ "🭿", "FloatBorder" },
{ "", "FloatBorder" },
{ "🭼", "FloatBorder" },
{ "", "FloatBorder" },
},
winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
}),
completion = cmp.config.window.bordered(),
documentation = cmp.config.window.bordered(),
},
}
end,

View File

@ -1,74 +0,0 @@
-- conform.nvim: Modern formatting without LSP overhead
-- Replaces none-ls for all formatting tasks
return {
"stevearc/conform.nvim",
event = { "BufReadPre", "BufNewFile" },
config = function()
local conform = require("conform")
conform.setup({
formatters_by_ft = {
-- JavaScript, TypeScript, CSS, SCSS, JSON, HTML, Markdown
javascript = { "prettier" },
javascriptreact = { "prettier" },
typescript = { "prettier" },
typescriptreact = { "prettier" },
css = { "prettier" },
scss = { "prettier" },
html = { "prettier" },
json = { "prettier" },
jsonc = { "prettier" },
markdown = { "prettier" },
-- PHP
php = { "phpcbf" },
-- Lua
lua = { "stylua" },
},
-- Formatter customization
formatters = {
-- Add WordPress coding standard to phpcbf
phpcbf = {
prepend_args = { "--standard=WordPress" },
},
},
-- Format on save
format_on_save = function(bufnr)
-- Check global flag
if vim.g.format_on_save == false then
return nil
end
return {
timeout_ms = 500,
lsp_fallback = false, -- Don't use LSP formatting
}
end,
})
-- Format-on-save is enabled by default
vim.g.format_on_save = true
-- Keymaps
-- Toggle format-on-save
vim.keymap.set("n", "<leader>lt", function()
vim.g.format_on_save = not vim.g.format_on_save
local status = vim.g.format_on_save and "enabled" or "disabled"
vim.notify("Format on save " .. status, vim.log.levels.INFO)
end, { desc = "Formatting: Toggle format on save", silent = true, noremap = true })
-- Manual format (buffer)
vim.keymap.set("n", "<leader>lf", function()
conform.format({ async = false, lsp_fallback = false })
end, { desc = "Formatting: Format buffer", silent = true, noremap = true })
-- Manual format (visual range)
vim.keymap.set("v", "<leader>lf", function()
conform.format({ async = false, lsp_fallback = false })
end, { desc = "Formatting: Format selection", silent = true, noremap = true })
end,
}

View File

@ -25,27 +25,16 @@ return {
vim.keymap.set(mode, l, r, opts)
end
-- Hunk navigation (includes zt to center after movement)
-- Note: gs.next_hunk/prev_hunk are async, so zt needs a delay to center after cursor moves
-- Hunk navigation
map('n', ']h', function()
if vim.wo.diff then return ']czt' end
vim.schedule(function()
gs.next_hunk()
end)
vim.defer_fn(function()
vim.cmd('normal! zt')
end, 50)
if vim.wo.diff then return ']c' end
vim.schedule(function() gs.next_hunk() end)
return '<Ignore>'
end, { expr = true, desc = 'Gitsigns: Next hunk' })
map('n', '[h', function()
if vim.wo.diff then return '[czt' end
vim.schedule(function()
gs.prev_hunk()
end)
vim.defer_fn(function()
vim.cmd('normal! zt')
end, 50)
if vim.wo.diff then return '[c' end
vim.schedule(function() gs.prev_hunk() end)
return '<Ignore>'
end, { expr = true, desc = 'Gitsigns: Previous hunk' })
@ -61,12 +50,6 @@ return {
map('n', '<leader>hd', gs.diffthis, { desc = 'Gitsigns: Diff this' })
map('n', '<leader>hD', function() gs.diffthis('~') end, { desc = 'Gitsigns: Diff this ~' })
-- Quickfix/Location list
map('n', '<leader>hq', function()
gs.setqflist('all')
vim.cmd('copen')
end, { desc = 'Gitsigns: All hunks to quickfix' })
-- Text object
map({ 'o', 'x' }, 'ih', ':<C-U>Gitsigns select_hunk<CR>', { desc = 'Gitsigns: Select hunk' })
end,

View File

@ -9,9 +9,9 @@ return {
tab_char = '',
},
scope = {
enabled = true, -- Highlight current scope indent guide
show_start = true, -- Disable underline at start of scope
show_end = false, -- Don't show underline at end
enabled = true, -- Highlight current scope
show_start = true, -- Show underline at start of scope
show_end = false, -- Don't show underline at end (can be noisy)
},
exclude = {
filetypes = {

123
lua/plugins/none-ls.lua Normal file
View File

@ -0,0 +1,123 @@
-- none-ls.nvim: Formatting only (linting moved to nvim-lint)
-- Philosophy: Formatters are authoritative. Project-local executables preferred.
-- Note: none-ls removed most linters from builtins, so we use nvim-lint for diagnostics
return {
"nvimtools/none-ls.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
event = { "BufReadPre", "BufNewFile" },
config = function()
local null_ls = require("null-ls")
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
-- Helper: Find project-local executable, fallback to global
-- Searches node_modules/.bin/, vendor/bin/, and Mason bin first
local function find_executable(names)
local cwd = vim.fn.getcwd()
local mason_bin = vim.fn.stdpath("data") .. "/mason/bin/"
-- Try project-local paths first, then Mason, then global
local search_paths = {
cwd .. "/node_modules/.bin/",
cwd .. "/vendor/bin/",
mason_bin,
}
for _, name in ipairs(names) do
for _, path in ipairs(search_paths) do
local full_path = path .. name
if vim.fn.executable(full_path) == 1 then
return full_path
end
end
-- Fallback to system PATH
if vim.fn.executable(name) == 1 then
return name
end
end
return nil
end
-- Formatters
local formatting = null_ls.builtins.formatting
-- Note: Diagnostics (linters) moved to nvim-lint plugin
-- Note: Python formatting handled by ruff LSP
null_ls.setup({
sources = {
-- Prettier (JS, TS, CSS, SCSS, JSON, Markdown, HTML)
formatting.prettier.with({
command = find_executable({ "prettier" }),
prefer_local = "node_modules/.bin",
}),
-- PHP CodeSniffer (phpcbf) - WordPress standards
formatting.phpcbf.with({
command = find_executable({ "phpcbf" }),
prefer_local = "vendor/bin",
-- Respects phpcs.xml in project root or uses WordPress standard
extra_args = function()
local phpcs_xml = vim.fn.findfile("phpcs.xml", ".;")
if phpcs_xml == "" then
phpcs_xml = vim.fn.findfile("phpcs.xml.dist", ".;")
end
if phpcs_xml == "" then
return { "--standard=WordPress" }
end
return {}
end,
}),
-- stylua (Lua)
formatting.stylua.with({
command = find_executable({ "stylua" }),
}),
},
-- Format on save
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
-- Only format if format-on-save is enabled (global flag)
if vim.g.format_on_save ~= false then
-- Save view (cursor position, folds, etc.) before formatting
local view = vim.fn.winsaveview()
vim.lsp.buf.format({ bufnr = bufnr })
-- Restore view after formatting to preserve folds
vim.fn.winrestview(view)
end
end,
})
end
end,
})
-- Format-on-save is enabled by default
vim.g.format_on_save = true
-- Keymaps
-- Toggle format-on-save
vim.keymap.set("n", "<leader>lt", function()
vim.g.format_on_save = not vim.g.format_on_save
local status = vim.g.format_on_save and "enabled" or "disabled"
vim.notify("Format on save " .. status, vim.log.levels.INFO)
end, { desc = "Formatting: Toggle format on save", silent = true, noremap = true })
-- Manual format (buffer)
vim.keymap.set("n", "<leader>lf", function()
vim.lsp.buf.format({ async = false })
end, { desc = "Formatting: Format buffer", silent = true, noremap = true })
-- Manual format (visual range)
vim.keymap.set("v", "<leader>lf", function()
vim.lsp.buf.format({ async = false })
end, { desc = "Formatting: Format selection", silent = true, noremap = true })
end,
}

View File

@ -47,21 +47,21 @@ return {
-- Configure phpcs for WordPress standards
lint.linters.phpcs.cmd = find_executable({ "phpcs" }) or "phpcs"
-- Build args dynamically based on project ruleset presence
-- Note: This runs once at config load, checks cwd for phpcs.xml
local cwd = vim.fn.getcwd()
local has_project_ruleset =
vim.loop.fs_stat(cwd .. "/phpcs.xml")
or vim.loop.fs_stat(cwd .. "/phpcs.xml.dist")
local phpcs_args = { "-q", "--report=json" }
if not has_project_ruleset then
table.insert(phpcs_args, "--standard=WordPress")
end
table.insert(phpcs_args, "-") -- stdin
lint.linters.phpcs.args = phpcs_args
lint.linters.phpcs.args = {
"-q",
"--report=json",
function()
local phpcs_xml = vim.fn.findfile("phpcs.xml", ".;")
if phpcs_xml == "" then
phpcs_xml = vim.fn.findfile("phpcs.xml.dist", ".;")
end
if phpcs_xml == "" then
return "--standard=WordPress"
end
return nil
end,
"-", -- stdin
}
-- Configure eslint_d to use project-local first
lint.linters.eslint_d.cmd = find_executable({ "eslint_d", "eslint" }) or "eslint_d"

View File

@ -19,9 +19,6 @@ return {
telescope.setup({
defaults = {
-- Always search from project root (where Neovim was opened)
cwd = vim.g.project_root,
-- Minimal UI, keep it clean
prompt_prefix = '> ',
selection_caret = '> ',

View File

@ -58,12 +58,9 @@ return {
},
},
-- Indentation: Enable for languages with good indent queries
-- PHP requires this because GetPhpIndent() depends on Vim syntax (disabled by Treesitter)
-- Indentation (experimental, disable if issues)
indent = {
enable = true,
-- Disable for languages with poor/incomplete indent queries if needed
disable = {},
enable = false, -- Start disabled; can enable per-filetype if stable
},
-- Textobjects

View File

@ -26,7 +26,7 @@ return {
-- Open folds when searching
open_fold_hl_timeout = 150,
close_fold_kinds_for_ft = {
default = { 'imports' },
default = { 'imports', 'comment' },
},
preview = {
win_config = {

View File

@ -5,93 +5,21 @@ vim.g.loaded_ruby_provider = 0
vim.g.loaded_perl_provider = 0
vim.g.loaded_node_provider = 0
-- Auto-detect system theme (Linux/GNOME)
local function set_background_from_system()
local handle = io.popen('gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null')
if handle then
local result = handle:read("*a")
handle:close()
if result:match("dark") then
vim.o.background = 'dark'
else
vim.o.background = 'light'
end
end
end
-- Set background based on system theme on startup
set_background_from_system()
-- Handle SIGUSR1 signal to update theme when system theme changes
-- External watcher script (scripts/theme-watcher.sh) sends this signal
vim.api.nvim_create_autocmd('Signal', {
pattern = 'SIGUSR1',
callback = function()
set_background_from_system()
end,
desc = 'Update colorscheme when system theme changes',
})
-- Store initial working directory as project root
-- Used by netrw navigation to return to project root after following symlinks
vim.g.project_root = vim.fn.getcwd()
-- Prevent automatic directory changes when switching files
vim.opt.autochdir = false -- Keep working directory at project root
-- Enable project-local configuration files
vim.opt.exrc = true -- Load .nvim.lua from project root
vim.opt.secure = true -- Prompt before loading untrusted files
-- Custom tabline configuration
-- Load utils module for custom tabline function
local utils = require('utils')
-- Configure number of parent directories to show in full (default: 1)
-- Examples:
-- 1: /path/to/my/project/src/file.txt → pat/to/my/project/src/file.txt
-- 2: /path/to/my/project/src/file.txt → pat/to/my/project/src/file.txt
utils.tabline_full_parents = 1
-- Configure number of characters to show for shortened directories (default: 3)
-- Examples:
-- 3: /path/to/my → pat/to/my
-- 1: /path/to/my → p/t/m
utils.tabline_shorten_length = 4
-- Use custom tabline function
vim.opt.tabline = '%!v:lua.require("utils").custom_tabline()'
vim.opt.showtabline = 1 -- Show tabline only when there are 2+ tabs
-- Phase 3.2: non-plugin settings (legacy values where specified)
-- Completion UI for nvim-cmp
vim.opt.completeopt = { "menu", "menuone", "noselect" }
-- Built-in completion popup style (for Ctrl-X Ctrl-N/F completions)
-- Use floating window with border for native completion
vim.opt.pumblend = 0 -- No transparency
-- Note: Native completion (pumvisible) doesn't support custom borders like LSP floats
-- The Pmenu* highlight groups control its appearance
-- Spelling
vim.opt.spell = true
vim.opt.spelllang = { "en_gb" }
vim.opt.spelloptions = { "camel,noplainbuffer" }
-- Search behavior
vim.opt.ignorecase = true -- Case-insensitive search by default
vim.opt.smartcase = true -- Case-sensitive when search contains uppercase letters
vim.opt.incsearch = true -- Show matches as you type
vim.opt.hlsearch = true -- Highlight all search matches
-- Keyword characters (add $ for PHP/shell variables, - for CSS/HTML/config files)
vim.opt.iskeyword:append("$")
vim.opt.iskeyword:append("-")
-- Terminal and color support
vim.opt.termguicolors = true -- Enable 24-bit RGB color (required for colored underlines)
-- Visuals
vim.opt.showbreak = ""
vim.opt.listchars = {
@ -120,12 +48,6 @@ vim.opt.tabstop = 4 -- Display tabs as 4 spaces
vim.opt.shiftwidth = 4 -- Indent by 4
vim.opt.softtabstop = 4 -- Backspace removes 4 spaces
vim.opt.expandtab = true -- Use spaces by default (overridden per-filetype)
vim.opt.autoindent = true -- Copy indent from current line when starting new line
vim.opt.smartindent = true -- Smart autoindenting (C-like programs, PHP, etc.)
-- Persistent undo
vim.opt.undofile = true -- Enable persistent undo
vim.opt.undodir = vim.fn.stdpath("config") .. "/undodir" -- Store undo files in config directory
-- LSP diagnostics configuration
vim.diagnostic.config({
@ -142,55 +64,9 @@ vim.diagnostic.config({
update_in_insert = false, -- Don't update diagnostics while typing
severity_sort = true, -- Sort by severity (errors first)
float = {
border = "single",
border = "rounded",
source = "always", -- Show diagnostic source
header = "",
prefix = "",
},
})
-- Configure LSP floating windows with borders and padding
-- Custom border with box-drawing characters flush to window edges
-- Using heavy/light box-drawing characters positioned at cell edges
local border = {
{ "🭽", "FloatBorder" }, -- top-left corner
{ "", "FloatBorder" }, -- top horizontal line (at top edge)
{ "🭾", "FloatBorder" }, -- top-right corner
{ "", "FloatBorder" }, -- right vertical line (at right edge)
{ "🭿", "FloatBorder" }, -- bottom-right corner
{ "", "FloatBorder" }, -- bottom horizontal line (at bottom edge)
{ "🭼", "FloatBorder" }, -- bottom-left corner
{ "", "FloatBorder" }, -- left vertical line (at left edge)
}
-- Override the default open_floating_preview to add padding by modifying content
local orig_util_open_floating_preview = vim.lsp.util.open_floating_preview
function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...)
opts = opts or {}
opts.border = opts.border or border
-- Add padding by wrapping content with empty lines and spacing
if type(contents) == "table" and #contents > 0 then
-- Add empty lines at top and bottom
table.insert(contents, 1, "")
table.insert(contents, 1, "")
table.insert(contents, "")
table.insert(contents, "")
-- Add horizontal padding (spaces) to each content line
for i = 3, #contents - 2 do
if type(contents[i]) == "string" then
contents[i] = " " .. contents[i] .. " "
end
end
end
return orig_util_open_floating_preview(contents, syntax, opts, ...)
end
vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, {
border = border,
})
vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, {
border = border,
})

View File

@ -6,281 +6,5 @@ function M.safe_require(name)
return nil
end
-- Number of parent directories to show in full in the tabline
-- The rest will be shortened according to tabline_shorten_length
-- Example: with full_parents = 1, /path/to/my/project/src/file.txt becomes pat/to/my/project/src/file.txt
-- Example: with full_parents = 2, it becomes pat/to/my/project/src/file.txt
M.tabline_full_parents = 1
-- Number of characters to show for shortened directory names in the tabline
-- Example: with shorten_length = 3, /path/to/my becomes pat/to/my
-- Example: with shorten_length = 1, /path/to/my becomes p/t/m
M.tabline_shorten_length = 3
-- Get all modified, deleted, and untracked Git files with status
-- Returns a list suitable for setqflist() or nil on error
function M.git_changed_files()
-- Use git status --porcelain to get all changes with status indicators
-- Format: "XY filename" where X=index status, Y=worktree status
-- Status codes: M=modified, D=deleted, A=added, ??=untracked, etc.
local handle = io.popen('git status --porcelain 2>/dev/null')
if not handle then
vim.notify('Failed to run git status', vim.log.levels.ERROR)
return nil
end
local result = handle:read('*a')
handle:close()
if result == '' then
vim.notify('No git changes found', vim.log.levels.INFO)
return nil
end
local qf_list = {}
local status_map = {
['M'] = 'Modified',
['A'] = 'Added',
['D'] = 'Deleted',
['R'] = 'Renamed',
['C'] = 'Copied',
['U'] = 'Unmerged',
['?'] = 'Untracked',
}
for line in result:gmatch('[^\n]+') do
-- Parse porcelain format: "XY filename" or "XY original -> renamed"
local index_status = line:sub(1, 1)
local work_status = line:sub(2, 2)
local filename = line:sub(4) -- Skip "XY " prefix
-- Determine status text (worktree takes precedence over index)
local status_code = work_status ~= ' ' and work_status or index_status
local status_text = status_map[status_code] or 'Changed'
table.insert(qf_list, {
filename = filename,
lnum = 1,
text = status_text,
})
end
return qf_list
end
-- Show highlight group and color information under cursor
-- Returns nothing, displays results in a floating window
function M.show_highlight_info()
local cursor_pos = vim.api.nvim_win_get_cursor(0)
local row, col = cursor_pos[1] - 1, cursor_pos[2]
-- Get all highlight groups at cursor position
local ts_hl = vim.treesitter.get_captures_at_pos(0, row, col)
local synID = vim.fn.synID(row + 1, col + 1, 1)
local synName = vim.fn.synIDattr(synID, 'name')
local synTrans = vim.fn.synIDattr(vim.fn.synIDtrans(synID), 'name')
-- Helper to resolve highlight links
local function resolve_hl(name)
local hl = vim.api.nvim_get_hl(0, { name = name })
local max_depth = 10
local depth = 0
while hl.link and depth < max_depth do
name = hl.link
hl = vim.api.nvim_get_hl(0, { name = name })
depth = depth + 1
end
return hl, name
end
local lines = {
'=== Highlight Info Under Cursor ===',
'',
'Position: row=' .. row .. ' col=' .. col,
'',
}
-- TreeSitter captures
if #ts_hl > 0 then
table.insert(lines, 'TreeSitter Captures:')
for _, capture in ipairs(ts_hl) do
local cap_name = '@' .. capture.capture
local hl, resolved_name = resolve_hl(cap_name)
table.insert(lines, string.format(' %s', cap_name))
if resolved_name ~= cap_name then
table.insert(lines, string.format(' → resolves to: %s', resolved_name))
end
if hl.fg then
table.insert(lines, string.format(' fg: #%06x', hl.fg))
end
if hl.bg then
table.insert(lines, string.format(' bg: #%06x', hl.bg))
end
local styles = {}
if hl.bold then table.insert(styles, 'bold') end
if hl.italic then table.insert(styles, 'italic') end
if hl.underline then table.insert(styles, 'underline') end
if #styles > 0 then
table.insert(lines, ' style: ' .. table.concat(styles, ', '))
end
end
table.insert(lines, '')
end
-- Syntax group
if synName ~= '' then
table.insert(lines, 'Syntax Group: ' .. synName)
if synTrans ~= synName and synTrans ~= '' then
table.insert(lines, 'Translates to: ' .. synTrans)
end
local hl, resolved_name = resolve_hl(synTrans ~= '' and synTrans or synName)
if hl.fg then
table.insert(lines, string.format(' fg: #%06x', hl.fg))
end
if hl.bg then
table.insert(lines, string.format(' bg: #%06x', hl.bg))
end
table.insert(lines, '')
end
-- Final applied highlight (use TreeSitter if available, otherwise syntax)
local final_hl_name = nil
if #ts_hl > 0 then
final_hl_name = '@' .. ts_hl[1].capture
elseif synTrans ~= '' then
final_hl_name = synTrans
elseif synName ~= '' then
final_hl_name = synName
end
if final_hl_name then
local final_hl, final_resolved = resolve_hl(final_hl_name)
table.insert(lines, 'Applied Highlight: ' .. final_resolved)
if final_hl.fg then
table.insert(lines, string.format(' fg: #%06x', final_hl.fg))
else
table.insert(lines, ' fg: NONE')
end
if final_hl.bg then
table.insert(lines, string.format(' bg: #%06x', final_hl.bg))
else
table.insert(lines, ' bg: NONE')
end
local styles = {}
if final_hl.bold then table.insert(styles, 'bold') end
if final_hl.italic then table.insert(styles, 'italic') end
if final_hl.underline then table.insert(styles, 'underline') end
if final_hl.undercurl then table.insert(styles, 'undercurl') end
if #styles > 0 then
table.insert(lines, ' style: ' .. table.concat(styles, ', '))
end
end
-- Show in a floating window
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
local width = 0
for _, line in ipairs(lines) do
width = math.max(width, #line)
end
width = math.min(width + 2, vim.o.columns - 4)
local height = #lines
local opts = {
relative = 'cursor',
width = width,
height = height,
row = 1,
col = 0,
style = 'minimal',
border = 'rounded',
}
vim.api.nvim_open_win(buf, false, opts)
end
-- Custom tabline function
-- Shows configurable number of full parent directories, shortens the rest
function M.custom_tabline()
local tabline = ''
local num_tabs = vim.fn.tabpagenr('$')
for i = 1, num_tabs do
local buflist = vim.fn.tabpagebuflist(i)
local winnr = vim.fn.tabpagewinnr(i)
local bufnr = buflist[winnr]
local bufname = vim.fn.bufname(bufnr)
local bufmodified = vim.fn.getbufvar(bufnr, "&modified")
-- Highlight for the tab
if i == vim.fn.tabpagenr() then
tabline = tabline .. '%#TabLineSel#'
else
tabline = tabline .. '%#TabLine#'
end
-- Tab number
tabline = tabline .. ' ' .. i .. ' '
-- Format the filename with smart path shortening
local filename
if bufname == '' then
filename = '[No Name]'
else
-- Get the full path relative to cwd if possible
local path = vim.fn.fnamemodify(bufname, ':~:.')
-- Split path into components
local parts = vim.split(path, '/', { plain = true })
if #parts > M.tabline_full_parents + 1 then
-- We have enough parts to do smart shortening
local result = {}
-- Shorten the leading directories (all but the last full_parents + filename)
local num_to_shorten = #parts - M.tabline_full_parents - 1
for j = 1, num_to_shorten do
table.insert(result, parts[j]:sub(1, M.tabline_shorten_length))
end
-- Add the full parent directories
for j = num_to_shorten + 1, #parts - 1 do
table.insert(result, parts[j])
end
-- Add the filename
table.insert(result, parts[#parts])
filename = table.concat(result, '/')
else
-- Path is short enough, just use it as-is
filename = path
end
end
-- Add modified flag
if bufmodified == 1 then
filename = filename .. ' [+]'
end
tabline = tabline .. filename .. ' '
end
-- Fill the rest with TabLineFill
tabline = tabline .. '%#TabLineFill#%T'
-- Right-align: show tab page count if more than one tab
if num_tabs > 1 then
tabline = tabline .. '%=%#TabLine# ' .. num_tabs .. ' tabs '
end
return tabline
end
return M

View File

@ -773,43 +773,3 @@ Tengo/!
Trengo
Hostinger's
hPanel
reauthoring
dropdowns
contenteditable
#uer/!
suer/!
RDV
param
int
bool
#/!
bspwm
composable
XDG
configs
Alacritty
overridable
www
Args
autostart
startup
deckboard
bwrap
bubblewrap
hardcodes
connectbot
pcmanfm
sxhkd
dmenu
Gippity
Keepass
bspc
POSIX
shebang
GI
Wyvern
journalwp
Jax
APA
RIS
mathjax

Binary file not shown.