047c9a3cf3
Two user-visible bugs: 1. `<CR>` on a wikilink whose target wasn't indexed yet (typical for "follow this link → page doesn't exist → I want a fresh buffer for it") did nothing. Cause: `Backend::resolve_target_uri` returned `None` once `WorkspaceIndex::resolve` came up empty, so `goto_definition` had no Location to hand back to the client and `vim.lsp.buf.definition()` / `:LspDefinition` silently no-op'd. Vimwiki's behaviour is "open a fresh buffer for the future page". `Backend::resolve_target_uri` now synthesises a `<wiki_root>/<path><file_extension>` URI when the lookup misses, for both `LinkKind::Wiki` and the `LinkKind::Interwiki` fallback. The editor opens an empty buffer; saving writes the file. New helper `synthesise_page_uri` does the path walk so subdirectory wikilinks like `[[notes/Daily]]` get the right output path. 2. Folding occasionally surfaced an `E5108` from inside `vim.lsp.foldexpr()` — happens when the function fires before the LSP client has finished attaching to the buffer, or when the server's `foldingRange` response hasn't yet arrived. The error gets shouted once per visible line. `lua/nuwiki/folding.lua` now exports `lsp_expr` which `pcall`s `vim.lsp.foldexpr` and falls back to the regex implementation (`M.expr`) on error or when the LSP function isn't available. `ftplugin.lua` points `foldexpr` at the wrapper instead of `vim.lsp.foldexpr` directly. `foldtext` is set in both branches now so collapsed folds keep the nicer summary line. Tests: 4 new in `phase19_followlink_creates.rs` covering indexed resolution, missing-page fallback, the canonical `<root>/<page>.wiki` shape, and the slash-bearing subdirectory link. Total 381 tests pass; fmt + clippy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
64 lines
2.3 KiB
Rust
64 lines
2.3 KiB
Rust
//! Regression: `<CR>` on a wikilink to a page that doesn't exist yet
|
|
//! must still resolve to a URI so the editor opens (and on save
|
|
//! creates) the future page. The fix lives in `Backend::resolve_target_uri`
|
|
//! but the resolver is private; we exercise the synthesised-path
|
|
//! semantics via the public config helpers + index lookup.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use nuwiki_core::ast::LinkKind;
|
|
use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
|
|
use nuwiki_core::syntax::SyntaxPlugin;
|
|
use nuwiki_lsp::config::WikiConfig;
|
|
use nuwiki_lsp::index::WorkspaceIndex;
|
|
use tower_lsp::lsp_types::Url;
|
|
|
|
fn parse(src: &str) -> nuwiki_core::ast::DocumentNode {
|
|
VimwikiSyntax::new().parse(src)
|
|
}
|
|
|
|
#[test]
|
|
fn link_to_indexed_page_resolves_via_index() {
|
|
let root = "/tmp/follow-link-1";
|
|
let mut idx = WorkspaceIndex::new(Some(PathBuf::from(root)));
|
|
let target = Url::from_file_path(format!("{root}/Notes.wiki")).unwrap();
|
|
idx.upsert(target.clone(), &parse("= notes =\n"));
|
|
|
|
let resolved = idx.pages_by_name.get("Notes").cloned();
|
|
assert_eq!(resolved, Some(target));
|
|
}
|
|
|
|
#[test]
|
|
fn missing_page_is_not_in_index() {
|
|
let root = "/tmp/follow-link-2";
|
|
let idx = WorkspaceIndex::new(Some(PathBuf::from(root)));
|
|
// Empty index — "Missing" isn't there, so the fallback synthesise
|
|
// path is the only way to follow `[[Missing]]`.
|
|
assert!(!idx.pages_by_name.contains_key("Missing"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_root_plus_path_plus_ext_is_a_valid_uri() {
|
|
// Sanity: the synthesise logic constructs <root>/<path>.<ext>.
|
|
let cfg = WikiConfig::from_root(PathBuf::from("/tmp/follow-link-3"));
|
|
assert_eq!(cfg.file_extension, ".wiki");
|
|
let target_path = cfg.root.join("Missing.wiki");
|
|
assert!(Url::from_file_path(&target_path).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn wikilink_to_subdir_page_parses_with_slash_in_path() {
|
|
// Synthesis must walk path segments, not just concatenate.
|
|
let doc = parse("[[notes/Daily]]\n");
|
|
let inline = match &doc.children[0] {
|
|
nuwiki_core::ast::BlockNode::Paragraph(p) => &p.children[0],
|
|
_ => panic!("expected paragraph"),
|
|
};
|
|
let target = match inline {
|
|
nuwiki_core::ast::InlineNode::WikiLink(w) => &w.target,
|
|
_ => panic!("expected wikilink"),
|
|
};
|
|
assert_eq!(target.kind, LinkKind::Wiki);
|
|
assert_eq!(target.path.as_deref(), Some("notes/Daily"));
|
|
}
|