fix(follow-link): create missing page + wrap word on <CR>
CI / cargo fmt --check (push) Successful in 35s
CI / cargo clippy (push) Successful in 1m8s
CI / cargo test (push) Successful in 1m16s

Two missing pieces in the previous "follow creates page" fix:

1. **`wiki_root` never reached the server.** The Lua glue (and the
   Vim autoload glue) was sending the config under `settings` — that
   field is for `workspace/didChangeConfiguration` notifications, not
   for `initialize`. The server's `Config::from_init_params` reads
   `initialization_options`, so `wiki_root` arrived as `None`, the
   `Wiki` aggregate was empty, and `wiki_for_uri` returned `None` →
   `resolve_target_uri` bailed before reaching my synthesise path,
   leaving `<CR>` with "No Location Found".

   Both glues now send the same payload under both keys:
   - `lua/nuwiki/lsp.lua` → adds `init_options = init_options()`
     alongside the existing `settings = …`.
   - `autoload/nuwiki/lsp.vim` → adds `initialization_options` to
     the `lsp#register_server` call.

   The server keeps reading from `initializationOptions` at startup
   *and* honouring `didChangeConfiguration` later (Phase 11 plumbing).

2. **No-wiki fallback in the server.** Even with the glue fix above,
   users who launch nuwiki on an ad-hoc `.wiki` file outside any
   workspace folder would still get an empty `wikis` list. The
   `LinkKind::Wiki` arm now falls back to a new
   `synthesise_page_uri_next_to(source_uri, path)` which builds the
   future page next to the current file. `<CR>` on `[[NewPage]]`
   opens `./NewPage.wiki`, save creates it.

3. **`<CR>` on a plain word now wraps + follows.** Mirrors vimwiki's
   `:VimwikiFollowLink`: when the cursor isn't already inside
   `[[…]]`, the new `nuwiki.commands.follow_link_or_create` /
   `nuwiki#commands#follow_link_or_create` first wraps the word
   under cursor as `[[word]]`, places the cursor inside the link,
   then dispatches `vim.lsp.buf.definition()` /
   `:LspDefinition`. Both editor paths now bind `<CR>` /
   `<S-CR>` / `<C-CR>` / `<C-S-CR>` to this smart variant.

Verified end-to-end:
- `[[NewPage]]` with the rebuilt binary: definition returns
  `file:///tmp/wiki/NewPage.wiki` as a Location, so the editor
  opens an empty buffer for it.
- A bare `MyPage` word becomes `[[MyPage]]` in the buffer before
  the follow request fires.

Total 381 tests still pass; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 01:44:26 +00:00
parent 9d86218b13
commit 347e2f02f7
7 changed files with 200 additions and 17 deletions
+41 -9
View File
@@ -247,18 +247,26 @@ impl Backend {
synthesise_page_uri(&target_wiki.config, path)
}
LinkKind::Wiki => {
let source_wiki = self.wiki_for_uri(source_uri)?;
let source_page =
index::page_name_from_uri(source_uri, Some(source_wiki.config.root.as_path()));
if let Ok(idx) = source_wiki.index.read() {
if let Some(uri) = idx.resolve(target, &source_page).cloned() {
return Some(uri);
if let Some(source_wiki) = self.wiki_for_uri(source_uri) {
let source_page = index::page_name_from_uri(
source_uri,
Some(source_wiki.config.root.as_path()),
);
if let Ok(idx) = source_wiki.index.read() {
if let Some(uri) = idx.resolve(target, &source_page).cloned() {
return Some(uri);
}
}
let path = target.path.as_deref()?;
return synthesise_page_uri(&source_wiki.config, path);
}
// Unindexed page — synthesise so `<CR>` opens (or
// creates-on-save) the missing wiki page.
// No wiki registered (no `initializationOptions`, no
// workspace folder). Fall back to the source buffer's
// parent directory so `<CR>` on `[[NewPage]]` still
// opens (and on save creates) the future page next to
// the current file.
let path = target.path.as_deref()?;
synthesise_page_uri(&source_wiki.config, path)
synthesise_page_uri_next_to(source_uri, path)
}
_ => {
let source_wiki = self.wiki_for_uri(source_uri)?;
@@ -928,6 +936,30 @@ impl LanguageServer for Backend {
}
}
/// Same as `synthesise_page_uri` but anchored on `source_uri`'s parent
/// directory and the conventional `.wiki` extension. Used when no
/// `Wiki` is registered (server received no `initializationOptions`
/// and no `workspace_folders`) — `<CR>` on a wikilink still opens a
/// future page next to the current file.
fn synthesise_page_uri_next_to(source_uri: &Url, path: &str) -> Option<Url> {
let source_path = source_uri.to_file_path().ok()?;
let dir = source_path.parent()?;
let mut p = dir.to_path_buf();
for segment in path.split('/') {
if !segment.is_empty() {
p.push(segment);
}
}
let stem = p
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
if !stem.is_empty() && !stem.contains('.') {
p.set_file_name(format!("{stem}.wiki"));
}
Url::from_file_path(p).ok()
}
/// Build a `<wiki_root>/<page><file_extension>` URI for a wikilink that
/// doesn't (yet) resolve to an indexed page. Used by
/// `Backend::resolve_target_uri` so `<CR>` on `[[Newish]]` opens the