phase 9: editor glue — plugin, LSP wiring, install, health, docs
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+74
-1
@@ -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('<sfile>: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
|
||||
|
||||
+159
-1
@@ -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 = {},
|
||||
}
|
||||
|
||||
<vim-plug: >
|
||||
|
||||
Plug 'gffranco/nuwiki', { 'do': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' }
|
||||
|
||||
<dein.vim: >
|
||||
|
||||
call dein#add('gffranco/nuwiki', {
|
||||
\ 'build': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"',
|
||||
\ })
|
||||
|
||||
<To force a build from source (no download), set: >
|
||||
|
||||
let g:nuwiki_build_from_source = 1
|
||||
|
||||
<in Vim before re-running the build hook, or in Neovim: >
|
||||
|
||||
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',
|
||||
})
|
||||
|
||||
<Vim sets the equivalent globals before plugin load: >
|
||||
|
||||
let g:nuwiki_wiki_root = '~/vimwiki'
|
||||
let g:nuwiki_file_extension = '.wiki'
|
||||
let g:nuwiki_syntax = 'vimwiki'
|
||||
let g:nuwiki_log_level = 'warn'
|
||||
|
||||
<Options:
|
||||
|
||||
*g:nuwiki_wiki_root*
|
||||
`wiki_root` ~/vimwiki
|
||||
Root directory of the wiki. Used as a fallback `root_dir` when no
|
||||
`.git` / `.nuwiki` marker is found near the buffer.
|
||||
|
||||
*g:nuwiki_file_extension*
|
||||
`file_extension` .wiki
|
||||
File extension associated with the wiki filetype. Note that
|
||||
`ftdetect/nuwiki.vim` hardcodes `.wiki`; users with a custom
|
||||
extension should add their own ftdetect autocmd.
|
||||
|
||||
*g:nuwiki_syntax*
|
||||
`syntax` vimwiki
|
||||
Future-proofing for additional syntaxes (e.g. `markdown`). Phase 9
|
||||
only ships the vimwiki plugin.
|
||||
|
||||
*g:nuwiki_log_level*
|
||||
`log_level` warn
|
||||
`error` | `warn` | `info` | `debug`. Forwarded to the language server.
|
||||
|
||||
*g:nuwiki_build_from_source*
|
||||
`g:nuwiki_build_from_source` 0
|
||||
When 1, the install step always builds from source (cargo) and skips
|
||||
the release-asset download path.
|
||||
|
||||
*g:nuwiki_binary_path*
|
||||
`g:nuwiki_binary_path`
|
||||
Override the auto-discovered server path. If set and the file is
|
||||
readable, `autoload/nuwiki/lsp.vim` uses it instead of the
|
||||
`{plugin}/bin/nuwiki-ls` default.
|
||||
|
||||
==============================================================================
|
||||
5. HEALTH CHECK *nuwiki-health*
|
||||
|
||||
Neovim users can run: >
|
||||
|
||||
:checkhealth nuwiki
|
||||
|
||||
<This reports whether the binary exists, responds to `--version`, whether
|
||||
`wiki_root` exists, whether `.wiki` filetype detection is wired up, and
|
||||
whether an LSP client is currently attached.
|
||||
|
||||
==============================================================================
|
||||
6. LSP FEATURES *nuwiki-features*
|
||||
|
||||
The language server provides every method listed in SPEC §6.9:
|
||||
|
||||
* Syntax highlighting via semantic tokens
|
||||
`textDocument/semanticTokens/full` + `/range`
|
||||
* Document outline
|
||||
`textDocument/documentSymbol`
|
||||
* Diagnostics (parse errors)
|
||||
`textDocument/publishDiagnostics`
|
||||
* Go-to-definition (follow wikilinks)
|
||||
`textDocument/definition`
|
||||
* Backlinks
|
||||
`textDocument/references`
|
||||
* Hover preview (page title + outline)
|
||||
`textDocument/hover`
|
||||
* Completion on `[[`
|
||||
`textDocument/completion`
|
||||
* Workspace symbol search
|
||||
`workspace/symbol`
|
||||
|
||||
==============================================================================
|
||||
7. COMMANDS *nuwiki-commands*
|
||||
|
||||
*:NuwikiInstall*
|
||||
:NuwikiInstall
|
||||
Install (or re-install) the `nuwiki-ls` binary into the plugin's
|
||||
`bin/` directory. Equivalent to running the build hook by hand.
|
||||
|
||||
vim:tw=78:ts=8:ft=help:norl:
|
||||
|
||||
+7
-2
@@ -1,2 +1,7 @@
|
||||
" ftdetect/nuwiki.vim — filetype detection for .wiki files.
|
||||
" Implementation deferred to Phase 9 (Editor Glue).
|
||||
" ftdetect/nuwiki.vim — associate *.wiki files with the `vimwiki` filetype.
|
||||
"
|
||||
" Users who configure a custom `file_extension` in setup() should add their
|
||||
" own `:autocmd BufRead,BufNewFile *.myext setfiletype vimwiki` mapping;
|
||||
" we hardcode `.wiki` since ftdetect runs before any user setup() call.
|
||||
|
||||
autocmd! BufRead,BufNewFile *.wiki setfiletype vimwiki
|
||||
|
||||
+18
-2
@@ -1,2 +1,18 @@
|
||||
" ftplugin/nuwiki.vim — per-buffer filetype settings for .wiki buffers.
|
||||
" Implementation deferred to Phase 9 (Editor Glue).
|
||||
" ftplugin/nuwiki.vim — per-buffer settings for `.wiki` buffers.
|
||||
"
|
||||
" Keeps this minimal: only the options that affect editing ergonomics for
|
||||
" markup files. Anything richer (folding, navigation keymaps, …) ships
|
||||
" downstream once users opt in.
|
||||
|
||||
if exists('b:did_ftplugin')
|
||||
finish
|
||||
endif
|
||||
let b:did_ftplugin = 1
|
||||
|
||||
setlocal commentstring=%%\ %s
|
||||
setlocal comments=:%%
|
||||
setlocal formatoptions+=ron
|
||||
setlocal suffixesadd=.wiki
|
||||
setlocal iskeyword+=-
|
||||
|
||||
let b:undo_ftplugin = 'setlocal commentstring< comments< formatoptions< suffixesadd< iskeyword<'
|
||||
|
||||
+16
-2
@@ -1,6 +1,20 @@
|
||||
-- lua/nuwiki/config.lua — user configuration schema and defaults.
|
||||
-- Implementation deferred to Phase 9 (Editor Glue).
|
||||
-- lua/nuwiki/config.lua — defaults and user-config merge.
|
||||
--
|
||||
-- Schema mirrors SPEC §7.5. Keep this aligned with `doc/nuwiki.txt`.
|
||||
|
||||
local M = {}
|
||||
|
||||
M.defaults = {
|
||||
wiki_root = '~/vimwiki',
|
||||
file_extension = '.wiki',
|
||||
syntax = 'vimwiki',
|
||||
log_level = 'warn',
|
||||
}
|
||||
|
||||
M.options = vim.deepcopy(M.defaults)
|
||||
|
||||
function M.apply(user)
|
||||
M.options = vim.tbl_deep_extend('force', M.defaults, user or {})
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
-- lua/nuwiki/health.lua — `:checkhealth nuwiki` target.
|
||||
--
|
||||
-- Checks per SPEC §7.6: binary exists, is executable, LSP client running,
|
||||
-- filetype detection works for `.wiki`.
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.check()
|
||||
local install = require('nuwiki.install')
|
||||
local config = require('nuwiki.config')
|
||||
|
||||
vim.health.start('nuwiki')
|
||||
|
||||
local bin = install.expected_path()
|
||||
if install.is_installed() then
|
||||
vim.health.ok('binary present at ' .. bin)
|
||||
|
||||
local out = vim.fn.system({ bin, '--version' })
|
||||
if vim.v.shell_error == 0 then
|
||||
vim.health.ok('binary --version: ' .. (out:gsub('\n', ' ')):gsub('^%s+', ''))
|
||||
else
|
||||
vim.health.warn(
|
||||
'binary did not respond to --version',
|
||||
{ 'Re-install with :lua require("nuwiki").install()' }
|
||||
)
|
||||
end
|
||||
else
|
||||
vim.health.error(
|
||||
'binary not found at ' .. bin,
|
||||
{ 'Run :lua require("nuwiki").install() to install' }
|
||||
)
|
||||
end
|
||||
|
||||
local root = vim.fn.expand(config.options.wiki_root or '')
|
||||
if root ~= '' then
|
||||
if vim.fn.isdirectory(root) == 1 then
|
||||
vim.health.ok('wiki_root exists: ' .. root)
|
||||
else
|
||||
vim.health.warn('wiki_root does not exist: ' .. root)
|
||||
end
|
||||
end
|
||||
|
||||
if vim.fn.exists('#filetypedetect#BufRead#*.wiki') ~= 0
|
||||
or vim.filetype.match({ filename = 'test.wiki' }) == 'vimwiki' then
|
||||
vim.health.ok('filetype detection for *.wiki is wired up')
|
||||
else
|
||||
vim.health.warn('filetype detection for *.wiki not active')
|
||||
end
|
||||
|
||||
local clients = vim.lsp.get_clients and vim.lsp.get_clients({ name = 'nuwiki' })
|
||||
or vim.lsp.get_active_clients({ name = 'nuwiki' })
|
||||
if clients and #clients > 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
|
||||
+25
-2
@@ -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
|
||||
|
||||
+109
-2
@@ -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
|
||||
|
||||
+60
-2
@@ -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
|
||||
|
||||
+24
-3
@@ -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('<sfile>:p')), ':h:h') . '/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('<sfile>: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
|
||||
|
||||
+89
-2
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user