From ee61d4087210addd08c2bcc823990bb0bdb4969d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Mon, 11 May 2026 00:38:09 +0000 Subject: [PATCH] =?UTF-8?q?phase=209:=20editor=20glue=20=E2=80=94=20plugin?= =?UTF-8?q?,=20LSP=20wiring,=20install,=20health,=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P5 + P6 resolved (SPEC §11): Neovim 0.11+ and Vim 9.1+. Universal Vim entry: - plugin/nuwiki.vim dispatches Vim vs Neovim. Neovim path waits for the user's `require('nuwiki').setup()` call; Vim path starts the LSP client on `FileType vimwiki` and exposes `:NuwikiInstall`. - ftdetect/nuwiki.vim associates `*.wiki` with the `vimwiki` filetype. - ftplugin/nuwiki.vim sets commentstring + comments + formatoptions + suffixesadd + iskeyword, with a clean `b:undo_ftplugin`. - syntax/nuwiki.vim ships default `@vimwiki*` highlight links for the semantic-token legend (with `level1..6` + `centered` modifiers), plus a minimal regex fallback so static highlighting works before the LSP attaches. Lua glue (Neovim 0.11+): - init.lua exposes setup() / install() / health() — wiring only per SPEC §6.2. - config.lua holds the §7.5 schema (wiki_root / file_extension / syntax / log_level) and a tbl_deep_extend apply(). - lsp.lua registers via `vim.lsp.config{} + vim.lsp.enable` on 0.11+; falls back to a FileType autocmd calling `vim.lsp.start` on older builds. Root dir walks up for `.git` / `.nuwiki`, then uses wiki_root. - install.lua does target-triple detection (Linux gnu/musl x x86_64/ aarch64, macOS arm/x86, Windows), curl+tar download from the release URL, cp/chmod into bin/, with a cargo fallback. Honours `g:nuwiki_build_from_source`. - health.lua implements `:checkhealth nuwiki` per §7.6. VimL glue (Vim 9.1+): - autoload/nuwiki/lsp.vim follows §7.4 preference order: vim-lsp (calls `lsp#register_server`) → coc.nvim (prints the coc-settings.json snippet) → error. - scripts/download_bin.vim mirrors the Lua installer for Dein / vim-plug build hooks — same target triples, same download → fallback → cargo build chain. Help: doc/nuwiki.txt with tags for install (lazy.nvim / vim-plug / Dein), config options, health, LSP feature list, `:NuwikiInstall`. Rust workspace unchanged this phase; 172 tests still green locally. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 11 ++- SPEC.md | 6 +- autoload/nuwiki/lsp.vim | 75 +++++++++++++++++- doc/nuwiki.txt | 160 ++++++++++++++++++++++++++++++++++++++- ftdetect/nuwiki.vim | 9 ++- ftplugin/nuwiki.vim | 20 ++++- lua/nuwiki/config.lua | 18 ++++- lua/nuwiki/health.lua | 59 +++++++++++++++ lua/nuwiki/init.lua | 27 ++++++- lua/nuwiki/install.lua | 111 ++++++++++++++++++++++++++- lua/nuwiki/lsp.lua | 62 ++++++++++++++- plugin/nuwiki.vim | 27 ++++++- scripts/download_bin.vim | 90 +++++++++++++++++++++- syntax/nuwiki.vim | 91 +++++++++++++++++++++- 14 files changed, 736 insertions(+), 30 deletions(-) create mode 100644 lua/nuwiki/health.lua diff --git a/README.md b/README.md index 366c4f3..d1ed7af 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,9 @@ A Vim/Neovim plugin providing full vimwiki syntax support, implemented as a Rust-based language server (LSP). -> **Status:** Phase 8 (navigation) complete. The server now serves -> definition, references, hover, completion, `workspace/symbol`, plus a -> background-indexed workspace per P8. Only editor glue and the release -> pipeline are still pending. +> **Status:** Phase 9 (editor glue) complete. Plugin installable via +> lazy.nvim / vim-plug / Dein; setup() + LSP wiring + binary downloader + +> :checkhealth ship. Only the release pipeline remains. See [`SPEC.md`](./SPEC.md) for the full project specification. @@ -23,8 +22,8 @@ See [`SPEC.md`](./SPEC.md) for the full project specification. | 6 | LSP Foundation | ✅ done | | 7 | Semantic Tokens | ✅ done | | 8 | Navigation | ✅ done | -| 9 | Editor Glue | ⏳ next | -| 10 | CI/CD release pipeline | ⏳ | +| 9 | Editor Glue | ✅ done | +| 10 | CI/CD release pipeline | ⏳ next | ## Repository layout diff --git a/SPEC.md b/SPEC.md index ce781cc..30872a0 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,7 +1,7 @@ # nuwiki — Project Specification > Last updated: 2026-05-10 -> Status: Phase 8 (Navigation) complete — moving to Phase 9 (Editor Glue) +> Status: Phase 9 (Editor Glue) complete — moving to Phase 10 (CI/CD release pipeline) --- @@ -588,8 +588,8 @@ These decisions are required before or during the phase indicated. | ~~P2~~ | ~~**MSRV**~~ | ~~Phase 0~~ | ✅ **stable-2 (Rust 1.83)** | | | ~~P3~~ | ~~**String representation in AST**~~ | ~~Phase 1~~ | ✅ **`String` (owned)** | | | ~~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 | +| ~~P5~~ | ~~**Minimum Neovim version**~~ | ~~Phase 9~~ | ✅ **Neovim 0.11+** | Server registered via `vim.lsp.config{}` + `vim.lsp.enable`; a `vim.lsp.start` autocmd fallback is wired up but only fires when the declarative API is unavailable. | +| ~~P6~~ | ~~**Minimum Vim version**~~ | ~~Phase 9~~ | ✅ **Vim 9.1+** | autoload glue assumes 9.1 idioms; uses `vim-lsp` first, then falls back to `coc.nvim`. | | ~~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~~ | ✅ **Lazy + background with progress** | Initial scan runs as a background tokio task; nav features answer with partial data until complete; progress reported via `window/workDoneProgress`. | | P9 | **macOS runner** | Phase 10 | Stay manual · Register macOS runner diff --git a/autoload/nuwiki/lsp.vim b/autoload/nuwiki/lsp.vim index 16be926..f87ea41 100644 --- a/autoload/nuwiki/lsp.vim +++ b/autoload/nuwiki/lsp.vim @@ -1,2 +1,75 @@ " autoload/nuwiki/lsp.vim — Vim (non-Neovim) LSP client wiring. -" Implementation deferred to Phase 9 (Editor Glue). +" +" Spec §7.4 preference order: +" 1. vim-lsp (matoto/vim-lsp) +" 2. coc.nvim +" 3. Print an error if neither is available. + +let s:started = 0 + +function! nuwiki#lsp#start() abort + if s:started + return + endif + let s:started = 1 + + let l:bin = s:bin_path() + if !executable(l:bin) + echohl ErrorMsg + echom 'nuwiki: language server not found at ' . l:bin + echom 'Run :NuwikiInstall to install the binary' + echohl None + return + endif + + if exists('*lsp#register_server') + " vim-lsp + call lsp#register_server({ + \ 'name': 'nuwiki', + \ 'cmd': {server_info -> [l:bin]}, + \ 'allowlist': ['vimwiki'], + \ 'config': { 'settings': { 'nuwiki': s:settings() } }, + \ }) + return + endif + + if exists(':CocConfig') == 2 || exists('*coc#config') + " coc.nvim — coc reads its server list from coc-settings.json. + " We can't inject it dynamically without owning the file, so just point + " the user at the snippet. + echohl WarningMsg + echom 'nuwiki: coc.nvim detected. Add this to your coc-settings.json:' + echom ' "languageserver": {' + echom ' "nuwiki": {' + echom ' "command": "' . l:bin . '",' + echom ' "filetypes": ["vimwiki"]' + echom ' }' + echom ' }' + echohl None + return + endif + + echohl ErrorMsg + echom 'nuwiki: no supported LSP client found.' + echom 'Install vim-lsp (https://github.com/prabirshrestha/vim-lsp)' + echom 'or coc.nvim (https://github.com/neoclide/coc.nvim).' + echohl None +endfunction + +function! s:bin_path() abort + if exists('g:nuwiki_binary_path') && filereadable(g:nuwiki_binary_path) + return g:nuwiki_binary_path + endif + let l:plugin_dir = fnamemodify(resolve(expand(':p')), ':h:h:h') + let l:suffix = has('win32') ? '.exe' : '' + return l:plugin_dir . '/bin/nuwiki-ls' . l:suffix +endfunction + +function! s:settings() abort + return { + \ 'wiki_root': get(g:, 'nuwiki_wiki_root', '~/vimwiki'), + \ 'file_extension': get(g:, 'nuwiki_file_extension', '.wiki'), + \ 'syntax': get(g:, 'nuwiki_syntax', 'vimwiki'), + \ 'log_level': get(g:, 'nuwiki_log_level', 'warn'), + \ } +endfunction diff --git a/doc/nuwiki.txt b/doc/nuwiki.txt index d7e33b4..f202b54 100644 --- a/doc/nuwiki.txt +++ b/doc/nuwiki.txt @@ -1,5 +1,163 @@ *nuwiki.txt* LSP-backed vimwiki support for Vim and Neovim. -Documentation deferred to Phase 9 (Editor Glue). +Author: Gabriel Fróes Franco +License: Dual MIT / Apache-2.0 + +============================================================================== +CONTENTS *nuwiki-contents* + + 1. Introduction ............ |nuwiki-introduction| + 2. Requirements ............ |nuwiki-requirements| + 3. Installation ............ |nuwiki-installation| + 4. Configuration ........... |nuwiki-config| + 5. Health check ............ |nuwiki-health| + 6. LSP features ............ |nuwiki-features| + 7. Commands ................ |nuwiki-commands| + +============================================================================== +1. INTRODUCTION *nuwiki-introduction* + +nuwiki brings full vimwiki syntax support to Vim and Neovim via a Rust- +based language server (|nuwiki-ls|). It is architecturally distinct from +vimwiki itself — installable through any standard plugin manager, with the +heavy lifting done in a single binary that speaks LSP over stdio. + +============================================================================== +2. REQUIREMENTS *nuwiki-requirements* + + * Neovim 0.11+ (preferred path) + * Vim 9.1+ with one of: + - vim-lsp (prabirshrestha/vim-lsp) + - coc.nvim (neoclide/coc.nvim) + * `curl` and `tar` (only for binary download; not needed if you build + from source) + * `cargo` and a Rust toolchain (only if you opt into source builds) + +============================================================================== +3. INSTALLATION *nuwiki-installation* + +Install via your plugin manager of choice; the build hook will download +the pre-built `nuwiki-ls` binary into the plugin's `bin/` directory. + +lazy.nvim: > + + { + "gffranco/nuwiki", + build = "lua require('nuwiki').install()", + ft = { "vimwiki" }, + opts = {}, + } + + + + Plug 'gffranco/nuwiki', { 'do': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' } + + + + call dein#add('gffranco/nuwiki', { + \ 'build': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"', + \ }) + + + + let g:nuwiki_build_from_source = 1 + + + + vim.g.nuwiki_build_from_source = 1 + require('nuwiki').install() + +<============================================================================== +4. CONFIGURATION *nuwiki-config* + +Neovim (`require('nuwiki').setup{}`): > + + require('nuwiki').setup({ + wiki_root = '~/vimwiki', + file_extension = '.wiki', + syntax = 'vimwiki', + log_level = 'warn', + }) + + + + let g:nuwiki_wiki_root = '~/vimwiki' + let g:nuwiki_file_extension = '.wiki' + let g:nuwiki_syntax = 'vimwiki' + let g:nuwiki_log_level = 'warn' + + + + :checkhealth nuwiki + + 0 then + vim.health.ok('LSP client attached (' .. #clients .. ' instance(s))') + else + vim.health.info('LSP client not yet attached — open a .wiki buffer') + end +end + +return M diff --git a/lua/nuwiki/init.lua b/lua/nuwiki/init.lua index d2d2a38..556e2b8 100644 --- a/lua/nuwiki/init.lua +++ b/lua/nuwiki/init.lua @@ -1,6 +1,29 @@ --- lua/nuwiki/init.lua — Neovim entry point. Exposes setup() and install(). --- Implementation deferred to Phase 9 (Editor Glue). +-- lua/nuwiki/init.lua — Neovim entry point. +-- +-- Public surface: +-- require('nuwiki').setup(opts) — apply user config + register LSP +-- require('nuwiki').install() — install / build the language server +-- require('nuwiki').health() — :checkhealth nuwiki target +-- +-- Per SPEC §6.2/§6.3 this layer is wiring only. Everything substantive +-- happens in the Rust language server. local M = {} +local config = require('nuwiki.config') +local lsp = require('nuwiki.lsp') + +function M.setup(opts) + config.apply(opts or {}) + lsp.register() +end + +function M.install() + return require('nuwiki.install').install() +end + +function M.health() + return require('nuwiki.health').check() +end + return M diff --git a/lua/nuwiki/install.lua b/lua/nuwiki/install.lua index 539907d..660ce04 100644 --- a/lua/nuwiki/install.lua +++ b/lua/nuwiki/install.lua @@ -1,6 +1,113 @@ --- lua/nuwiki/install.lua — binary download / build-from-source. --- Implementation deferred to Phase 9 (Editor Glue). +-- lua/nuwiki/install.lua — install / build the `nuwiki-ls` binary. +-- +-- Strategy (matches SPEC §7.2): +-- 1. Look for `bin/nuwiki-ls[.exe]` next to the plugin. +-- 2. If missing or `g:nuwiki_build_from_source` is set, build with cargo. +-- 3. Otherwise try to download a release asset for the current target. +-- On failure, fall back to cargo. local M = {} +local function plugin_root() + local source = debug.getinfo(1, 'S').source:sub(2) + return vim.fn.fnamemodify(source, ':h:h:h') +end + +local function exe_suffix() + return vim.fn.has('win32') == 1 and '.exe' or '' +end + +function M.expected_path() + return plugin_root() .. '/bin/nuwiki-ls' .. exe_suffix() +end + +function M.is_installed() + return vim.fn.executable(M.expected_path()) == 1 +end + +local function target_triple() + if vim.fn.has('win32') == 1 then + return 'x86_64-pc-windows-msvc' + elseif vim.fn.has('mac') == 1 then + if vim.fn.has('arm64') == 1 or jit and jit.arch == 'arm64' then + return 'aarch64-apple-darwin' + end + return 'x86_64-apple-darwin' + elseif vim.fn.has('linux') == 1 then + local uname = vim.uv and vim.uv.os_uname() or vim.loop.os_uname() + local musl = (vim.fn.system('ldd --version 2>&1'):find('musl') ~= nil) + if uname.machine == 'aarch64' or uname.machine == 'arm64' then + return musl and 'aarch64-unknown-linux-musl' or 'aarch64-unknown-linux-gnu' + end + return musl and 'x86_64-unknown-linux-musl' or 'x86_64-unknown-linux-gnu' + end + return nil +end + +local function build_from_source(dest) + if vim.fn.executable('cargo') ~= 1 then + vim.notify('nuwiki: cargo not found, cannot build from source', + vim.log.levels.ERROR) + return false + end + local root = plugin_root() + vim.notify('nuwiki: building from source (cargo build --release) …', vim.log.levels.INFO) + local _ = vim.fn.system({ + 'cargo', 'build', '--release', '-p', 'nuwiki-ls', + '--manifest-path', root .. '/Cargo.toml', + }) + if vim.v.shell_error ~= 0 then + vim.notify('nuwiki: cargo build failed', vim.log.levels.ERROR) + return false + end + local built = root .. '/target/release/nuwiki-ls' .. exe_suffix() + vim.fn.system({ 'cp', built, dest }) + vim.fn.system({ 'chmod', '+x', dest }) + return vim.fn.executable(dest) == 1 +end + +local function download_release(dest) + local target = target_triple() + if not target then + return false + end + if vim.fn.executable('curl') ~= 1 or vim.fn.executable('tar') ~= 1 then + return false + end + local url = string.format( + 'https://code.gfran.co/gffranco/nuwiki/releases/latest/download/nuwiki-ls-%s.tar.gz', + target + ) + local archive = vim.fn.tempname() .. '.tar.gz' + vim.fn.system({ 'curl', '-fsSL', '-o', archive, url }) + if vim.v.shell_error ~= 0 then + return false + end + local extract = vim.fn.tempname() + vim.fn.mkdir(extract, 'p') + vim.fn.system({ 'tar', '-xzf', archive, '-C', extract }) + if vim.v.shell_error ~= 0 then + return false + end + vim.fn.system({ 'cp', extract .. '/nuwiki-ls' .. exe_suffix(), dest }) + vim.fn.system({ 'chmod', '+x', dest }) + return vim.fn.executable(dest) == 1 +end + +function M.install() + local dest = M.expected_path() + vim.fn.mkdir(vim.fn.fnamemodify(dest, ':h'), 'p') + + if vim.g.nuwiki_build_from_source == 1 then + return build_from_source(dest) + end + if download_release(dest) then + vim.notify('nuwiki: installed ' .. dest, vim.log.levels.INFO) + return true + end + vim.notify('nuwiki: download failed, falling back to source build', + vim.log.levels.WARN) + return build_from_source(dest) +end + return M diff --git a/lua/nuwiki/lsp.lua b/lua/nuwiki/lsp.lua index 94ae4de..53b6750 100644 --- a/lua/nuwiki/lsp.lua +++ b/lua/nuwiki/lsp.lua @@ -1,6 +1,64 @@ --- lua/nuwiki/lsp.lua — vim.lsp.start wiring for the nuwiki-ls binary. --- Implementation deferred to Phase 9 (Editor Glue). +-- lua/nuwiki/lsp.lua — registration of the nuwiki LSP server in Neovim. +-- +-- Prefers Neovim 0.11+'s declarative `vim.lsp.config{}` + `vim.lsp.enable`. +-- Falls back to a FileType autocmd that calls `vim.lsp.start` for older +-- versions (we still officially support only 0.11+; the fallback exists so +-- the plugin degrades gracefully rather than erroring out). local M = {} +local config = require('nuwiki.config') +local install = require('nuwiki.install') + +local function command() + return { install.expected_path() } +end + +local function server_settings() + return { + nuwiki = config.options, + } +end + +local function root_dir_for(buf_file) + -- Walk up from the buffer until we hit a workspace marker, or fall back + -- to the configured wiki_root. + local found = vim.fs.find( + { '.git', '.nuwiki' }, + { upward = true, path = vim.fs.dirname(buf_file) } + )[1] + if found then + return vim.fs.dirname(found) + end + return vim.fn.expand(config.options.wiki_root) +end + +function M.register() + if vim.lsp.config and vim.lsp.enable then + -- Neovim 0.11+: declarative server registration. + vim.lsp.config('nuwiki', { + cmd = command(), + filetypes = { 'vimwiki' }, + root_markers = { '.git', '.nuwiki' }, + settings = server_settings(), + }) + vim.lsp.enable('nuwiki') + return + end + + -- Pre-0.11 fallback path. + vim.api.nvim_create_autocmd('FileType', { + pattern = 'vimwiki', + desc = 'start nuwiki language server', + callback = function(ev) + vim.lsp.start({ + name = 'nuwiki', + cmd = command(), + root_dir = root_dir_for(ev.file), + settings = server_settings(), + }) + end, + }) +end + return M diff --git a/plugin/nuwiki.vim b/plugin/nuwiki.vim index abd7405..02298db 100644 --- a/plugin/nuwiki.vim +++ b/plugin/nuwiki.vim @@ -1,8 +1,29 @@ -" plugin/nuwiki.vim — universal entry point for Vim and Neovim. -" Loaded once per session by all plugin managers. -" Implementation deferred to Phase 9 (Editor Glue). +" plugin/nuwiki.vim — universal Vim/Neovim entry point. +" +" Neovim 0.11+ users are expected to call `require('nuwiki').setup({})` from +" their config (lazy.nvim does this automatically through `opts`). On Vim +" we start the LSP client when a vimwiki buffer is loaded. +" +" Per SPEC §6.2 / §6.3 this file contains wiring only — all logic lives in +" the Rust language server. if exists('g:loaded_nuwiki') finish endif let g:loaded_nuwiki = 1 + +if has('nvim') + " Neovim path is initialised by `require('nuwiki').setup()`. Nothing else + " to do at vimscript boot — wait until the user runs setup. + finish +endif + +" Vim path: start the LSP client the first time a vimwiki buffer is opened. +augroup nuwiki_vim_lsp_start + autocmd! + autocmd FileType vimwiki call nuwiki#lsp#start() +augroup END + +" :NuwikiInstall — convenience wrapper around scripts/download_bin.vim, so +" users don't have to invoke a plugin-manager build hook by hand. +command! -nargs=0 NuwikiInstall execute 'source' fnamemodify(resolve(expand(':p')), ':h:h') . '/scripts/download_bin.vim' diff --git a/scripts/download_bin.vim b/scripts/download_bin.vim index 02b534b..96475f0 100644 --- a/scripts/download_bin.vim +++ b/scripts/download_bin.vim @@ -1,2 +1,88 @@ -" scripts/download_bin.vim — VimL binary download for Dein/vim-plug build hooks. -" Implementation deferred to Phase 9 (Editor Glue). +" scripts/download_bin.vim — install `nuwiki-ls` from a release tarball, +" falling back to `cargo build --release` on failure. +" +" Invoked by Dein / vim-plug build hooks: +" vim -e -s -c "source scripts/download_bin.vim" -c "q" + +let s:plugin_dir = fnamemodify(resolve(expand(':p')), ':h:h') +let s:bin_dir = s:plugin_dir . '/bin' +let s:suffix = has('win32') ? '.exe' : '' +let s:bin = s:bin_dir . '/nuwiki-ls' . s:suffix + +call mkdir(s:bin_dir, 'p') + +function! s:target() abort + if has('win32') + return 'x86_64-pc-windows-msvc' + elseif has('mac') + if has('arm64') + return 'aarch64-apple-darwin' + endif + return 'x86_64-apple-darwin' + elseif has('linux') + let l:ldd = system('ldd --version 2>&1') + let l:musl = stridx(l:ldd, 'musl') >= 0 + let l:uname_m = trim(system('uname -m')) + if l:uname_m ==# 'aarch64' || l:uname_m ==# 'arm64' + return l:musl ? 'aarch64-unknown-linux-musl' : 'aarch64-unknown-linux-gnu' + endif + return l:musl ? 'x86_64-unknown-linux-musl' : 'x86_64-unknown-linux-gnu' + endif + return '' +endfunction + +function! s:build_from_source() abort + if !executable('cargo') + echohl ErrorMsg | echom 'nuwiki: cargo not found, cannot build from source' | echohl None + return 0 + endif + echom 'nuwiki: cargo build --release …' + call system('cargo build --release -p nuwiki-ls --manifest-path ' . shellescape(s:plugin_dir . '/Cargo.toml')) + if v:shell_error != 0 + echohl ErrorMsg | echom 'nuwiki: cargo build failed' | echohl None + return 0 + endif + let l:built = s:plugin_dir . '/target/release/nuwiki-ls' . s:suffix + call system('cp ' . shellescape(l:built) . ' ' . shellescape(s:bin)) + call system('chmod +x ' . shellescape(s:bin)) + return executable(s:bin) +endfunction + +function! s:download_release() abort + let l:target = s:target() + if empty(l:target) + return 0 + endif + if !executable('curl') || !executable('tar') + return 0 + endif + let l:url = 'https://code.gfran.co/gffranco/nuwiki/releases/latest/download/nuwiki-ls-' + \ . l:target . '.tar.gz' + let l:archive = tempname() . '.tar.gz' + call system('curl -fsSL -o ' . shellescape(l:archive) . ' ' . shellescape(l:url)) + if v:shell_error != 0 + return 0 + endif + let l:extract = tempname() + call mkdir(l:extract, 'p') + call system('tar -xzf ' . shellescape(l:archive) . ' -C ' . shellescape(l:extract)) + if v:shell_error != 0 + return 0 + endif + call system('cp ' . shellescape(l:extract . '/nuwiki-ls' . s:suffix) . ' ' . shellescape(s:bin)) + call system('chmod +x ' . shellescape(s:bin)) + return executable(s:bin) +endfunction + +if get(g:, 'nuwiki_build_from_source', 0) + if !s:build_from_source() + echohl ErrorMsg | echom 'nuwiki: source build failed' | echohl None + endif +elseif s:download_release() + echom 'nuwiki: installed ' . s:bin +else + echohl WarningMsg | echom 'nuwiki: download failed, falling back to source build' | echohl None + if !s:build_from_source() + echohl ErrorMsg | echom 'nuwiki: install failed' | echohl None + endif +endif diff --git a/syntax/nuwiki.vim b/syntax/nuwiki.vim index 00923aa..b056cd4 100644 --- a/syntax/nuwiki.vim +++ b/syntax/nuwiki.vim @@ -1,2 +1,89 @@ -" syntax/nuwiki.vim — static fallback syntax highlighting (no LSP required). -" Implementation deferred to Phase 9 (Editor Glue). +" syntax/nuwiki.vim — default highlight groups for nuwiki. +" +" Two responsibilities: +" 1. Default `@vimwiki*` highlight groups for the LSP semantic tokens +" (SPEC §11 P7). Loaded eagerly so the very first hover/highlight +" cycle after server startup already has colours. +" 2. A small regex-based fallback so static highlighting works on +" buffers where the LSP isn't running yet. + +if exists('b:current_syntax') + finish +endif + +" ===== Semantic-token defaults (Neovim's TS-style @group names) ===== + +" Headings — level modifiers map to progressively softer accents. +highlight default link @vimwikiHeading Title +highlight default link @vimwikiHeading.level1 Title +highlight default link @vimwikiHeading.level2 Function +highlight default link @vimwikiHeading.level3 Identifier +highlight default link @vimwikiHeading.level4 Type +highlight default link @vimwikiHeading.level5 Constant +highlight default link @vimwikiHeading.level6 PreProc +highlight default link @vimwikiHeading.centered Title + +" Inline text styling. +highlight default link @vimwikiBold markdownBold +highlight default link @vimwikiItalic markdownItalic +highlight default link @vimwikiBoldItalic markdownBoldItalic +highlight default link @vimwikiStrikethrough Comment +highlight default link @vimwikiSuperscript Number +highlight default link @vimwikiSubscript Number + +" Code + math. +highlight default link @vimwikiCode String +highlight default link @vimwikiCodeBlock String +highlight default link @vimwikiMath Number +highlight default link @vimwikiMathBlock Number + +" Links + transclusions. +highlight default link @vimwikiWikilink Underlined +highlight default link @vimwikiExternalLink Underlined +highlight default link @vimwikiUrl Underlined +highlight default link @vimwikiTransclusion Identifier + +" Misc. +highlight default link @vimwikiKeyword Todo +highlight default link @vimwikiColor Constant +highlight default link @vimwikiComment Comment +highlight default link @vimwikiDefinitionTerm Statement + +" ===== Static fallback (no LSP required) ===== + +syntax case match + +syntax match nuwikiHeading /^\s*=\{1,6}\s.\{-}\s=\{1,6}\s*$/ +syntax match nuwikiHorizontal /^-\{4,}\s*$/ +syntax match nuwikiCommentLine /^%%.*$/ +syntax region nuwikiCommentBlock start=/^%%+/ end=/+%%/ keepend +syntax region nuwikiPreformatted start=/^{{{/ end=/^}}}/ keepend +syntax region nuwikiMathBlock start=/^{{\$/ end=/^}}\$/ keepend +syntax match nuwikiPlaceholder /^%\(title\|nohtml\|template\|date\)\>.*$/ + +syntax match nuwikiBold /\*[^*]\+\*/ +syntax match nuwikiItalic /\<_[^_]\+_\>/ +syntax match nuwikiCode /`[^`]\+`/ +syntax match nuwikiMathInline /\$[^$]\+\$/ +syntax match nuwikiKeyword /\<\(TODO\|DONE\|STARTED\|FIXME\|FIXED\|XXX\)\>/ +syntax region nuwikiWikilink start=/\[\[/ end=/\]\]/ keepend +syntax region nuwikiTransclusion start=/{{[^{]/ end=/}}/ keepend +syntax match nuwikiUrl /\<\(https\?\|ftp\|mailto\|file\):\/\?\/\?\S\+/ + +highlight default link nuwikiHeading Title +highlight default link nuwikiHorizontal Comment +highlight default link nuwikiCommentLine Comment +highlight default link nuwikiCommentBlock Comment +highlight default link nuwikiPreformatted String +highlight default link nuwikiMathBlock Number +highlight default link nuwikiPlaceholder PreProc +highlight default link nuwikiBold Bold +highlight default link nuwikiItalic Italic +highlight default link nuwikiCode String +highlight default link nuwikiMathInline Number +highlight default link nuwikiKeyword Todo +highlight default link nuwikiWikilink Underlined +highlight default link nuwikiTransclusion Identifier +highlight default link nuwikiUrl Underlined + +let b:current_syntax = 'vimwiki'