diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 059249d..0c79516 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -124,3 +124,5 @@ jobs: run: ./development/tests/test-vimwiki-compat-vim.sh - name: run Neovim global-shorthand harness run: ./development/tests/test-global-shorthand.sh + - name: run Vim coc-registration harness + run: ./development/tests/test-coc-register-vim.sh diff --git a/README.md b/README.md index 2ce1536..7a62bda 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ Neovim) instead of relying on a plugin-manager build hook. Older releases fall back to a `vim.lsp.start` autocmd but aren't officially supported. - **Vim 9+** — needs a third-party LSP client: - [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) (recommended, - auto-registered) or [`coc.nvim`](https://github.com/neoclide/coc.nvim) - (one-time `coc-settings.json` entry — see [Vim LSP client + [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) or + [`coc.nvim`](https://github.com/neoclide/coc.nvim) — both + auto-registered, no manual config (see [Vim LSP client setup](#vim-lsp-client-setup)). - **Rust toolchain (1.83+ stable)** — only needed when building the binary from source; pre-built release downloads skip this. @@ -145,10 +145,18 @@ auto-enables it. Your wiki settings (`g:nuwiki_wiki_root`, `g:nuwiki_wikis`, …) are forwarded as the server's initialization options. -**coc.nvim** — [`coc.nvim`](https://github.com/neoclide/coc.nvim) reads -its server list from `coc-settings.json`, which the plugin can't edit -on your behalf, so add the entry once yourself (open it with -`:CocConfig`): +**coc.nvim** — also zero configuration. nuwiki registers itself with +[`coc.nvim`](https://github.com/neoclide/coc.nvim) programmatically +(`coc#config('languageserver.nuwiki', …)`) when the first `.wiki` buffer +loads, forwarding the same wiki settings (`g:nuwiki_wiki_root`, +`g:nuwiki_wikis`, your `g:vimwiki_*` config, the global shorthand, …) as +the server's initialization options. **No `coc-settings.json` +`languageserver` entry needed** — change your wiki config and reload; the +new settings are picked up. + +To manage the entry yourself instead, set `let g:nuwiki_no_coc_register = 1` +and add it to `coc-settings.json` manually (nuwiki then just prints the +snippet on first load): ```json { diff --git a/autoload/nuwiki/lsp.vim b/autoload/nuwiki/lsp.vim index e3d7434..d673905 100644 --- a/autoload/nuwiki/lsp.vim +++ b/autoload/nuwiki/lsp.vim @@ -136,22 +136,7 @@ function! nuwiki#lsp#start() abort 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. `initializationOptions` carries wiki_root etc. - " to the server exactly like the vim-lsp registration above — without it - " the server resolves links against its default root, so existing pages - " look broken and follow-to-create lands in the wrong directory. - 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 ' "initializationOptions": ' . json_encode(s:settings()) - echom ' }' - echom ' }' - echohl None + call s:register_with_coc(l:bin) return endif @@ -162,6 +147,66 @@ function! nuwiki#lsp#start() abort echohl None endfunction +" ===== coc.nvim integration ===== +" +" Register the nuwiki language server with coc *programmatically* via +" coc#config, so users don't have to hand-maintain a coc-settings.json entry +" per wiki. The config (wiki roots, per-wiki keys, the vimwiki/nuwiki global +" shorthand) comes from s:settings() — the same payload the vim-lsp path sends. +" coc reacts to the `languageserver` config change, registers the server, and +" starts it for the open vimwiki buffer. +" +" Opt out with `let g:nuwiki_no_coc_register = 1` to manage the entry in +" coc-settings.json yourself (we then just print the snippet). +function! s:register_with_coc(bin) abort + if get(g:, 'nuwiki_no_coc_register', 0) + call s:print_coc_snippet(a:bin) + return + endif + let s:coc_bin = a:bin + if get(g:, 'coc_service_initialized', 0) + call s:coc_register() + else + " coc hasn't finished starting its node service yet — defer until it has, + " otherwise the updateConfig notification is sent to a service that isn't + " listening. + augroup nuwiki_coc_register + autocmd! + autocmd User CocNvimInit ++once call s:coc_register() + augroup END + endif +endfunction + +function! s:coc_register() abort + let l:cfg = s:settings() + " Call coc#config directly — `exists('*coc#config')` can't be trusted because + " it doesn't trigger autoloading, so we let the call autoload coc.vim and + " fall back to the manual snippet only if coc genuinely isn't there. + try + call coc#config('languageserver.nuwiki', { + \ 'command': s:coc_bin, + \ 'filetypes': ['vimwiki'], + \ 'initializationOptions': l:cfg, + \ 'settings': { 'nuwiki': l:cfg }, + \ }) + catch /^Vim\%((\a\+)\)\=:E11[78]/ + call s:print_coc_snippet(s:coc_bin) + endtry +endfunction + +function! s:print_coc_snippet(bin) abort + echohl WarningMsg + echom 'nuwiki: coc.nvim detected. Add this to your coc-settings.json:' + echom ' "languageserver": {' + echom ' "nuwiki": {' + echom ' "command": "' . a:bin . '",' + echom ' "filetypes": ["vimwiki"],' + echom ' "initializationOptions": ' . json_encode(s:settings()) + echom ' }' + echom ' }' + echohl None +endfunction + function! s:bin_path() abort if exists('g:nuwiki_binary_path') && filereadable(g:nuwiki_binary_path) return g:nuwiki_binary_path diff --git a/development/start-vim-coc.sh b/development/start-vim-coc.sh index cae8c6f..2987084 100755 --- a/development/start-vim-coc.sh +++ b/development/start-vim-coc.sh @@ -72,46 +72,14 @@ ensure_coc() { write_coc_settings() { mkdir -p "$DEV_DIR" - # Mirror what the vim-lsp client sends (autoload/nuwiki/lsp.vim s:settings()): - # both `initializationOptions` (sent on initialize) and `settings.nuwiki` - # (served via workspace/configuration). Without these the server falls back - # to its own default wiki_root (~/vimwiki) instead of $WIKI_DIR, so it can't - # resolve links (e.g. Notes shows as broken) or create-on-follow correctly. - # toc_header_level / html_header_numbering are set to non-defaults so the - # dev wiki exercises them: :NuwikiTOC writes `== Contents ==` (level 2) and - # HTML export numbers headings. coc reads this file verbatim, so these are - # the values the server actually receives. + # No `languageserver.nuwiki` block here on purpose: this dogfoods nuwiki's + # *programmatic* coc registration (autoload/nuwiki/lsp.vim → coc#config). The + # plugin registers the server itself from g:nuwiki_* (set in the vimrc), + # exactly the "no hand-written coc-settings.json" flow real users get. Only + # the jump-command preference (the -opens-a-tab reproducer) lives here. cat > "$COC_SETTINGS" </dev/null 2>&1; then + log 'vim not installed — skipping' + exit 0 +fi + +TMP="$(mktemp -d -t nuwiki-coc-test-XXXXXX)" +trap 'rm -rf "$TMP"' EXIT + +# Stub coc#config as a real autoload function so exists('*coc#config') resolves +# it. It records the last call into g:_recorded for the test to assert. +mkdir -p "$TMP/stub/autoload" +cat > "$TMP/stub/autoload/coc.vim" <<'EOF' +function! coc#config(section, value) abort + let g:_recorded = {'section': a:section, 'value': deepcopy(a:value)} +endfunction +EOF + +OUT="$TMP/coc.out" +VIMRC="$TMP/vimrc" +cat > "$VIMRC" <"$TMP/vim.log" 2>&1 || true + +if [[ ! -f "$OUT" ]]; then + echo 'coc-registration harness produced no output' >&2 + echo '--- vim log ---' >&2 + cat "$TMP/vim.log" >&2 || true + exit 1 +fi + +cat "$OUT" +PASSED="$(grep -c '^PASS ' "$OUT" || true)" +FAILED="$(grep -c '^FAIL ' "$OUT" || true)" +echo "SUMMARY: ${PASSED} passed, ${FAILED} failed" + +if [[ "$FAILED" -ne 0 ]]; then + exit 1 +fi +log 'coc registration OK ✓' +exit 0 diff --git a/development/tests/test-coc-register-vim.vim b/development/tests/test-coc-register-vim.vim new file mode 100644 index 0000000..4ec9046 --- /dev/null +++ b/development/tests/test-coc-register-vim.vim @@ -0,0 +1,42 @@ +" development/tests/test-coc-register-vim.vim — programmatic coc.nvim +" registration. Relies on a stub autoload/coc.vim (created by the wrapper) that +" records coc#config calls into g:_recorded. Drives nuwiki#lsp#start()'s coc +" branch and asserts the exact languageserver config nuwiki injects (command, +" filetypes, and the s:settings() payload with the global shorthand folded in). +" Writes PASS/FAIL to $NUWIKI_COC_OUT. + +let s:out = [] +function! s:check(desc, cond) abort + call add(s:out, (a:cond ? 'PASS ' : 'FAIL ') . a:desc) +endfunction + +" Pretend coc's node service is already up so registration runs synchronously. +let g:coc_service_initialized = 1 +let g:_recorded = {} + +" Make sure the vim-lsp branch is NOT taken. +if exists('g:lsp_loaded') | unlet g:lsp_loaded | endif + +" A readable, executable 'binary' so nuwiki#lsp#start() gets past its check. +let g:nuwiki_binary_path = executable('true') ? exepath('true') : '/usr/bin/true' + +" Config: single-wiki shorthand + a global display setting that must fold into +" the wiki the server sees (the global shorthand). +let g:nuwiki_wiki_root = '/tmp/realwiki' +let g:nuwiki_toc_header_level = 2 + +call nuwiki#lsp#start() + +let s:r = get(g:, '_recorded', {}) +call s:check('coc#config was called', !empty(s:r)) +call s:check('section is languageserver.nuwiki', get(s:r, 'section', '') ==# 'languageserver.nuwiki') +let s:v = get(s:r, 'value', {}) +call s:check('command is the binary', get(s:v, 'command', '') ==# g:nuwiki_binary_path) +call s:check('filetypes = [vimwiki]', get(s:v, 'filetypes', []) == ['vimwiki']) +let s:io = get(s:v, 'initializationOptions', {}) +call s:check('initOpts has wiki_root', get(s:io, 'wiki_root', '') ==# '/tmp/realwiki') +call s:check('global toc_header_level folded into payload', get(s:io, 'toc_header_level', 0) == 2) +call s:check('settings.nuwiki mirrors initOpts', get(get(s:v, 'settings', {}), 'nuwiki', {}) ==# s:io) + +call writefile(s:out, $NUWIKI_COC_OUT) +qa!