diff --git a/README.md b/README.md
index 6c9adb9..3d20988 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,10 @@
A Vim/Neovim plugin providing full vimwiki syntax support, implemented as a
Rust-based language server (LSP).
-> **Status:** Phase 6 (LSP foundation) complete. `nuwiki-ls` speaks LSP over
-> stdio: didOpen/didChange/didClose, parse diagnostics, nested document
-> outline. Editor glue, semantic tokens, and navigation still pending.
+> **Status:** Phase 7 (semantic tokens) complete. The server now serves
+> `semanticTokens/full` + `/range` with custom `vimwiki*` token types and
+> `level1`..`level6` + `centered` modifiers. Navigation features and
+> editor glue still pending.
See [`SPEC.md`](./SPEC.md) for the full project specification.
@@ -20,8 +21,8 @@ See [`SPEC.md`](./SPEC.md) for the full project specification.
| 4 | Vimwiki Parser | ✅ done |
| 5 | Renderer | ✅ done |
| 6 | LSP Foundation | ✅ done |
-| 7 | Semantic Tokens | ⏳ next |
-| 8 | Navigation | ⏳ |
+| 7 | Semantic Tokens | ✅ done |
+| 8 | Navigation | ⏳ next |
| 9 | Editor Glue | ⏳ |
| 10 | CI/CD release pipeline | ⏳ |
diff --git a/SPEC.md b/SPEC.md
index 2bd6c22..16708a8 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -1,7 +1,7 @@
# nuwiki — Project Specification
> Last updated: 2026-05-10
-> Status: Phase 6 (LSP Foundation) complete — moving to Phase 7 (Semantic Tokens)
+> Status: Phase 7 (Semantic Tokens) complete — moving to Phase 8 (Navigation)
---
@@ -590,7 +590,7 @@ These decisions are required before or during the phase indicated.
| ~~P4~~ | ~~**Visitor pattern**~~ | ~~Phase 1~~ | ✅ **Defined in Phase 1** | Open-recursion `Visitor` trait + `walk_*` helpers |
| P5 | **Minimum Neovim version** | Phase 9 | 0.8 (`vim.lsp.start`) · 0.11 (`vim.lsp.config`) | 0.8 = broader compat; 0.11 = cleaner Lua API |
| P6 | **Minimum Vim version** | Phase 9 | Vim 8.2 · Vim 9.0 · Vim 9.1 | LSP client plugins work best on 9.0+; recommend documenting 9.1 as minimum |
-| P7 | **Semantic token type mapping** | Phase 7 | Standard LSP types only · Custom token types | Custom types need client-side highlight group definitions; more flexible but more setup for users |
+| ~~P7~~ | ~~**Semantic token type mapping**~~ | ~~Phase 7~~ | ✅ **Custom vimwiki-specific token types** | ~20 types (`vimwikiHeading`, `vimwikiBold`, …) + `level1`..`level6` + `centered` modifiers. Phase 9 ships default highlight groups in `syntax/nuwiki.vim` and the Lua glue. |
| P8 | **Workspace indexing strategy** | Phase 8 | Eager on startup · Lazy + background | Eager blocks startup on large wikis; recommend lazy with progress notification |
| P9 | **macOS runner** | Phase 10 | Stay manual · Register macOS runner
| Revisit if a Mac is available; no action needed now |
diff --git a/crates/nuwiki-lsp/src/lib.rs b/crates/nuwiki-lsp/src/lib.rs
index 52fc5b2..d00aa88 100644
--- a/crates/nuwiki-lsp/src/lib.rs
+++ b/crates/nuwiki-lsp/src/lib.rs
@@ -14,6 +14,8 @@
//!
//! Re-parses are full per change (incremental parsing deferred post-v1).
+pub mod semantic_tokens;
+
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -25,8 +27,10 @@ use tower_lsp::lsp_types::{
Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
InitializeParams, InitializeResult, InitializedParams, MessageType, OneOf,
- PositionEncodingKind, ServerCapabilities, ServerInfo, SymbolKind, TextDocumentSyncCapability,
- TextDocumentSyncKind, Url,
+ PositionEncodingKind, SemanticTokens, SemanticTokensFullOptions, SemanticTokensOptions,
+ SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
+ SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities, ServerInfo,
+ SymbolKind, TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkDoneProgressOptions,
};
use tower_lsp::{Client, LanguageServer, LspService, Server};
@@ -136,6 +140,16 @@ impl LanguageServer for Backend {
TextDocumentSyncKind::FULL,
)),
document_symbol_provider: Some(OneOf::Left(true)),
+ semantic_tokens_provider: Some(
+ SemanticTokensServerCapabilities::SemanticTokensOptions(
+ SemanticTokensOptions {
+ work_done_progress_options: WorkDoneProgressOptions::default(),
+ legend: semantic_tokens::legend(),
+ range: Some(true),
+ full: Some(SemanticTokensFullOptions::Bool(true)),
+ },
+ ),
+ ),
..ServerCapabilities::default()
},
server_info: Some(ServerInfo {
@@ -199,6 +213,50 @@ impl LanguageServer for Backend {
headings_to_symbols(&doc.ast, &doc.text, self.use_utf8.load(Ordering::Relaxed));
Ok(Some(DocumentSymbolResponse::Nested(symbols)))
}
+
+ async fn semantic_tokens_full(
+ &self,
+ params: SemanticTokensParams,
+ ) -> LspResult