phase 19: editor glue v2
Server-side:
- New `folding.rs` module with `folding_ranges(ast, total_lines)`.
Emits one fold per top-level heading (start → line before the
next same-or-higher-level heading, or EOF) plus one per top-level
list block. Nested headings fold inside their parent thanks to the
level-aware end-line computation; sublists fold implicitly via
their parent item's source span. `FoldingRangeKind::Region` is set
on every fold so collapsing UIs render them as section folds.
- `Backend::folding_range` handler wires the module into
`textDocument/foldingRange`; `ServerCapabilities.folding_range_provider`
advertises it.
- `folding::line_count` exposed for the handler and tests; treats a
trailing newline as a virtual empty line (the line LSP positions
use for EOF).
Editor glue (Neovim primary, Vim minimal):
- `lua/nuwiki/commands.lua` — async `workspace/executeCommand`
wrappers for every server command shipped through Phase 18. The
open-* family handles `{ uri }` responses by opening the file
(with `tab` / `split` variants). `check_links` and `find_orphans`
hand results to `setqflist` + `:copen` for quickfix-style review.
§13.1-deferred commands (`list.changeSymbol`, table rewriters,
`link.pasteWikilink/pasteUrl`, `colorize`) stub out with
`vim.notify` so users get a clear "not yet implemented" signal
instead of an LSP "unknown command" error.
- `lua/nuwiki/keymaps.lua` — buffer-local default mappings. Subgroups
(`list_editing`, `header_nav`, `diary`, `html_export`,
`text_objects`) flip independently via the new
`mappings.<group>` config. Heading promote/demote uses `g=`/`g-`
to avoid clobbering Vim's built-in `=`/`-` operators.
- `lua/nuwiki/textobjects.lua` — `ah`/`ih` (around/inside heading)
using a buffer scan for the heading-block boundary. The four
remaining text objects from SPEC §12.10 wait until §13.1 lands
the table/list rewriters they share infrastructure with.
- `lua/nuwiki/folding.lua` — pure regex `foldexpr` + `foldtext`
fallback for clients without `foldingRange`. Same heading-block
model as the server.
- `lua/nuwiki/ftplugin.lua` — single per-buffer attach entry point.
`folding = 'lsp'` (default) uses Neovim 0.11+'s `vim.lsp.foldexpr`,
falling back to the regex on older versions; `'expr'` forces the
fallback; `'off'` skips folding setup.
- `lua/nuwiki/config.lua` — extends defaults with `mappings = {...}`
(P10 keymap layer) and `folding` (P14 resolved).
- `ftplugin/nuwiki.vim` — declares every `:Vimwiki*` / `:Nuwiki*`
command from SPEC §12.10 by inlining `lua require(...).fn()`
bodies (no script-local function indirection so commands stay
callable after re-source). Plain-Vim users get only the buffer
options; they're expected to drive the LSP via vim-lsp / coc's
built-in commands.
Health check (§12.10 additions):
- `:checkhealth nuwiki` now reports the count of `executeCommand`
entries the server advertises, whether `foldingRange` capability
was negotiated, and whether the configured HTML output directory
exists + is writable. Default keymap subgroup status is also
surfaced.
Tests: 8 new in `phase19_folding.rs` covering empty docs, single
heading → EOF, sibling headings each getting their own fold,
nested heading bounded by parent, top-level list folds, single-line
heading non-fold, fold-kind classification, and `line_count`
semantics. Total 377 tests pass; fmt + clippy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
-- lua/nuwiki/keymaps.lua — buffer-local key bindings for `.wiki` buffers.
|
||||
--
|
||||
-- Opt-in via `mappings.enabled = true` (default). Subgroups can be
|
||||
-- disabled independently — see `defaults.lua` for the schema.
|
||||
--
|
||||
-- Mappings here cover the subset of vimwiki's defaults whose server-side
|
||||
-- commands are implemented. Anything pointing at a SPEC §13.1 deferred
|
||||
-- command is intentionally omitted to avoid surprising no-ops.
|
||||
|
||||
local M = {}
|
||||
|
||||
local function map(mode, lhs, rhs, opts, bufnr)
|
||||
opts = vim.tbl_extend('force', { buffer = bufnr or 0, silent = true }, opts or {})
|
||||
vim.keymap.set(mode, lhs, rhs, opts)
|
||||
end
|
||||
|
||||
--- Register all keymaps for the given buffer. Caller passes the
|
||||
--- effective `mappings` config (already merged with defaults).
|
||||
function M.attach(bufnr, mappings)
|
||||
local cmd = require('nuwiki.commands')
|
||||
|
||||
if not mappings or mappings.enabled == false then
|
||||
return
|
||||
end
|
||||
|
||||
-- ===== Wiki navigation =====
|
||||
map('n', '<Leader>ww', cmd.diary_today, { desc = 'nuwiki: open today\'s diary' }, bufnr)
|
||||
map('n', '<Leader>wt', cmd.diary_today, { desc = 'nuwiki: open today\'s diary' }, bufnr)
|
||||
map('n', '<Leader>ws', cmd.wiki_ui_select, { desc = 'nuwiki: pick wiki' }, bufnr)
|
||||
map('n', '<Leader>wi', cmd.diary_index, { desc = 'nuwiki: open diary index' }, bufnr)
|
||||
map('n', '<Leader>w<Leader>w', cmd.diary_today, { desc = 'nuwiki: today' }, bufnr)
|
||||
map('n', '<Leader>w<Leader>y', cmd.diary_yesterday, { desc = 'nuwiki: yesterday' }, bufnr)
|
||||
map('n', '<Leader>w<Leader>t', cmd.diary_tomorrow, { desc = 'nuwiki: tomorrow' }, bufnr)
|
||||
map('n', '<Leader>wd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr)
|
||||
map('n', '<Leader>wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr)
|
||||
map('n', '<Leader>wn', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr)
|
||||
|
||||
-- Link follow / nav
|
||||
map('n', '<CR>', vim.lsp.buf.definition, { desc = 'nuwiki: follow link' }, bufnr)
|
||||
map('n', '<Backspace>', '<C-o>', { desc = 'nuwiki: go back', remap = true }, bufnr)
|
||||
|
||||
-- ===== Diary nav =====
|
||||
if mappings.diary ~= false then
|
||||
map('n', '<C-Down>', cmd.diary_next, { desc = 'nuwiki: next diary entry' }, bufnr)
|
||||
map('n', '<C-Up>', cmd.diary_prev, { desc = 'nuwiki: previous diary entry' }, bufnr)
|
||||
end
|
||||
|
||||
-- ===== List editing =====
|
||||
if mappings.list_editing ~= false then
|
||||
map('n', '<C-Space>', cmd.toggle_list_item, { desc = 'nuwiki: toggle checkbox' }, bufnr)
|
||||
map('n', 'gnt', cmd.next_task, { desc = 'nuwiki: next unfinished task' }, bufnr)
|
||||
map('n', 'gln', cmd.cycle_list_item, { desc = 'nuwiki: cycle checkbox' }, bufnr)
|
||||
map('n', 'glp', cmd.cycle_list_item, { desc = 'nuwiki: cycle checkbox' }, bufnr)
|
||||
map('n', 'glx', cmd.reject_list_item, { desc = 'nuwiki: reject checkbox' }, bufnr)
|
||||
end
|
||||
|
||||
-- ===== Header level =====
|
||||
if mappings.header_nav ~= false then
|
||||
-- `=`/`-` is the vimwiki default but collides with Vim's built-in
|
||||
-- reformat operator. Use `g=` / `g-` instead to stay out of the way.
|
||||
map('n', 'g=', cmd.heading_add_level, { desc = 'nuwiki: heading deeper' }, bufnr)
|
||||
map('n', 'g-', cmd.heading_remove_level, { desc = 'nuwiki: heading shallower' }, bufnr)
|
||||
end
|
||||
|
||||
-- ===== HTML export =====
|
||||
if mappings.html_export ~= false then
|
||||
map('n', '<Leader>wh', cmd.export_current, { desc = 'nuwiki: export to HTML' }, bufnr)
|
||||
map('n', '<Leader>whh', cmd.export_browse, { desc = 'nuwiki: export + browse' }, bufnr)
|
||||
map('n', '<Leader>wha', cmd.export_all, { desc = 'nuwiki: export all' }, bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user