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:
+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
|
||||
|
||||
Reference in New Issue
Block a user