phase 11: plumbing prereqs — Wiki, edits, config, snapshot, diags
Cross-cutting infrastructure that every v1.1 phase depends on. Lands as a numbered phase before any user-visible feature so Phases 12–19 stay mechanical. 5 pieces: - `config.rs` — `Config` + `WikiConfig` + `DiagnosticConfig` + `LinkSeverity`. Serde-deserialised from `initialization_options`. v1.0 single-wiki shape (`wiki_root` + `file_extension` + `syntax`) desugars to one-entry `wikis = [...]` per P16. `~/...` expansion without pulling in `dirs`. Fallbacks: workspace_folders[0] → deprecated root_uri → empty list. - `wiki.rs` — `Wiki` aggregate (id + WikiConfig + Arc<RwLock<Index>>). `build_wikis(&[WikiConfig])` materialises the list; `resolve_uri_to_wiki` picks the longest-prefix root match so nested wikis route correctly. Single-entry today, multi-entry once Phase 18 lands. - `edits.rs` — `WorkspaceEditBuilder` + `text_edit_replace/insert/delete` + `op_rename/delete/create`. Handles the UTF-8/UTF-16 conversion at the span boundary so each Phase 13+ command stays a 3-liner. Builder picks `documentChanges` when file ops are present (LSP requires it), `changes` map otherwise (works for LSP 3.15- clients). File ops precede content edits in the output so created/renamed files exist when edits apply. `DeleteFile` is built without `annotation_id` — lsp-types 0.94.x ships it that way; later versions added the field. - `Backend` refactor — `index: Arc<RwLock<WorkspaceIndex>>` replaced by `wikis: Arc<RwLock<Vec<Wiki>>>` + `config: Arc<RwLock<Config>>`. New `wiki(id)` / `default_wiki()` / `wiki_for_uri(uri)` helpers. Every v1.0 handler (definition, references, hover, completion, workspace/symbol, update_document) goes through `wiki_for_uri`. `initialize` builds wikis from `Config::from_init_params`; `initialized` spawns one indexer task per wiki with the existing `window/workDoneProgress` wrapper. New `did_change_configuration` handler re-applies settings (rebuild semantics revisited in Phase 18). The old `workspace_root_from_params` helper is gone. - `read_or_open` + `DocSnapshot` (scaffolded, `#[allow(dead_code)]` until Phase 13's `workspace/rename` consumes it). Returns the live document when held in the documents map, otherwise async-reads from disk and re-parses so cross-document operations get accurate text and spans instead of trusting stale `WorkspaceIndex` data. - `collect_diagnostics` composable collector. Same `ErrorNode` walk today; takes `Option<&WorkspaceIndex>` + `page_name` + `&DiagnosticConfig` so Phase 15 can drop a link-health source in without further refactor. `ast_diagnostics` kept as a thin wrapper for the v1.0 test surface. New deps: `serde` + `serde_json` (already transitive through tower-lsp; elevated to direct deps for clarity and stability). `tokio` gains the `fs` feature for `read_or_open`'s async disk read. Tests (19 new in `phase11_plumbing.rs`): - Edits: UTF-8 passthrough + UTF-16 conversion (`héllo`), insert/delete shape, builder mode selection, file-op accumulation, `op_create` round-trip - Config: defaults, v1.0 desugar, explicit list overrides legacy, empty JSON, invalid JSON preserves prior state - Wiki: sequential id assignment, longest-prefix resolution, no-match, `Wiki::contains` - Diagnostics: error-node composition, link-health stub returns empty in Phase 11 All 172 v1.0 tests still pass — backward compatible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+223
-91
@@ -21,9 +21,12 @@
|
||||
//! The workspace scan runs in a background tokio task spawned from
|
||||
//! `initialize` (P8) with `window/workDoneProgress` begin/end events.
|
||||
|
||||
pub mod config;
|
||||
pub mod edits;
|
||||
pub mod index;
|
||||
pub mod nav;
|
||||
pub mod semantic_tokens;
|
||||
pub mod wiki;
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
@@ -35,11 +38,11 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tower_lsp::jsonrpc::Result as LspResult;
|
||||
use tower_lsp::lsp_types::{
|
||||
CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
|
||||
Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
|
||||
DidOpenTextDocumentParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
|
||||
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
|
||||
HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, Location,
|
||||
MarkupContent, MarkupKind, MessageType, OneOf, PositionEncodingKind, ProgressParams,
|
||||
Diagnostic, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
|
||||
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbol, DocumentSymbolParams,
|
||||
DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents,
|
||||
HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams,
|
||||
Location, MarkupContent, MarkupKind, MessageType, OneOf, PositionEncodingKind, ProgressParams,
|
||||
ProgressParamsValue, ProgressToken, Range, ReferenceParams, SemanticTokens,
|
||||
SemanticTokensFullOptions, SemanticTokensOptions, SemanticTokensParams,
|
||||
SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult,
|
||||
@@ -57,7 +60,20 @@ use nuwiki_core::ast::{
|
||||
use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
|
||||
use nuwiki_core::syntax::SyntaxRegistry;
|
||||
|
||||
use crate::config::{Config, DiagnosticConfig};
|
||||
use crate::index::{IndexedPage, WorkspaceIndex};
|
||||
use crate::wiki::{build_wikis, resolve_uri_to_wiki, Wiki, WikiId};
|
||||
|
||||
/// Snapshot of a document — either live (held in the documents map) or
|
||||
/// read from disk on demand. Used by cross-document operations
|
||||
/// (`workspace/rename`, link health checks for closed pages, …) so
|
||||
/// callers don't trust potentially stale index spans.
|
||||
#[derive(Debug)]
|
||||
pub struct DocSnapshot {
|
||||
pub text: String,
|
||||
pub ast: DocumentNode,
|
||||
pub in_memory: bool,
|
||||
}
|
||||
|
||||
/// Run the LSP server over the given async reader/writer pair. The binary
|
||||
/// (`nuwiki-ls`) just calls this with stdin/stdout.
|
||||
@@ -96,8 +112,12 @@ struct Backend {
|
||||
/// True after `initialize` if the client opted into UTF-8 encoding.
|
||||
/// Otherwise we keep the LSP 3.16 default of UTF-16.
|
||||
use_utf8: Arc<AtomicBool>,
|
||||
/// Workspace index — populated lazily in the background per P8.
|
||||
index: Arc<RwLock<WorkspaceIndex>>,
|
||||
/// Per-wiki state (P11). Vector holds one entry until Phase 18
|
||||
/// (multi-wiki) lets users add more.
|
||||
wikis: Arc<RwLock<Vec<Wiki>>>,
|
||||
/// Server config received via `initializationOptions` and refreshed
|
||||
/// via `workspace/didChangeConfiguration` (P18).
|
||||
config: Arc<RwLock<Config>>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
@@ -109,10 +129,68 @@ impl Backend {
|
||||
documents: Arc::new(DashMap::new()),
|
||||
registry: Arc::new(registry),
|
||||
use_utf8: Arc::new(AtomicBool::new(false)),
|
||||
index: Arc::new(RwLock::new(WorkspaceIndex::default())),
|
||||
wikis: Arc::new(RwLock::new(Vec::new())),
|
||||
config: Arc::new(RwLock::new(Config::default())),
|
||||
}
|
||||
}
|
||||
|
||||
fn wiki(&self, id: WikiId) -> Option<Wiki> {
|
||||
let wikis = self.wikis.read().ok()?;
|
||||
wikis.iter().find(|w| w.id == id).cloned()
|
||||
}
|
||||
|
||||
/// Scaffolded for Phase 12+ commands that operate on the default wiki
|
||||
/// without a URI in hand (workspace-level operations).
|
||||
#[allow(dead_code)]
|
||||
fn default_wiki(&self) -> Option<Wiki> {
|
||||
let wikis = self.wikis.read().ok()?;
|
||||
wikis.first().cloned()
|
||||
}
|
||||
|
||||
/// Resolve `uri` to the wiki whose root is the longest prefix of it.
|
||||
/// Falls back to the default wiki when no root matches — that's the
|
||||
/// usual case for ad-hoc `.wiki` files outside any registered wiki.
|
||||
fn wiki_for_uri(&self, uri: &Url) -> Option<Wiki> {
|
||||
let id_or_default = {
|
||||
let wikis = self.wikis.read().ok()?;
|
||||
resolve_uri_to_wiki(&wikis, uri).or_else(|| wikis.first().map(|w| w.id))
|
||||
};
|
||||
id_or_default.and_then(|id| self.wiki(id))
|
||||
}
|
||||
|
||||
/// Return live state if `uri` is open in the editor, otherwise read
|
||||
/// the file from disk and re-parse. The closed-doc path is used by
|
||||
/// cross-document operations (Phase 13 `workspace/rename` and link
|
||||
/// health) that need accurate spans + text without trusting cached
|
||||
/// `WorkspaceIndex` data.
|
||||
///
|
||||
/// Scaffolded ahead of the Phase 13 caller — `#[allow(dead_code)]`
|
||||
/// drops once Phase 13 wires it in.
|
||||
#[allow(dead_code)]
|
||||
pub async fn read_or_open(&self, uri: &Url) -> io::Result<DocSnapshot> {
|
||||
if let Some(doc) = self.documents.get(uri) {
|
||||
return Ok(DocSnapshot {
|
||||
text: doc.text.clone(),
|
||||
ast: doc.ast.clone(),
|
||||
in_memory: true,
|
||||
});
|
||||
}
|
||||
let path = uri
|
||||
.to_file_path()
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "non-file URI"))?;
|
||||
let text = tokio::fs::read_to_string(&path).await?;
|
||||
let plugin = self
|
||||
.registry
|
||||
.get("vimwiki")
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "vimwiki syntax plugin missing"))?;
|
||||
let ast = plugin.parse(&text);
|
||||
Ok(DocSnapshot {
|
||||
text,
|
||||
ast,
|
||||
in_memory: false,
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_document(&self, uri: Url, text: String, version: i32) {
|
||||
// For Phase 6 we always treat unknown buffers as vimwiki — if/when
|
||||
// multi-syntax dispatch lands the pick should follow the URI's ext.
|
||||
@@ -126,11 +204,34 @@ impl Backend {
|
||||
}
|
||||
};
|
||||
let ast = plugin.parse(&text);
|
||||
let diagnostics = ast_diagnostics(&ast, &text, self.use_utf8.load(Ordering::Relaxed));
|
||||
let utf8 = self.use_utf8.load(Ordering::Relaxed);
|
||||
|
||||
// Keep the workspace index in sync with what the editor is showing.
|
||||
{
|
||||
let mut idx = self.index.write().expect("index lock poisoned");
|
||||
// Compute diagnostics + update the matching wiki's index. All locks
|
||||
// released before the `await` below.
|
||||
let diagnostics = {
|
||||
let wiki = self.wiki_for_uri(&uri);
|
||||
let cfg = self.config.read().expect("config lock poisoned");
|
||||
let page_name = wiki
|
||||
.as_ref()
|
||||
.map(|w| crate::index::page_name_from_uri(&uri, Some(&w.config.root)));
|
||||
let diags = if let Some(w) = wiki.as_ref() {
|
||||
let idx_guard = w.index.read().expect("index lock poisoned");
|
||||
collect_diagnostics(
|
||||
&ast,
|
||||
&text,
|
||||
Some(&idx_guard),
|
||||
page_name.as_deref(),
|
||||
&cfg.diagnostic,
|
||||
utf8,
|
||||
)
|
||||
} else {
|
||||
collect_diagnostics(&ast, &text, None, None, &cfg.diagnostic, utf8)
|
||||
};
|
||||
diags
|
||||
};
|
||||
|
||||
if let Some(wiki) = self.wiki_for_uri(&uri) {
|
||||
let mut idx = wiki.index.write().expect("index lock poisoned");
|
||||
idx.upsert(uri.clone(), &ast);
|
||||
}
|
||||
|
||||
@@ -161,11 +262,11 @@ impl LanguageServer for Backend {
|
||||
None // server defaults to UTF-16 per LSP 3.16.
|
||||
};
|
||||
|
||||
// Capture the workspace root for the background indexer.
|
||||
let root = workspace_root_from_params(¶ms);
|
||||
if let Some(p) = &root {
|
||||
self.index.write().expect("index lock poisoned").root = Some(p.clone());
|
||||
}
|
||||
// Build server config from initializationOptions (P18) and the
|
||||
// workspace folders, then materialise the `Wiki` aggregate (P11).
|
||||
let cfg = Config::from_init_params(¶ms);
|
||||
*self.wikis.write().expect("wikis lock poisoned") = build_wikis(&cfg.wikis);
|
||||
*self.config.write().expect("config lock poisoned") = cfg;
|
||||
|
||||
Ok(InitializeResult {
|
||||
capabilities: ServerCapabilities {
|
||||
@@ -214,18 +315,31 @@ impl LanguageServer for Backend {
|
||||
)
|
||||
.await;
|
||||
|
||||
// Kick off the background workspace scan (P8). Skips if no root.
|
||||
let root = self.index.read().expect("index lock poisoned").root.clone();
|
||||
if let Some(root) = root {
|
||||
let index = Arc::clone(&self.index);
|
||||
// Kick off the background workspace scan (P8) — one task per wiki.
|
||||
let wikis = self.wikis.read().expect("wikis lock poisoned").clone();
|
||||
for wiki in wikis {
|
||||
if wiki.config.root.as_os_str().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let client = self.client.clone();
|
||||
let registry = Arc::clone(&self.registry);
|
||||
let index = Arc::clone(&wiki.index);
|
||||
let root = wiki.config.root.clone();
|
||||
tokio::spawn(async move {
|
||||
index_workspace(root, index, client, registry).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
|
||||
// Apply the new settings to `Config`. Re-indexing on root changes
|
||||
// is out of scope for Phase 11; for now we just accept the new
|
||||
// values so later commands see the right diagnostic settings etc.
|
||||
// Phase 18 (multi-wiki) revisits this with proper rebuild semantics.
|
||||
let mut cfg = self.config.write().expect("config lock poisoned");
|
||||
cfg.apply_change(¶ms.settings);
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> LspResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -318,22 +432,25 @@ impl LanguageServer for Backend {
|
||||
};
|
||||
let location = match node {
|
||||
InlineNode::WikiLink(w) => {
|
||||
let source_page = index::page_name_from_uri(
|
||||
uri,
|
||||
self.index
|
||||
.read()
|
||||
.expect("index lock poisoned")
|
||||
.root
|
||||
.as_deref(),
|
||||
);
|
||||
let idx = self.index.read().expect("index lock poisoned");
|
||||
let target_uri = idx.resolve(&w.target, &source_page).cloned();
|
||||
let wiki = self.wiki_for_uri(uri);
|
||||
let source_page =
|
||||
index::page_name_from_uri(uri, wiki.as_ref().map(|w| w.config.root.as_path()));
|
||||
let idx_guard = wiki
|
||||
.as_ref()
|
||||
.map(|w| w.index.read().expect("index lock poisoned"));
|
||||
let target_uri = idx_guard
|
||||
.as_ref()
|
||||
.and_then(|idx| idx.resolve(&w.target, &source_page).cloned());
|
||||
target_uri.map(|target| {
|
||||
let target_range = w
|
||||
.target
|
||||
.anchor
|
||||
.as_deref()
|
||||
.and_then(|a| heading_range_in(&idx, &target, a, utf8))
|
||||
.and_then(|a| {
|
||||
idx_guard
|
||||
.as_ref()
|
||||
.and_then(|idx| heading_range_in(idx, &target, a, utf8))
|
||||
})
|
||||
.unwrap_or_else(zero_range);
|
||||
Location {
|
||||
uri: target,
|
||||
@@ -360,14 +477,9 @@ impl LanguageServer for Backend {
|
||||
// about". Two cases:
|
||||
// - cursor is on a wikilink → references to the linked page
|
||||
// - cursor isn't on a link → references to the *current* page
|
||||
let source_page = index::page_name_from_uri(
|
||||
uri,
|
||||
self.index
|
||||
.read()
|
||||
.expect("index lock poisoned")
|
||||
.root
|
||||
.as_deref(),
|
||||
);
|
||||
let wiki = self.wiki_for_uri(uri);
|
||||
let source_page =
|
||||
index::page_name_from_uri(uri, wiki.as_ref().map(|w| w.config.root.as_path()));
|
||||
let target_page = match nav::find_inline_at(&doc.ast, line, col) {
|
||||
Some(InlineNode::WikiLink(w)) => match w.target.kind {
|
||||
nuwiki_core::ast::LinkKind::Wiki => w.target.path.clone(),
|
||||
@@ -379,7 +491,10 @@ impl LanguageServer for Backend {
|
||||
let Some(page) = target_page else {
|
||||
return Ok(None);
|
||||
};
|
||||
let idx = self.index.read().expect("index lock poisoned");
|
||||
let Some(wiki) = wiki else {
|
||||
return Ok(Some(Vec::new()));
|
||||
};
|
||||
let idx = wiki.index.read().expect("index lock poisoned");
|
||||
let locations: Vec<Location> = idx
|
||||
.backlinks_for(&page)
|
||||
.iter()
|
||||
@@ -420,22 +535,21 @@ impl LanguageServer for Backend {
|
||||
|
||||
let (markdown, hover_span) = match node {
|
||||
InlineNode::WikiLink(w) => {
|
||||
let source_page = index::page_name_from_uri(
|
||||
uri,
|
||||
self.index
|
||||
.read()
|
||||
.expect("index lock poisoned")
|
||||
.root
|
||||
.as_deref(),
|
||||
);
|
||||
let idx = self.index.read().expect("index lock poisoned");
|
||||
let target_uri = idx.resolve(&w.target, &source_page).cloned();
|
||||
let body = match target_uri.as_ref().and_then(|u| idx.page(u)) {
|
||||
Some(page) => preview_for(page, w.target.anchor.as_deref()),
|
||||
None => match &w.target.path {
|
||||
Some(p) => format!("**{p}** — page not in index"),
|
||||
None => "(unresolved link)".into(),
|
||||
},
|
||||
let wiki = self.wiki_for_uri(uri);
|
||||
let source_page =
|
||||
index::page_name_from_uri(uri, wiki.as_ref().map(|w| w.config.root.as_path()));
|
||||
let body = if let Some(wiki) = wiki.as_ref() {
|
||||
let idx = wiki.index.read().expect("index lock poisoned");
|
||||
let target_uri = idx.resolve(&w.target, &source_page).cloned();
|
||||
match target_uri.as_ref().and_then(|u| idx.page(u)) {
|
||||
Some(page) => preview_for(page, w.target.anchor.as_deref()),
|
||||
None => match &w.target.path {
|
||||
Some(p) => format!("**{p}** — page not in index"),
|
||||
None => "(unresolved link)".into(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
"(no wiki configured)".into()
|
||||
};
|
||||
(body, w.span)
|
||||
}
|
||||
@@ -465,7 +579,10 @@ impl LanguageServer for Backend {
|
||||
if !is_inside_wikilink(&doc.text, line, col) {
|
||||
return Ok(None);
|
||||
}
|
||||
let idx = self.index.read().expect("index lock poisoned");
|
||||
let Some(wiki) = self.wiki_for_uri(uri) else {
|
||||
return Ok(Some(CompletionResponse::Array(Vec::new())));
|
||||
};
|
||||
let idx = wiki.index.read().expect("index lock poisoned");
|
||||
let items: Vec<CompletionItem> = idx
|
||||
.page_names()
|
||||
.into_iter()
|
||||
@@ -484,23 +601,26 @@ impl LanguageServer for Backend {
|
||||
params: WorkspaceSymbolParams,
|
||||
) -> LspResult<Option<Vec<LspSymbolInformation>>> {
|
||||
let q = params.query.to_lowercase();
|
||||
let idx = self.index.read().expect("index lock poisoned");
|
||||
let wikis = self.wikis.read().expect("wikis lock poisoned").clone();
|
||||
let mut out = Vec::new();
|
||||
for page in idx.pages_by_uri.values() {
|
||||
for h in &page.headings {
|
||||
if q.is_empty() || h.title.to_lowercase().contains(&q) {
|
||||
#[allow(deprecated)]
|
||||
out.push(LspSymbolInformation {
|
||||
name: h.title.clone(),
|
||||
kind: SymbolKind::STRING,
|
||||
tags: None,
|
||||
deprecated: None,
|
||||
location: Location {
|
||||
uri: page.uri.clone(),
|
||||
range: span_to_lsp_range_no_text(&h.span),
|
||||
},
|
||||
container_name: Some(page.name.clone()),
|
||||
});
|
||||
for wiki in &wikis {
|
||||
let idx = wiki.index.read().expect("index lock poisoned");
|
||||
for page in idx.pages_by_uri.values() {
|
||||
for h in &page.headings {
|
||||
if q.is_empty() || h.title.to_lowercase().contains(&q) {
|
||||
#[allow(deprecated)]
|
||||
out.push(LspSymbolInformation {
|
||||
name: h.title.clone(),
|
||||
kind: SymbolKind::STRING,
|
||||
tags: None,
|
||||
deprecated: None,
|
||||
location: Location {
|
||||
uri: page.uri.clone(),
|
||||
range: span_to_lsp_range_no_text(&h.span),
|
||||
},
|
||||
container_name: Some(page.name.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -579,9 +699,34 @@ fn utf16_column(text: &str, line: u32, byte_col: u32) -> u32 {
|
||||
.sum::<usize>() as u32
|
||||
}
|
||||
|
||||
/// Backward-compat wrapper kept for v1.0 test surface. New call sites
|
||||
/// should use `collect_diagnostics` so they can opt into link-health
|
||||
/// (Phase 15) and other sources.
|
||||
pub fn ast_diagnostics(ast: &DocumentNode, text: &str, utf8: bool) -> Vec<Diagnostic> {
|
||||
collect_diagnostics(ast, text, None, None, &DiagnosticConfig::default(), utf8)
|
||||
}
|
||||
|
||||
/// Composable diagnostic collector (P11 §12.2.4).
|
||||
///
|
||||
/// Sources, each gated by `cfg`:
|
||||
///
|
||||
/// 1. Parse errors (always on).
|
||||
/// 2. Broken-link warnings — wired up in Phase 15. For Phase 11 the
|
||||
/// `index` / `page_name` / `cfg.link_severity` arguments are
|
||||
/// accepted but the source is a stub returning no diagnostics, so
|
||||
/// handlers can already pass them in without further refactor.
|
||||
pub fn collect_diagnostics(
|
||||
ast: &DocumentNode,
|
||||
text: &str,
|
||||
index: Option<&WorkspaceIndex>,
|
||||
page_name: Option<&str>,
|
||||
cfg: &DiagnosticConfig,
|
||||
utf8: bool,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut out = Vec::new();
|
||||
walk_blocks_for_errors(&ast.children, text, utf8, &mut out);
|
||||
// Phase 15 will implement link-health here.
|
||||
let _ = (index, page_name, cfg);
|
||||
out
|
||||
}
|
||||
|
||||
@@ -732,23 +877,10 @@ fn keyword_str(k: nuwiki_core::ast::Keyword) -> &'static str {
|
||||
}
|
||||
|
||||
// ===== Workspace + navigation helpers =====
|
||||
|
||||
fn workspace_root_from_params(params: &InitializeParams) -> Option<PathBuf> {
|
||||
if let Some(folders) = ¶ms.workspace_folders {
|
||||
if let Some(first) = folders.first() {
|
||||
if let Ok(p) = first.uri.to_file_path() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
if let Some(uri) = ¶ms.root_uri {
|
||||
if let Ok(p) = uri.to_file_path() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
//
|
||||
// `workspace_root_from_params` lived here through Phase 10; Phase 11 lifted
|
||||
// that logic into `Config::from_init_params` (see `config.rs`) so workspace
|
||||
// folders and `root_uri` are handled alongside `initializationOptions`.
|
||||
|
||||
/// Background workspace scan (P8). Walks every `.wiki` file under `root`,
|
||||
/// parses it, and feeds it into the shared index. Wraps the work in a
|
||||
|
||||
Reference in New Issue
Block a user