feat(vim): :Vimwiki* / :Nuwiki* commands for plain Vim via vim-lsp
The Phase 19 ftplugin only registered the command surface on Neovim
and finished early for plain Vim, leaving `start-vim.sh` users with
only commentstring + suffixesadd — exactly the "no commands" report.
`autoload/nuwiki/commands.vim` now ships the Vim-side dispatch:
- `s:exec` sends `workspace/executeCommand` through vim-lsp's
`lsp#send_request`. Optional callback for commands that return data.
- `s:open_uri_from` reads `{ uri }` out of the LSP response and runs
`:edit` (or `:tabedit` for the tab variants).
- `s:results_to_qf` lifts `checkLinks` / `findOrphans` / `tags.search`
arrays into the quickfix list and opens `:copen` — same UX as the
Neovim path.
- `s:open_browser` fires `open` / `xdg-open` after `export.browse`.
- §13.1 deferred commands stub out with a "not yet implemented"
notification so users get the same signal as on Neovim.
`ftplugin/vimwiki.vim` defines the same `:Vimwiki*` / `:Nuwiki*`
command set on the plain-Vim path, each delegating to its
`nuwiki#commands#…` autoload counterpart. `:VimwikiFollowLink` /
`:VimwikiBacklinks` map straight to vim-lsp's `:LspDefinition` /
`:LspReferences`. coc.nvim users can still use `:CocCommand` directly
and ignore the aliases entirely.
Verified with `vim -e -s -u .vimrc index.wiki`:
filetype=vimwiki, did_ftplugin=1,
:VimwikiTOC, :NuwikiIndex, :VimwikiToggleListItem all defined.
Total 377 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
" autoload/nuwiki/commands.vim — Vim-side wrappers around the LSP
|
||||
" commands the server advertises (Phase 19).
|
||||
"
|
||||
" Lives in autoload/ so the cost is paid only when a `:Vimwiki*` /
|
||||
" `:Nuwiki*` command is actually invoked. Dispatches via vim-lsp's
|
||||
" `lsp#send_request`. coc.nvim's command surface is rich enough that
|
||||
" users should just call `:CocCommand nuwiki.x` directly there — this
|
||||
" file targets the vim-lsp path.
|
||||
|
||||
let s:server = 'nuwiki'
|
||||
|
||||
function! s:buf_uri() abort
|
||||
return 'file://' . fnamemodify(expand('%:p'), ':p')
|
||||
endfunction
|
||||
|
||||
function! s:cursor_position() abort
|
||||
return {
|
||||
\ 'textDocument': { 'uri': s:buf_uri() },
|
||||
\ 'position': { 'line': line('.') - 1, 'character': col('.') - 1 },
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
function! s:has_vimlsp() abort
|
||||
return exists('*lsp#send_request')
|
||||
endfunction
|
||||
|
||||
function! s:exec(command, arguments, ...) abort
|
||||
if !s:has_vimlsp()
|
||||
echohl ErrorMsg
|
||||
echom 'nuwiki: vim-lsp not loaded; cannot run ' . a:command
|
||||
echohl None
|
||||
return
|
||||
endif
|
||||
let l:opts = {
|
||||
\ 'method': 'workspace/executeCommand',
|
||||
\ 'params': { 'command': a:command, 'arguments': a:arguments },
|
||||
\ }
|
||||
if a:0 >= 1
|
||||
let l:opts['on_notification'] = a:1
|
||||
endif
|
||||
call lsp#send_request(s:server, l:opts)
|
||||
endfunction
|
||||
|
||||
function! s:open_uri_from(response, ...) abort
|
||||
let l:result = get(get(a:response, 'response', {}), 'result', {})
|
||||
if type(l:result) != type({}) || !has_key(l:result, 'uri')
|
||||
return
|
||||
endif
|
||||
let l:path = substitute(l:result['uri'], '^file://', '', '')
|
||||
let l:cmd = a:0 >= 1 ? a:1 : 'edit'
|
||||
execute l:cmd . ' ' . fnameescape(l:path)
|
||||
endfunction
|
||||
|
||||
function! s:notify_deferred(name) abort
|
||||
echohl WarningMsg
|
||||
echom 'nuwiki: ' . a:name . ' is not yet implemented — see SPEC §13.1'
|
||||
echohl None
|
||||
endfunction
|
||||
|
||||
" ===== Wiki picker =====
|
||||
|
||||
function! nuwiki#commands#wiki_index(count) abort
|
||||
let l:args = a:count > 0 ? [{ 'wiki': a:count - 1 }] : [{}]
|
||||
call s:exec('nuwiki.wiki.openIndex', l:args,
|
||||
\ function('s:open_uri_from'))
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#wiki_tab_index(count) abort
|
||||
let l:args = a:count > 0 ? [{ 'wiki': a:count - 1 }] : [{}]
|
||||
call s:exec('nuwiki.wiki.tabOpenIndex', l:args,
|
||||
\ {n -> s:open_uri_from(n, 'tabedit')})
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#wiki_goto_page(name) abort
|
||||
let l:name = a:name
|
||||
if l:name ==# ''
|
||||
let l:name = input('Goto page: ')
|
||||
endif
|
||||
if l:name ==# ''
|
||||
return
|
||||
endif
|
||||
call s:exec('nuwiki.wiki.gotoPage', [{ 'page': l:name }],
|
||||
\ function('s:open_uri_from'))
|
||||
endfunction
|
||||
|
||||
" ===== Diary =====
|
||||
|
||||
function! s:diary_open(cmd_name) abort
|
||||
call s:exec(a:cmd_name, [{ 'uri': s:buf_uri() }],
|
||||
\ function('s:open_uri_from'))
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#diary_today() abort | call s:diary_open('nuwiki.diary.openToday') | endfunction
|
||||
function! nuwiki#commands#diary_yesterday() abort | call s:diary_open('nuwiki.diary.openYesterday') | endfunction
|
||||
function! nuwiki#commands#diary_tomorrow() abort | call s:diary_open('nuwiki.diary.openTomorrow') | endfunction
|
||||
function! nuwiki#commands#diary_index() abort | call s:diary_open('nuwiki.diary.openIndex') | endfunction
|
||||
function! nuwiki#commands#diary_next() abort | call s:diary_open('nuwiki.diary.next') | endfunction
|
||||
function! nuwiki#commands#diary_prev() abort | call s:diary_open('nuwiki.diary.prev') | endfunction
|
||||
|
||||
function! nuwiki#commands#diary_generate_index() abort
|
||||
call s:exec('nuwiki.diary.generateIndex', [{ 'uri': s:buf_uri() }])
|
||||
endfunction
|
||||
|
||||
" ===== List + heading editing =====
|
||||
|
||||
function! s:exec_pos(cmd) abort
|
||||
let l:p = s:cursor_position()
|
||||
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
||||
call s:exec(a:cmd, l:args)
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#toggle_list_item() abort | call s:exec_pos('nuwiki.list.toggleCheckbox') | endfunction
|
||||
function! nuwiki#commands#cycle_list_item() abort | call s:exec_pos('nuwiki.list.cycleCheckbox') | endfunction
|
||||
function! nuwiki#commands#reject_list_item() abort | call s:exec_pos('nuwiki.list.rejectCheckbox') | endfunction
|
||||
function! nuwiki#commands#heading_add() abort | call s:exec_pos('nuwiki.heading.addLevel') | endfunction
|
||||
function! nuwiki#commands#heading_remove() abort | call s:exec_pos('nuwiki.heading.removeLevel') | endfunction
|
||||
|
||||
function! nuwiki#commands#next_task() abort
|
||||
let l:p = s:cursor_position()
|
||||
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
||||
call s:exec('nuwiki.list.nextTask', l:args,
|
||||
\ {n -> s:goto_location(n)})
|
||||
endfunction
|
||||
|
||||
function! s:goto_location(notification) abort
|
||||
let l:loc = get(get(a:notification, 'response', {}), 'result', {})
|
||||
if type(l:loc) != type({}) || !has_key(l:loc, 'range')
|
||||
return
|
||||
endif
|
||||
let l:line = l:loc['range']['start']['line'] + 1
|
||||
let l:col = l:loc['range']['start']['character'] + 1
|
||||
call cursor(l:line, l:col)
|
||||
endfunction
|
||||
|
||||
" ===== Generation + workspace =====
|
||||
|
||||
function! nuwiki#commands#toc_generate() abort | call s:exec('nuwiki.toc.generate', [{'uri': s:buf_uri()}]) | endfunction
|
||||
function! nuwiki#commands#links_generate() abort | call s:exec('nuwiki.links.generate', [{'uri': s:buf_uri()}]) | endfunction
|
||||
|
||||
function! nuwiki#commands#check_links() abort
|
||||
call s:exec('nuwiki.workspace.checkLinks', [{ 'uri': s:buf_uri() }],
|
||||
\ {n -> s:results_to_qf(n, 'broken links')})
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#find_orphans() abort
|
||||
call s:exec('nuwiki.workspace.findOrphans', [{ 'uri': s:buf_uri() }],
|
||||
\ {n -> s:results_to_qf(n, 'orphans')})
|
||||
endfunction
|
||||
|
||||
function! s:results_to_qf(notification, title) abort
|
||||
let l:rows = get(get(a:notification, 'response', {}), 'result', [])
|
||||
if type(l:rows) != type([]) || empty(l:rows)
|
||||
echom 'nuwiki: no ' . a:title
|
||||
return
|
||||
endif
|
||||
let l:items = []
|
||||
for l:row in l:rows
|
||||
let l:path = substitute(get(l:row, 'uri', ''), '^file://', '', '')
|
||||
let l:lnum = 1
|
||||
let l:col = 1
|
||||
if has_key(l:row, 'range')
|
||||
let l:lnum = l:row['range']['start']['line'] + 1
|
||||
let l:col = l:row['range']['start']['character'] + 1
|
||||
endif
|
||||
let l:text = get(l:row, 'message', get(l:row, 'name', ''))
|
||||
call add(l:items, { 'filename': l:path, 'lnum': l:lnum, 'col': l:col, 'text': l:text })
|
||||
endfor
|
||||
call setqflist([], ' ', { 'title': 'nuwiki ' . a:title, 'items': l:items })
|
||||
copen
|
||||
endfunction
|
||||
|
||||
" ===== Tags =====
|
||||
|
||||
function! nuwiki#commands#tags_search(query) abort
|
||||
let l:q = a:query
|
||||
if l:q ==# ''
|
||||
let l:q = input('Search tags: ')
|
||||
endif
|
||||
call s:exec('nuwiki.tags.search', [{ 'uri': s:buf_uri(), 'query': l:q }],
|
||||
\ {n -> s:results_to_qf(n, 'tag matches')})
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#tags_generate_links(tag) abort
|
||||
let l:args = { 'uri': s:buf_uri() }
|
||||
if a:tag !=# ''
|
||||
let l:args['tag'] = a:tag
|
||||
endif
|
||||
call s:exec('nuwiki.tags.generateLinks', [l:args])
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#tags_rebuild() abort
|
||||
call s:exec('nuwiki.tags.rebuild', [{ 'uri': s:buf_uri() }])
|
||||
endfunction
|
||||
|
||||
" ===== HTML export =====
|
||||
|
||||
function! nuwiki#commands#export_current() abort | call s:exec('nuwiki.export.currentToHtml', [{'uri': s:buf_uri()}]) | endfunction
|
||||
function! nuwiki#commands#export_all() abort | call s:exec('nuwiki.export.allToHtml', [{'uri': s:buf_uri()}]) | endfunction
|
||||
function! nuwiki#commands#export_all_force() abort | call s:exec('nuwiki.export.allToHtmlForce', [{'uri': s:buf_uri()}]) | endfunction
|
||||
function! nuwiki#commands#export_rss() abort | call s:exec('nuwiki.export.rss', [{'uri': s:buf_uri()}]) | endfunction
|
||||
|
||||
function! nuwiki#commands#export_browse() abort
|
||||
call s:exec('nuwiki.export.browse', [{ 'uri': s:buf_uri() }],
|
||||
\ {n -> s:open_browser(n)})
|
||||
endfunction
|
||||
|
||||
function! s:open_browser(notification) abort
|
||||
let l:result = get(get(a:notification, 'response', {}), 'result', {})
|
||||
let l:url = get(l:result, 'browse', '')
|
||||
if l:url ==# ''
|
||||
return
|
||||
endif
|
||||
if has('mac')
|
||||
silent execute '!open ' . shellescape(l:url, 1) . ' &'
|
||||
elseif has('unix')
|
||||
silent execute '!xdg-open ' . shellescape(l:url, 1) . ' &'
|
||||
else
|
||||
echom 'nuwiki: exported → ' . l:url
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" ===== File ops =====
|
||||
|
||||
function! nuwiki#commands#delete_file() abort
|
||||
if input('Delete current page? [y/N] ') !~? '^y'
|
||||
return
|
||||
endif
|
||||
call s:exec('nuwiki.file.delete', [{ 'uri': s:buf_uri() }])
|
||||
endfunction
|
||||
|
||||
function! nuwiki#commands#rename_file() abort
|
||||
if exists('*lsp#rename')
|
||||
LspRename
|
||||
else
|
||||
echohl WarningMsg
|
||||
echom 'nuwiki: :LspRename unavailable; install vim-lsp'
|
||||
echohl None
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" ===== §13.1 deferred =====
|
||||
|
||||
function! nuwiki#commands#list_change_lvl() abort | call s:notify_deferred(':VimwikiListChangeLvl') | endfunction
|
||||
function! nuwiki#commands#list_remove_done() abort | call s:notify_deferred(':VimwikiRemoveDone') | endfunction
|
||||
function! nuwiki#commands#table_insert() abort | call s:notify_deferred(':VimwikiTable') | endfunction
|
||||
function! nuwiki#commands#table_move_left() abort | call s:notify_deferred(':VimwikiTableMoveColumnLeft') | endfunction
|
||||
function! nuwiki#commands#table_move_right() abort | call s:notify_deferred(':VimwikiTableMoveColumnRight') | endfunction
|
||||
function! nuwiki#commands#colorize(color) abort | call s:notify_deferred(':VimwikiColorize') | endfunction
|
||||
function! nuwiki#commands#paste_link() abort | call s:notify_deferred(':VimwikiPasteLink') | endfunction
|
||||
function! nuwiki#commands#paste_url() abort | call s:notify_deferred(':VimwikiPasteUrl') | endfunction
|
||||
Reference in New Issue
Block a user