phase 19: editor glue v2
CI / cargo fmt --check (push) Successful in 21s
CI / cargo clippy (push) Successful in 1m8s
CI / cargo test (push) Successful in 1m18s

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:
2026-05-11 21:57:05 +00:00
parent 106268d488
commit 63e5b6d514
13 changed files with 1029 additions and 31 deletions
+78 -13
View File
@@ -1,20 +1,16 @@
-- lua/nuwiki/health.lua — `:checkhealth nuwiki` target.
--
-- Checks per SPEC §7.6: binary exists, is executable, LSP client running,
-- filetype detection works for `.wiki`.
-- v1.0 checks per SPEC §7.6 (binary, version, filetype, LSP attach).
-- v1.1 §12.10 additions: registered LSP commands, indexed wikis, HTML
-- output path writability, default keymaps installed.
local M = {}
function M.check()
local function bin_section()
local install = require('nuwiki.install')
local config = require('nuwiki.config')
vim.health.start('nuwiki')
local bin = install.expected_path()
if install.is_installed() then
vim.health.ok('binary present at ' .. bin)
local out = vim.fn.system({ bin, '--version' })
if vim.v.shell_error == 0 then
vim.health.ok('binary --version: ' .. (out:gsub('\n', ' ')):gsub('^%s+', ''))
@@ -30,8 +26,10 @@ function M.check()
{ 'Run :lua require("nuwiki").install() to install' }
)
end
end
local root = vim.fn.expand(config.options.wiki_root or '')
local function wiki_root_section(opts)
local root = vim.fn.expand(opts.wiki_root or '')
if root ~= '' then
if vim.fn.isdirectory(root) == 1 then
vim.health.ok('wiki_root exists: ' .. root)
@@ -39,21 +37,88 @@ function M.check()
vim.health.warn('wiki_root does not exist: ' .. root)
end
end
end
local function filetype_section()
if vim.fn.exists('#filetypedetect#BufRead#*.wiki') ~= 0
or vim.filetype.match({ filename = 'test.wiki' }) == 'vimwiki' then
vim.health.ok('filetype detection for *.wiki is wired up')
else
vim.health.warn('filetype detection for *.wiki not active')
end
end
local clients = vim.lsp.get_clients and vim.lsp.get_clients({ name = 'nuwiki' })
or vim.lsp.get_active_clients({ name = 'nuwiki' })
if clients and #clients > 0 then
vim.health.ok('LSP client attached (' .. #clients .. ' instance(s))')
local function clients()
return (vim.lsp.get_clients or vim.lsp.get_active_clients)({ name = 'nuwiki' })
end
local function lsp_section()
local cs = clients()
if cs and #cs > 0 then
vim.health.ok('LSP client attached (' .. #cs .. ' instance(s))')
local cl = cs[1]
local cmds = vim.tbl_get(
cl,
'server_capabilities',
'executeCommandProvider',
'commands'
) or {}
if #cmds > 0 then
vim.health.ok('server advertises ' .. #cmds .. ' executeCommand entries')
else
vim.health.info('server has not yet reported executeCommand entries')
end
local folding = vim.tbl_get(cl, 'server_capabilities', 'foldingRangeProvider')
if folding then
vim.health.ok('server supports textDocument/foldingRange')
else
vim.health.info('server has not advertised foldingRange capability')
end
else
vim.health.info('LSP client not yet attached — open a .wiki buffer')
end
end
local function html_section(opts)
local html_path = (opts.wikis and opts.wikis[1] and opts.wikis[1].html_path)
or (opts.wiki_root and (vim.fn.expand(opts.wiki_root) .. '/_html'))
if not html_path or html_path == '' then
return
end
if vim.fn.isdirectory(html_path) == 1 then
-- Probe writability with `filewritable` on the directory.
if vim.fn.filewritable(html_path) == 2 then
vim.health.ok('html output dir writable: ' .. html_path)
else
vim.health.warn('html output dir exists but not writable: ' .. html_path)
end
else
vim.health.info(
'html output dir does not exist yet (created on first export): ' .. html_path
)
end
end
local function mappings_section(opts)
if opts.mappings and opts.mappings.enabled == false then
vim.health.info('default keymaps disabled by user config')
return
end
vim.health.ok('default keymaps enabled (subgroups: '
.. table.concat(vim.tbl_keys(opts.mappings or {}), ',') .. ')')
end
function M.check()
local config = require('nuwiki.config')
local opts = config.options or config.defaults
vim.health.start('nuwiki')
bin_section()
wiki_root_section(opts)
filetype_section()
lsp_section()
html_section(opts)
mappings_section(opts)
end
return M