2026-05-30 18:35:40 +00:00
|
|
|
" development/tests/test-keymaps-vim.vim — driven by development/tests/test-keymaps-vim.sh.
|
2026-05-12 12:41:46 +00:00
|
|
|
"
|
|
|
|
|
" Pure-VimL keymap regression suite. Plain Vim's LSP path
|
|
|
|
|
" (vim-lsp + async.vim) talks to the server via timers, which won't
|
|
|
|
|
" reliably fire inside `vim -e -s` headless mode — so the cases here
|
|
|
|
|
" are restricted to the bindings whose RHS is implemented entirely in
|
|
|
|
|
" VimL (header/link nav, `o`/`O` bullet continuation, the wrap step
|
|
|
|
|
" of `<CR>`). LSP-roundtrip bindings (`<C-Space>`, `=`, `-`, …) get
|
2026-05-30 18:35:40 +00:00
|
|
|
" their coverage from the Neovim harness in development/tests/test-keymaps.lua
|
2026-05-12 12:41:46 +00:00
|
|
|
" — same Lua codepath, same server.
|
|
|
|
|
|
|
|
|
|
let s:results = []
|
|
|
|
|
let s:pass = 0
|
|
|
|
|
let s:fail = 0
|
|
|
|
|
let s:out = $NUWIKI_KEYMAP_RESULTS
|
|
|
|
|
|
|
|
|
|
function! s:record(ok, name, detail) abort
|
|
|
|
|
let l:line = (a:ok ? 'PASS' : 'FAIL') . ' ' . a:name
|
|
|
|
|
if a:detail !=# ''
|
|
|
|
|
let l:line .= ' — ' . a:detail
|
|
|
|
|
endif
|
|
|
|
|
call add(s:results, l:line)
|
|
|
|
|
if a:ok
|
|
|
|
|
let s:pass += 1
|
|
|
|
|
else
|
|
|
|
|
let s:fail += 1
|
|
|
|
|
endif
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:set_buf(lines) abort
|
|
|
|
|
silent %delete _
|
|
|
|
|
call setline(1, a:lines)
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:lines() abort
|
|
|
|
|
return getline(1, '$')
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:run(name, opts) abort
|
|
|
|
|
try
|
|
|
|
|
call s:set_buf(a:opts.lines)
|
|
|
|
|
let l:cursor = get(a:opts, 'cursor', [1, 1])
|
|
|
|
|
call cursor(l:cursor[0], l:cursor[1])
|
|
|
|
|
" feedkeys with 'tx' — t: typed (respects maps), x: execute now.
|
|
|
|
|
let l:keys = a:opts.keys
|
|
|
|
|
call feedkeys(l:keys, 'tx')
|
|
|
|
|
let l:detail = ''
|
|
|
|
|
if has_key(a:opts, 'expect_lines')
|
|
|
|
|
if s:lines() !=# a:opts.expect_lines
|
|
|
|
|
let l:detail = 'lines mismatch — expected ' . string(a:opts.expect_lines)
|
|
|
|
|
\ . ', actual ' . string(s:lines())
|
|
|
|
|
call s:record(0, a:name, l:detail)
|
|
|
|
|
return
|
|
|
|
|
endif
|
|
|
|
|
endif
|
|
|
|
|
if has_key(a:opts, 'expect_line') && has_key(a:opts, 'expect_at')
|
|
|
|
|
let l:got = getline(a:opts.expect_at)
|
|
|
|
|
if l:got !=# a:opts.expect_line
|
|
|
|
|
let l:detail = 'line ' . a:opts.expect_at . ' — expected ' . string(a:opts.expect_line)
|
|
|
|
|
\ . ', got ' . string(l:got)
|
|
|
|
|
call s:record(0, a:name, l:detail)
|
|
|
|
|
return
|
|
|
|
|
endif
|
|
|
|
|
endif
|
|
|
|
|
if has_key(a:opts, 'expect_cursor_line')
|
|
|
|
|
let l:lnum = line('.')
|
|
|
|
|
if l:lnum != a:opts.expect_cursor_line
|
|
|
|
|
let l:detail = 'cursor line — expected ' . a:opts.expect_cursor_line
|
|
|
|
|
\ . ', got ' . l:lnum
|
|
|
|
|
call s:record(0, a:name, l:detail)
|
|
|
|
|
return
|
|
|
|
|
endif
|
|
|
|
|
endif
|
|
|
|
|
call s:record(1, a:name, '')
|
|
|
|
|
catch
|
|
|
|
|
call s:record(0, a:name, v:exception)
|
|
|
|
|
endtry
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
" ===== Smoke: ftplugin loaded + commands defined =====
|
|
|
|
|
|
|
|
|
|
if &filetype !=# 'vimwiki'
|
|
|
|
|
call s:record(0, 'ftplugin.attached', 'filetype is ' . &filetype . ' (expected vimwiki)')
|
|
|
|
|
else
|
|
|
|
|
call s:record(1, 'ftplugin.attached', '')
|
|
|
|
|
endif
|
|
|
|
|
if exists(':VimwikiTOC') == 2
|
|
|
|
|
call s:record(1, 'cmd.VimwikiTOC_defined', '')
|
|
|
|
|
else
|
|
|
|
|
call s:record(0, 'cmd.VimwikiTOC_defined', ':VimwikiTOC not registered')
|
|
|
|
|
endif
|
|
|
|
|
if exists(':NuwikiIndex') == 2
|
|
|
|
|
call s:record(1, 'cmd.NuwikiIndex_defined', '')
|
|
|
|
|
else
|
|
|
|
|
call s:record(0, 'cmd.NuwikiIndex_defined', ':NuwikiIndex not registered')
|
|
|
|
|
endif
|
|
|
|
|
|
2026-05-30 18:35:40 +00:00
|
|
|
" ===== Documented command surface =====
|
|
|
|
|
" Every command must be reachable in BOTH the canonical :Nuwiki* form and
|
|
|
|
|
" the vimwiki-compatible :Vimwiki* form. The suffixes below are the command
|
|
|
|
|
" list minus the prefix. :NuwikiInstall is the one exception — it's a nuwiki-only
|
|
|
|
|
" maintenance command with no :Vimwiki* alias, so it's checked separately.
|
|
|
|
|
|
|
|
|
|
let s:both_forms = [
|
|
|
|
|
\ 'Index', 'TabIndex', 'UISelect', 'Goto', 'FollowLink', 'Backlinks',
|
2026-05-31 07:54:25 -03:00
|
|
|
\ 'SplitLink', 'VSplitLink', 'TabnewLink', 'TabDropLink', 'GoBackLink',
|
2026-05-30 18:35:40 +00:00
|
|
|
\ 'NextLink', 'PrevLink', 'BaddLink', 'RenameFile', 'DeleteFile',
|
2026-05-30 17:36:05 -03:00
|
|
|
\ 'MakeDiaryNote', 'TabMakeDiaryNote', 'MakeYesterdayDiaryNote',
|
|
|
|
|
\ 'MakeTomorrowDiaryNote',
|
2026-05-30 18:35:40 +00:00
|
|
|
\ 'DiaryIndex', 'DiaryGenerateLinks', 'DiaryNextDay', 'DiaryPrevDay',
|
|
|
|
|
\ 'TOC', 'GenerateLinks', 'CheckLinks', 'SearchTags', 'GenerateTagLinks',
|
|
|
|
|
\ 'RebuildTags', '2HTML', '2HTMLBrowse', 'All2HTML', 'Rss',
|
2026-05-30 17:36:05 -03:00
|
|
|
\ 'NormalizeLink', 'RenumberList', 'RenumberAllLists',
|
|
|
|
|
\ 'ListToggle', 'IncrementListItem', 'DecrementListItem',
|
|
|
|
|
\ 'CatUrl',
|
2026-05-30 18:35:40 +00:00
|
|
|
\ ]
|
|
|
|
|
for s:suffix in s:both_forms
|
|
|
|
|
for s:prefix in ['Nuwiki', 'Vimwiki']
|
|
|
|
|
let s:cmd = s:prefix . s:suffix
|
|
|
|
|
let s:ok = exists(':' . s:cmd) == 2
|
|
|
|
|
call s:record(s:ok ? 1 : 0, 'surface.' . s:cmd,
|
|
|
|
|
\ s:ok ? '' : ':' . s:cmd . ' not registered')
|
|
|
|
|
endfor
|
|
|
|
|
endfor
|
|
|
|
|
let s:ok = exists(':NuwikiInstall') == 2
|
|
|
|
|
call s:record(s:ok ? 1 : 0, 'surface.NuwikiInstall',
|
|
|
|
|
\ s:ok ? '' : ':NuwikiInstall not registered')
|
|
|
|
|
|
2026-05-30 17:36:05 -03:00
|
|
|
" Asymmetric / compat-only spellings: the canonical :Nuwiki* name differs
|
|
|
|
|
" from the upstream :Vimwiki* name (or has no canonical counterpart because
|
|
|
|
|
" nuwiki already exposes it under a clearer name).
|
|
|
|
|
for s:cmd in [
|
|
|
|
|
\ 'NuwikiTableAlign', 'VimwikiTableAlignQ', 'VimwikiTableAlignW',
|
|
|
|
|
\ 'NuwikiChangeSymbol', 'VimwikiChangeSymbolTo', 'VimwikiListChangeSymbolI',
|
|
|
|
|
\ 'NuwikiChangeSymbolInList', 'VimwikiChangeSymbolInListTo',
|
|
|
|
|
\ 'VimwikiDeleteLink', 'VimwikiRenameLink', 'VimwikiGenerateTags',
|
|
|
|
|
\ 'NuwikiRemoveCheckbox', 'VimwikiRemoveSingleCB',
|
|
|
|
|
\ 'NuwikiRemoveCheckboxInList', 'VimwikiRemoveCBInList',
|
|
|
|
|
\ ]
|
|
|
|
|
let s:ok = exists(':' . s:cmd) == 2
|
|
|
|
|
call s:record(s:ok ? 1 : 0, 'surface.' . s:cmd,
|
|
|
|
|
\ s:ok ? '' : ':' . s:cmd . ' not registered')
|
|
|
|
|
endfor
|
|
|
|
|
|
2026-05-30 18:35:40 +00:00
|
|
|
" ===== Pure-VimL command invocation (cursor movement) =====
|
|
|
|
|
" :NuwikiNextLink / :NuwikiPrevLink wrap search('\[\[', …) so they run
|
|
|
|
|
" entirely in VimL and move the cursor without touching the LSP.
|
|
|
|
|
|
|
|
|
|
call s:set_buf(['intro [[A]] more [[B]] end'])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
silent NuwikiNextLink
|
|
|
|
|
call s:record(
|
|
|
|
|
\ col('.') == 7 ? 1 : 0,
|
|
|
|
|
\ 'invoke.NuwikiNextLink_jumps_to_first_link',
|
|
|
|
|
\ 'col=' . col('.'))
|
|
|
|
|
silent NuwikiNextLink
|
|
|
|
|
call s:record(
|
|
|
|
|
\ col('.') == 18 ? 1 : 0,
|
|
|
|
|
\ 'invoke.NuwikiNextLink_jumps_to_second_link',
|
|
|
|
|
\ 'col=' . col('.'))
|
|
|
|
|
silent NuwikiPrevLink
|
|
|
|
|
call s:record(
|
|
|
|
|
\ col('.') == 7 ? 1 : 0,
|
|
|
|
|
\ 'invoke.NuwikiPrevLink_jumps_back',
|
|
|
|
|
\ 'col=' . col('.'))
|
|
|
|
|
|
|
|
|
|
" The :Vimwiki* alias must drive the identical motion.
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
silent VimwikiNextLink
|
|
|
|
|
call s:record(
|
|
|
|
|
\ col('.') == 7 ? 1 : 0,
|
|
|
|
|
\ 'invoke.VimwikiNextLink_jumps_to_first_link',
|
|
|
|
|
\ 'col=' . col('.'))
|
|
|
|
|
|
|
|
|
|
" ===== Documented mapping surface =====
|
|
|
|
|
" Every documented mapping must resolve to a buffer-local mapping in the
|
|
|
|
|
" mode(s) the doc lists it under. The mouse group is opt-in — the shell
|
|
|
|
|
" wrapper sets g:nuwiki_mouse_mappings before the ftplugin loads.
|
|
|
|
|
|
|
|
|
|
function! s:has_map(lhs, mode) abort
|
|
|
|
|
let l:d = maparg(a:lhs, a:mode, 0, 1)
|
|
|
|
|
return !empty(l:d) && get(l:d, 'buffer', 0)
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
let s:mapping_surface = [
|
|
|
|
|
\ ['<CR>', 'n'], ['<S-CR>', 'n'], ['<C-CR>', 'n'], ['<C-S-CR>', 'n'],
|
2026-05-31 17:28:06 +00:00
|
|
|
\ ['<M-CR>', 'n'], ['<D-CR>', 'n'],
|
2026-05-30 18:35:40 +00:00
|
|
|
\ ['<BS>', 'n'], ['<Tab>', 'n'], ['<S-Tab>', 'n'], ['+', 'nx'],
|
|
|
|
|
\ ['<C-Space>', 'nx'], ['<C-@>', 'nx'], ['<Nul>', 'nx'],
|
|
|
|
|
\ ['gnt', 'n'], ['gln', 'nx'], ['glp', 'nx'], ['glx', 'nx'],
|
|
|
|
|
\ ['glh', 'n'], ['gll', 'n'], ['gLh', 'n'], ['gLl', 'n'],
|
2026-05-31 08:41:27 -03:00
|
|
|
\ ['glr', 'n'], ['gLr', 'n'], ['gl', 'n'], ['gL', 'n'],
|
2026-05-31 08:10:10 -03:00
|
|
|
\ ['gl-', 'n'], ['gL-', 'n'], ['gl*', 'n'], ['gL*', 'n'],
|
|
|
|
|
\ ['gl#', 'n'], ['gL#', 'n'], ['gl1', 'n'], ['gL1', 'n'],
|
|
|
|
|
\ ['gli', 'n'], ['gLi', 'n'], ['glI', 'n'], ['gLI', 'n'],
|
|
|
|
|
\ ['gla', 'n'], ['gLa', 'n'], ['glA', 'n'], ['gLA', 'n'],
|
2026-05-30 18:35:40 +00:00
|
|
|
\ ['o', 'n'], ['O', 'n'],
|
|
|
|
|
\ ['=', 'n'], ['-', 'n'], [']]', 'n'], ['[[', 'n'],
|
|
|
|
|
\ [']=', 'n'], ['[=', 'n'], [']u', 'n'], ['[u', 'n'],
|
|
|
|
|
\ ['gqq', 'n'], ['gq1', 'n'], ['gww', 'n'], ['gw1', 'n'],
|
|
|
|
|
\ ['<A-Left>', 'n'], ['<A-Right>', 'n'],
|
|
|
|
|
\ ['<Leader>ww', 'n'], ['<Leader>ws', 'n'], ['<Leader>wi', 'n'],
|
|
|
|
|
\ ['<Leader>w<Leader>w', 'n'], ['<Leader>w<Leader>y', 'n'],
|
2026-05-31 17:28:06 +00:00
|
|
|
\ ['<Leader>w<Leader>t', 'n'], ['<Leader>w<Leader>m', 'n'],
|
|
|
|
|
\ ['<Leader>w<Leader>i', 'n'],
|
2026-05-30 18:35:40 +00:00
|
|
|
\ ['<Leader>wn', 'n'], ['<Leader>wd', 'n'], ['<Leader>wr', 'n'],
|
|
|
|
|
\ ['<Leader>wh', 'n'], ['<Leader>whh', 'n'], ['<Leader>wha', 'n'],
|
|
|
|
|
\ ['<Leader>wc', 'nx'], ['<C-Down>', 'n'], ['<C-Up>', 'n'],
|
|
|
|
|
\ ['<2-LeftMouse>', 'n'], ['<S-2-LeftMouse>', 'n'], ['<C-2-LeftMouse>', 'n'],
|
|
|
|
|
\ ['<MiddleMouse>', 'n'], ['<RightMouse>', 'n'],
|
|
|
|
|
\ ['ah', 'ox'], ['ih', 'ox'], ['aH', 'ox'], ['iH', 'ox'],
|
|
|
|
|
\ ['al', 'ox'], ['il', 'ox'], ['a\', 'ox'], ['i\', 'ox'],
|
|
|
|
|
\ ['ac', 'ox'], ['ic', 'ox'],
|
|
|
|
|
\ ['<CR>', 'i'], ['<Tab>', 'i'], ['<S-Tab>', 'i'],
|
|
|
|
|
\ ['<C-D>', 'i'], ['<C-T>', 'i'],
|
|
|
|
|
\ ['<C-L><C-J>', 'i'], ['<C-L><C-K>', 'i'], ['<C-L><C-M>', 'i'],
|
|
|
|
|
\ ]
|
|
|
|
|
for s:entry in s:mapping_surface
|
|
|
|
|
for s:m in split(s:entry[1], '\zs')
|
|
|
|
|
let s:ok = s:has_map(s:entry[0], s:m)
|
|
|
|
|
call s:record(s:ok ? 1 : 0, 'map[' . s:m . '].' . s:entry[0],
|
|
|
|
|
\ s:ok ? '' : s:entry[0] . ' (' . s:m . ') not mapped buffer-local')
|
|
|
|
|
endfor
|
|
|
|
|
endfor
|
|
|
|
|
|
2026-05-31 16:21:49 +00:00
|
|
|
" ===== Diary <Leader>w<Leader>{t,m} target (collision fix) =====
|
|
|
|
|
" Upstream: <Leader>w<Leader>t = today in a new tab, <Leader>w<Leader>m =
|
|
|
|
|
" tomorrow. nuwiki used to bind both to tomorrow.
|
|
|
|
|
call s:record(
|
|
|
|
|
\ maparg('<Leader>w<Leader>t', 'n') =~# 'diary_today_tab' ? 1 : 0,
|
|
|
|
|
\ 'diary.leader_t_opens_today_in_tab',
|
|
|
|
|
\ 'rhs=' . maparg('<Leader>w<Leader>t', 'n'))
|
|
|
|
|
call s:record(
|
|
|
|
|
\ maparg('<Leader>w<Leader>m', 'n') =~# 'diary_tomorrow' ? 1 : 0,
|
|
|
|
|
\ 'diary.leader_m_opens_tomorrow',
|
|
|
|
|
\ 'rhs=' . maparg('<Leader>w<Leader>m', 'n'))
|
|
|
|
|
|
2026-05-12 12:41:46 +00:00
|
|
|
" ===== Header nav (pure VimL) =====
|
|
|
|
|
|
|
|
|
|
call s:run('headers.]]_jumps_to_next', {
|
|
|
|
|
\ 'lines': ['= One =', 'body', '== Two ==', 'body', '= Three ='],
|
|
|
|
|
\ 'cursor': [1, 1],
|
|
|
|
|
\ 'keys': ']]',
|
|
|
|
|
\ 'expect_cursor_line': 3,
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('headers.[[_jumps_to_prev', {
|
|
|
|
|
\ 'lines': ['= One =', 'body', '== Two ==', 'body', '= Three ='],
|
|
|
|
|
\ 'cursor': [5, 1],
|
|
|
|
|
\ 'keys': '[[',
|
|
|
|
|
\ 'expect_cursor_line': 3,
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('headers.]u_jumps_to_parent', {
|
|
|
|
|
\ 'lines': ['= Parent =', '== Child ==', 'body'],
|
|
|
|
|
\ 'cursor': [3, 1],
|
|
|
|
|
\ 'keys': ']u',
|
|
|
|
|
\ 'expect_cursor_line': 1,
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('headers.]=_jumps_to_next_sibling', {
|
|
|
|
|
\ 'lines': ['= A =', '== child ==', 'body', '= B ='],
|
|
|
|
|
\ 'cursor': [1, 1],
|
|
|
|
|
\ 'keys': ']=',
|
|
|
|
|
\ 'expect_cursor_line': 4,
|
|
|
|
|
\ })
|
2026-05-30 18:35:40 +00:00
|
|
|
call s:run('headers.[=_jumps_to_prev_sibling', {
|
|
|
|
|
\ 'lines': ['= A =', '== child ==', 'body', '= B ='],
|
|
|
|
|
\ 'cursor': [4, 1],
|
|
|
|
|
\ 'keys': '[=',
|
|
|
|
|
\ 'expect_cursor_line': 1,
|
|
|
|
|
\ })
|
2026-05-12 12:41:46 +00:00
|
|
|
|
|
|
|
|
" ===== Link nav (pure VimL) =====
|
|
|
|
|
|
|
|
|
|
call s:run('links.<Tab>_finds_next_wikilink', {
|
|
|
|
|
\ 'lines': ['intro [[A]] more [[B]] end'],
|
|
|
|
|
\ 'cursor': [1, 1],
|
|
|
|
|
\ 'keys': "\<Tab>",
|
|
|
|
|
\ 'expect_cursor_line': 1,
|
|
|
|
|
\ })
|
|
|
|
|
|
|
|
|
|
" ===== <CR> first press wraps bare word =====
|
|
|
|
|
|
|
|
|
|
call s:run('cr.wraps_bare_word_first_press', {
|
|
|
|
|
\ 'lines': ['MyPage rest'],
|
|
|
|
|
\ 'cursor': [1, 3],
|
|
|
|
|
\ 'keys': "\<CR>",
|
|
|
|
|
\ 'expect_line': '[[MyPage]] rest',
|
|
|
|
|
\ 'expect_at': 1,
|
|
|
|
|
\ })
|
|
|
|
|
|
|
|
|
|
" ===== Bullet continuation =====
|
|
|
|
|
|
|
|
|
|
call s:run('lists.o_continues_list_marker', {
|
|
|
|
|
\ 'lines': ['- first'],
|
|
|
|
|
\ 'cursor': [1, 1],
|
|
|
|
|
\ 'keys': "o\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['- first', '- '],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('lists.o_continues_checkbox', {
|
|
|
|
|
\ 'lines': ['- [ ] first'],
|
|
|
|
|
\ 'cursor': [1, 1],
|
|
|
|
|
\ 'keys': "o\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['- [ ] first', '- [ ] '],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('lists.O_continues_above', {
|
|
|
|
|
\ 'lines': ['- second'],
|
|
|
|
|
\ 'cursor': [1, 1],
|
|
|
|
|
\ 'keys': "O\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['- ', '- second'],
|
|
|
|
|
\ })
|
|
|
|
|
|
2026-05-12 22:30:15 +00:00
|
|
|
" ===== Insert-mode smart_return (Cluster 5) =====
|
|
|
|
|
|
|
|
|
|
call s:run('cr.continues_list_marker', {
|
|
|
|
|
\ 'lines': ['- first'],
|
|
|
|
|
\ 'cursor': [1, 7],
|
|
|
|
|
\ 'keys': "A\<CR>second\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['- first', '- second'],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('cr.continues_checkbox', {
|
|
|
|
|
\ 'lines': ['- [ ] first'],
|
|
|
|
|
\ 'cursor': [1, 11],
|
|
|
|
|
\ 'keys': "A\<CR>second\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['- [ ] first', '- [ ] second'],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('cr.breaks_on_empty_list_line', {
|
|
|
|
|
\ 'lines': ['- '],
|
|
|
|
|
\ 'cursor': [1, 2],
|
|
|
|
|
\ 'keys': "A\<CR>plain\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['', 'plain'],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('cr.adds_new_table_row', {
|
|
|
|
|
\ 'lines': ['| a | b |'],
|
|
|
|
|
\ 'cursor': [1, 4],
|
|
|
|
|
\ 'keys': "A\<CR>x\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['| a | b |', '|x | |'],
|
|
|
|
|
\ })
|
|
|
|
|
|
|
|
|
|
" ===== Insert-mode smart_tab / smart_shift_tab (Cluster 6) =====
|
|
|
|
|
|
|
|
|
|
call s:run('tab.next_cell_in_table', {
|
|
|
|
|
\ 'lines': ['| a | b |'],
|
|
|
|
|
\ 'cursor': [1, 3],
|
|
|
|
|
\ 'keys': "A\<Tab>x\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['| a | b |', '|x | |'],
|
|
|
|
|
\ })
|
2026-05-26 22:55:40 -03:00
|
|
|
call s:run('tab.cycles_to_next_row_when_not_last', {
|
|
|
|
|
\ 'lines': ['| a | b |', '|---|---|', '| c | d |'],
|
|
|
|
|
\ 'cursor': [1, 7],
|
|
|
|
|
\ 'keys': "i\<Tab>x\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['| a | b |', '|---|---|', '|x c | d |'],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('tab.inserts_row_only_when_on_last_row', {
|
|
|
|
|
\ 'lines': ['| a | b |', '|---|---|', '| c | d |'],
|
|
|
|
|
\ 'cursor': [3, 7],
|
|
|
|
|
\ 'keys': "i\<Tab>x\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['| a | b |', '|---|---|', '| c | d |', '|x | |'],
|
|
|
|
|
\ })
|
2026-05-12 22:30:15 +00:00
|
|
|
call s:run('tab.from_first_cell_moves_to_second', {
|
|
|
|
|
\ 'lines': ['| a | b |'],
|
|
|
|
|
\ 'cursor': [1, 3],
|
|
|
|
|
\ 'keys': "i\<Tab>x\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['| a |x b |'],
|
|
|
|
|
\ })
|
|
|
|
|
call s:run('shift_tab.prev_cell_in_table', {
|
|
|
|
|
\ 'lines': ['| a | b |'],
|
|
|
|
|
\ 'cursor': [1, 7],
|
|
|
|
|
\ 'keys': "i\<S-Tab>x\<Esc>",
|
|
|
|
|
\ 'expect_lines': ['|x a | b |'],
|
|
|
|
|
\ })
|
|
|
|
|
|
|
|
|
|
" ===== Named link commands (Cluster 1) =====
|
|
|
|
|
|
|
|
|
|
if exists(':VimwikiNextLink') == 2
|
|
|
|
|
call s:record(1, 'cmd.VimwikiNextLink_defined', '')
|
|
|
|
|
else
|
|
|
|
|
call s:record(0, 'cmd.VimwikiNextLink_defined', ':VimwikiNextLink not registered')
|
|
|
|
|
endif
|
|
|
|
|
if exists(':VimwikiPrevLink') == 2
|
|
|
|
|
call s:record(1, 'cmd.VimwikiPrevLink_defined', '')
|
|
|
|
|
else
|
|
|
|
|
call s:record(0, 'cmd.VimwikiPrevLink_defined', ':VimwikiPrevLink not registered')
|
|
|
|
|
endif
|
|
|
|
|
if exists(':VimwikiBaddLink') == 2
|
|
|
|
|
call s:record(1, 'cmd.VimwikiBaddLink_defined', '')
|
|
|
|
|
else
|
|
|
|
|
call s:record(0, 'cmd.VimwikiBaddLink_defined', ':VimwikiBaddLink not registered')
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" ===== Text objects (Cluster 3) =====
|
|
|
|
|
" Exercise the pure helpers directly — visual-mode mark inspection in
|
|
|
|
|
" `vim -e -s` is unreliable for the same reasons the Lua harness uses
|
|
|
|
|
" the helper-level tests.
|
|
|
|
|
|
|
|
|
|
call s:set_buf(['= h1 =', 'a', '== h2 ==', 'b', '== h3 ==', 'c'])
|
|
|
|
|
call s:record(
|
|
|
|
|
\ nuwiki#textobjects#_heading_block(1, 0) ==# [1, 2]
|
|
|
|
|
\ ? 1 : 0,
|
|
|
|
|
\ 'textobj.heading_block_section_only',
|
|
|
|
|
\ 'got ' . string(nuwiki#textobjects#_heading_block(1, 0)))
|
|
|
|
|
call s:record(
|
|
|
|
|
\ nuwiki#textobjects#_heading_block(1, 1) ==# [1, 6]
|
|
|
|
|
\ ? 1 : 0,
|
|
|
|
|
\ 'textobj.heading_block_subtree',
|
|
|
|
|
\ 'got ' . string(nuwiki#textobjects#_heading_block(1, 1)))
|
|
|
|
|
|
|
|
|
|
let s:cells = nuwiki#textobjects#_cell_ranges('| a | b | c |')
|
|
|
|
|
call s:record(
|
|
|
|
|
\ len(s:cells) == 3 ? 1 : 0,
|
|
|
|
|
\ 'textobj.cell_ranges_three_cells',
|
|
|
|
|
\ len(s:cells) == 3 ? '' : 'got ' . string(s:cells))
|
|
|
|
|
|
|
|
|
|
let s:cell_idx = nuwiki#textobjects#_current_cell_for_line('| a | b | c |', 8)
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:cell_idx == 2 ? 1 : 0,
|
|
|
|
|
\ 'textobj.current_cell_picks_middle',
|
|
|
|
|
\ s:cell_idx == 2 ? '' : 'got idx=' . s:cell_idx)
|
|
|
|
|
|
|
|
|
|
call setline(1, ['before', '| a | b |', '| c | d |', '', 'after'])
|
|
|
|
|
let s:bnd = nuwiki#textobjects#_table_bounds(2)
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:bnd ==# [2, 3] ? 1 : 0,
|
|
|
|
|
\ 'textobj.table_bounds_walks_contig_block',
|
|
|
|
|
\ s:bnd ==# [2, 3] ? '' : 'got ' . string(s:bnd))
|
|
|
|
|
|
|
|
|
|
" Operator-pending: `dah` should delete the heading section.
|
|
|
|
|
call s:set_buf(['= h1 =', 'body', '= h2 =', 'rest'])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
call feedkeys('dah', 'tx')
|
|
|
|
|
let s:dah = getline(1, '$')
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:dah ==# ['= h2 =', 'rest'] || s:dah ==# ['', '= h2 =', 'rest']
|
|
|
|
|
\ ? 1 : 0,
|
|
|
|
|
\ 'textobj.dah_deletes_heading_section',
|
|
|
|
|
\ 'got ' . string(s:dah))
|
|
|
|
|
|
|
|
|
|
" ===== Folding =====
|
|
|
|
|
|
|
|
|
|
call setline(1, ['= h1 =', 'a', '== h2 ==', 'b', 'c'])
|
|
|
|
|
let &l:foldmethod = 'expr'
|
|
|
|
|
let &l:foldexpr = 'nuwiki#folding#expr()'
|
|
|
|
|
call s:record(
|
|
|
|
|
\ foldlevel(1) > 0 ? 1 : 0,
|
|
|
|
|
\ 'folding.heading_starts_fold',
|
|
|
|
|
\ 'foldlevel(1)=' . foldlevel(1))
|
|
|
|
|
call s:record(
|
|
|
|
|
\ foldlevel(2) > 0 ? 1 : 0,
|
|
|
|
|
\ 'folding.body_inherits_fold',
|
|
|
|
|
\ 'foldlevel(2)=' . foldlevel(2))
|
2026-05-26 23:02:17 -03:00
|
|
|
" ftplugin sets `foldlevel=99` so every heading-block fold starts open
|
|
|
|
|
" rather than collapsed on file open.
|
|
|
|
|
call s:record(
|
|
|
|
|
\ &l:foldlevel == 99 ? 1 : 0,
|
|
|
|
|
\ 'folding.starts_with_all_folds_open',
|
|
|
|
|
\ '&l:foldlevel=' . &l:foldlevel)
|
2026-05-12 22:30:15 +00:00
|
|
|
|
2026-05-28 20:03:32 -03:00
|
|
|
" ===== Conceal =====
|
|
|
|
|
|
|
|
|
|
" The ftplugin reveals the cursor line in normal mode (concealcursor has no
|
|
|
|
|
" `n`) so left/right motions move over visible characters instead of stalling
|
|
|
|
|
" on hidden conceal regions like a wikilink's `[[`/`]]`.
|
|
|
|
|
call s:record(
|
|
|
|
|
\ &l:conceallevel == 2 ? 1 : 0,
|
|
|
|
|
\ 'conceal.level_is_two',
|
|
|
|
|
\ '&l:conceallevel=' . &l:conceallevel)
|
|
|
|
|
call s:record(
|
|
|
|
|
\ &l:concealcursor !~# 'n' ? 1 : 0,
|
|
|
|
|
\ 'conceal.cursor_line_revealed_in_normal_mode',
|
|
|
|
|
\ '&l:concealcursor=' . &l:concealcursor)
|
|
|
|
|
|
2026-05-28 20:04:31 -03:00
|
|
|
" ===== Wiki picker (config-driven, no LSP) =====
|
|
|
|
|
|
|
|
|
|
" The picker builds its list straight from config so it works from any buffer
|
|
|
|
|
" before the LSP (which only starts on a wiki buffer) is running.
|
|
|
|
|
let g:nuwiki_wikis = [
|
|
|
|
|
\ {'name': 'Personal', 'root': '/tmp/nuwiki-a'},
|
|
|
|
|
\ {'name': 'Work', 'root': '/tmp/nuwiki-b', 'file_extension': 'md'},
|
|
|
|
|
\ ]
|
|
|
|
|
let s:wl = nuwiki#commands#wiki_list()
|
|
|
|
|
call s:record(
|
|
|
|
|
\ len(s:wl) == 2 ? 1 : 0,
|
|
|
|
|
\ 'wiki_select.lists_all_configured_wikis',
|
|
|
|
|
\ 'count=' . len(s:wl))
|
|
|
|
|
call s:record(
|
|
|
|
|
\ get(get(s:wl, 0, {}), 'name', '') ==# 'Personal'
|
|
|
|
|
\ && get(get(s:wl, 1, {}), 'name', '') ==# 'Work' ? 1 : 0,
|
|
|
|
|
\ 'wiki_select.preserves_names_and_order',
|
|
|
|
|
\ 'names=' . string(map(copy(s:wl), 'get(v:val, "name", "")')))
|
|
|
|
|
call s:record(
|
|
|
|
|
\ get(get(s:wl, 1, {}), 'ext', '') ==# '.md' ? 1 : 0,
|
|
|
|
|
\ 'wiki_select.honours_per_wiki_extension',
|
|
|
|
|
\ 'ext=' . get(get(s:wl, 1, {}), 'ext', ''))
|
|
|
|
|
unlet g:nuwiki_wikis
|
|
|
|
|
|
2026-05-31 15:37:09 +00:00
|
|
|
" ===== Colorize (cword, visual selection, ranged command) =====
|
|
|
|
|
" The command is -nargs=1 -range: normal invocation wraps the cword; a visual
|
|
|
|
|
" range (`:'<,'>VimwikiColorize` / the x-mode <Leader>wc map) wraps the
|
|
|
|
|
" selection. Run these while still on the vimwiki buffer (the buffer-local
|
|
|
|
|
" command must be defined).
|
|
|
|
|
call s:set_buf(['word here'])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
silent VimwikiColorize red
|
|
|
|
|
call s:record(
|
|
|
|
|
\ getline(1) ==# '<span style="color:red">word</span> here' ? 1 : 0,
|
|
|
|
|
\ 'colorize.command_wraps_cword',
|
|
|
|
|
\ 'got ' . string(getline(1)))
|
|
|
|
|
|
|
|
|
|
" Visual selection → wraps the selection (visual flag passed explicitly).
|
|
|
|
|
call s:set_buf(['one two three'])
|
|
|
|
|
call cursor(1, 5)
|
|
|
|
|
call feedkeys("viw\<Esc>", 'tx')
|
|
|
|
|
call nuwiki#commands#colorize('blue', 2)
|
|
|
|
|
call s:record(
|
|
|
|
|
\ getline(1) ==# 'one <span style="color:blue">two</span> three' ? 1 : 0,
|
|
|
|
|
\ 'colorize.visual_wraps_selection',
|
|
|
|
|
\ 'got ' . string(getline(1)))
|
|
|
|
|
|
|
|
|
|
" Ranged command must not raise E481 and wraps the selection.
|
|
|
|
|
call s:set_buf(['alpha beta gamma'])
|
|
|
|
|
call cursor(1, 8)
|
|
|
|
|
call feedkeys("viw\<Esc>", 'tx')
|
|
|
|
|
let s:cz_err = ''
|
|
|
|
|
try
|
|
|
|
|
silent '<,'>VimwikiColorize green
|
|
|
|
|
catch
|
|
|
|
|
let s:cz_err = v:exception
|
|
|
|
|
endtry
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:cz_err ==# '' && getline(1) ==# 'alpha <span style="color:green">beta</span> gamma' ? 1 : 0,
|
|
|
|
|
\ 'colorize.ranged_command_no_error',
|
|
|
|
|
\ 'err=' . s:cz_err . ' got ' . string(getline(1)))
|
|
|
|
|
|
2026-05-31 16:01:21 +00:00
|
|
|
" Colour spans are concealed down to their text. Put the span on a non-cursor
|
|
|
|
|
" line so concealcursor (=c) doesn't reveal it, then refresh + inspect.
|
|
|
|
|
call s:set_buf(['plain line', '<span style="color:red">word</span> tail'])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
call nuwiki#colors#refresh()
|
|
|
|
|
let s:cz_l = getline(2)
|
|
|
|
|
let s:cz_open = synconcealed(2, 1)[0]
|
|
|
|
|
let s:cz_word = synconcealed(2, stridx(s:cz_l, 'word') + 1)
|
|
|
|
|
let s:cz_close = synconcealed(2, stridx(s:cz_l, '</span>') + 1)[0]
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:cz_open == 1 && s:cz_close == 1 && s:cz_word[0] == 0
|
|
|
|
|
\ && synIDattr(synID(2, stridx(s:cz_l, 'word') + 1, 1), 'name') ==# 'nuwikiColorSpan_red' ? 1 : 0,
|
|
|
|
|
\ 'colorize.conceal_hides_tags_shows_text',
|
|
|
|
|
\ 'open=' . s:cz_open . ' close=' . s:cz_close . ' word=' . s:cz_word[0]
|
|
|
|
|
\ . ' grp=' . synIDattr(synID(2, stridx(s:cz_l, 'word') + 1, 1), 'name'))
|
|
|
|
|
|
|
|
|
|
" Running the command conceals the new span immediately (colorize refreshes;
|
|
|
|
|
" no manual step). Move the cursor off the line so concealcursor reveals nothing.
|
|
|
|
|
call s:set_buf(['target word', 'other line'])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
silent VimwikiColorize teal
|
|
|
|
|
call cursor(2, 1)
|
|
|
|
|
let s:cz_l2 = getline(1)
|
|
|
|
|
call s:record(
|
|
|
|
|
\ synconcealed(1, 1)[0] == 1
|
|
|
|
|
\ && synIDattr(synID(1, stridx(s:cz_l2, 'target') + 1, 1), 'name') ==# 'nuwikiColorSpan_teal' ? 1 : 0,
|
|
|
|
|
\ 'colorize.command_conceals_immediately',
|
|
|
|
|
\ 'concealed=' . synconcealed(1, 1)[0]
|
|
|
|
|
\ . ' grp=' . synIDattr(synID(1, stridx(s:cz_l2, 'target') + 1, 1), 'name'))
|
|
|
|
|
|
2026-05-31 16:09:15 +00:00
|
|
|
" A span whose colour Vim can't allocate (rgb()/prose) must be skipped — not
|
|
|
|
|
" abort refresh, not conceal — while a real span afterwards still conceals.
|
|
|
|
|
call s:set_buf(['plain', 'x <span style="color:rgb(1,2,3)">y</span> z', '<span style="color:red">word</span> z'])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
let s:cz_e = ''
|
|
|
|
|
try
|
|
|
|
|
call nuwiki#colors#refresh()
|
|
|
|
|
catch
|
|
|
|
|
let s:cz_e = v:exception
|
|
|
|
|
endtry
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:cz_e ==# ''
|
|
|
|
|
\ && synconcealed(3, 1)[0] == 1
|
|
|
|
|
\ && synconcealed(2, stridx(getline(2), '<span') + 1)[0] == 0 ? 1 : 0,
|
|
|
|
|
\ 'colorize.skips_unallocatable_colour',
|
|
|
|
|
\ 'err=' . s:cz_e . ' real=' . synconcealed(3, 1)[0]
|
|
|
|
|
\ . ' rgb=' . synconcealed(2, stridx(getline(2), '<span') + 1)[0])
|
|
|
|
|
|
2026-05-31 17:57:03 +00:00
|
|
|
" ===== NormalizeLink visual wraps the selection (pure-VimL) =====
|
|
|
|
|
" `:VimwikiNormalizeLink 1` / the x-mode `+` wrap the `'<`/`'>` selection.
|
|
|
|
|
call s:set_buf(['one two three'])
|
|
|
|
|
call cursor(1, 5)
|
|
|
|
|
call feedkeys("viw\<Esc>", 'tx')
|
|
|
|
|
call nuwiki#commands#normalize_link(1)
|
|
|
|
|
call s:record(
|
|
|
|
|
\ getline(1) ==# 'one [[two]] three' ? 1 : 0,
|
|
|
|
|
\ 'normalize.visual_wraps_selection',
|
|
|
|
|
\ 'got ' . string(getline(1)))
|
|
|
|
|
|
2026-05-31 15:00:59 +00:00
|
|
|
" ===== Follow-link resolves + opens in the current window (regression) =====
|
|
|
|
|
" follow_link_or_create() must resolve the definition itself and `:edit` the
|
|
|
|
|
" target in the CURRENT window — including a page that does not exist yet
|
|
|
|
|
" (create-on-follow). We open the URI ourselves rather than delegating to
|
|
|
|
|
" :LspDefinition / coc's jumpDefinition because those fetch the target text
|
|
|
|
|
" (vim-lsp readfile()s it, throwing E484 for a missing page and aborting the
|
|
|
|
|
" jump). This harness loads only nuwiki, so :LspDefinition is absent and the
|
|
|
|
|
" coc branch of s:open_definition() runs once CocAction* are stubbed.
|
|
|
|
|
" The stubbed getDefinition points at a NON-EXISTENT file, so a green result
|
|
|
|
|
" proves both current-window placement and create-on-follow.
|
|
|
|
|
let g:_nuwiki_target = tempname() . '-newpage.wiki'
|
|
|
|
|
function! CocActionAsync(...) abort
|
|
|
|
|
endfunction
|
|
|
|
|
function! CocAction(action, ...) abort
|
|
|
|
|
if a:action ==# 'definitions'
|
|
|
|
|
return [{ 'uri': 'file://' . g:_nuwiki_target,
|
|
|
|
|
\ 'range': { 'start': { 'line': 0, 'character': 0 },
|
|
|
|
|
\ 'end': { 'line': 0, 'character': 0 } } }]
|
|
|
|
|
endif
|
|
|
|
|
return []
|
|
|
|
|
endfunction
|
|
|
|
|
" `:edit` must be able to abandon the (modified) scratch buffer.
|
|
|
|
|
let s:had_hidden = &hidden
|
|
|
|
|
set hidden
|
|
|
|
|
let s:seed = expand('%:p')
|
|
|
|
|
call s:set_buf(['[[NewPage]] rest'])
|
|
|
|
|
call cursor(1, 4)
|
|
|
|
|
call nuwiki#commands#follow_link_or_create()
|
|
|
|
|
let s:now = expand('%:p')
|
|
|
|
|
call s:record(
|
|
|
|
|
\ (s:now =~# 'newpage\.wiki$' && s:now !=# s:seed) ? 1 : 0,
|
|
|
|
|
\ 'cr.follow_opens_missing_page_in_current_window',
|
|
|
|
|
\ 'buf=' . s:now)
|
|
|
|
|
let &hidden = s:had_hidden
|
|
|
|
|
delfunction CocAction
|
|
|
|
|
delfunction CocActionAsync
|
|
|
|
|
unlet g:_nuwiki_target
|
|
|
|
|
" NOTE: the vim-lsp branch of s:open_definition() can't be driven headlessly —
|
|
|
|
|
" it gates on `exists('*lsp#send_request')`, which returns 0 for an inline stub
|
|
|
|
|
" (Vim only resolves real autoload files). It shares the same s:open_location()
|
|
|
|
|
" open logic the coc case above exercises, and mirrors the shipped
|
|
|
|
|
" follow_link_drop() request mechanism; its end-to-end coverage is the manual
|
|
|
|
|
" development/start-vim.sh check.
|
|
|
|
|
|
2026-05-31 20:10:53 +00:00
|
|
|
" ===== :VimwikiTable -nargs=* (cols+rows) =====
|
|
|
|
|
" Was -nargs=1, which raised E488 on `:VimwikiTable 4 3`. The table insert
|
|
|
|
|
" itself is an LSP roundtrip (no server in this headless harness), so we only
|
|
|
|
|
" assert the arg parse succeeds — i.e. no E488 trailing-characters error.
|
|
|
|
|
call s:set_buf([''])
|
|
|
|
|
call cursor(1, 1)
|
|
|
|
|
let s:tbl_err = ''
|
|
|
|
|
try
|
|
|
|
|
silent VimwikiTable 4 3
|
|
|
|
|
catch /E488/
|
|
|
|
|
let s:tbl_err = v:exception
|
|
|
|
|
catch
|
|
|
|
|
" A non-E488 error (e.g. no LSP attached) is expected and fine here.
|
|
|
|
|
endtry
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:tbl_err ==# '' ? 1 : 0,
|
|
|
|
|
\ 'cmd.VimwikiTable_accepts_cols_rows',
|
|
|
|
|
\ 'err=' . s:tbl_err)
|
|
|
|
|
|
2026-05-31 20:16:10 +00:00
|
|
|
" ===== :VimwikiListChangeLvl -range -nargs=+ =====
|
|
|
|
|
" Was -nargs=? (range-less): `:1,3VimwikiListChangeLvl increase 0` raised E481
|
|
|
|
|
" (no range allowed) and/or E488 (trailing chars). The level change is an LSP
|
|
|
|
|
" roundtrip (no server here), so assert only the attribute parse: no E481/E488.
|
|
|
|
|
call s:set_buf(['- a', '- b', '- c'])
|
|
|
|
|
let s:lcl_err = ''
|
|
|
|
|
try
|
|
|
|
|
silent 1,3VimwikiListChangeLvl increase 0
|
|
|
|
|
catch /E481\|E488/
|
|
|
|
|
let s:lcl_err = v:exception
|
|
|
|
|
catch
|
|
|
|
|
" non-attribute error (e.g. no LSP) is expected and fine here.
|
|
|
|
|
endtry
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:lcl_err ==# '' ? 1 : 0,
|
|
|
|
|
\ 'cmd.VimwikiListChangeLvl_accepts_range_and_args',
|
|
|
|
|
\ 'err=' . s:lcl_err)
|
|
|
|
|
|
2026-05-31 20:20:43 +00:00
|
|
|
" ===== :VimwikiSplitLink -nargs=* =====
|
|
|
|
|
" Upstream takes optional reuse/move-cursor flags; nuwiki's defs took none, so
|
|
|
|
|
" `:VimwikiSplitLink 0 1` raised E488. Now -nargs=*: assert the arg parse
|
|
|
|
|
" succeeds. The split opens a window (the follow itself is an LSP no-op here);
|
|
|
|
|
" collapse back to one window afterward.
|
|
|
|
|
call s:set_buf(['[[Target]]'])
|
|
|
|
|
let s:sl_err = ''
|
|
|
|
|
try
|
|
|
|
|
silent VimwikiSplitLink 0 1
|
|
|
|
|
catch /E488/
|
|
|
|
|
let s:sl_err = v:exception
|
|
|
|
|
catch
|
|
|
|
|
" non-E488 (e.g. no LSP attached) is expected and fine here.
|
|
|
|
|
endtry
|
|
|
|
|
silent! only
|
|
|
|
|
call s:record(
|
|
|
|
|
\ s:sl_err ==# '' ? 1 : 0,
|
|
|
|
|
\ 'cmd.VimwikiSplitLink_accepts_args',
|
|
|
|
|
\ 'err=' . s:sl_err)
|
|
|
|
|
|
2026-05-12 12:41:46 +00:00
|
|
|
" ===== Wrap up =====
|
|
|
|
|
|
|
|
|
|
call add(s:results, '')
|
|
|
|
|
call add(s:results, printf('SUMMARY: %d passed, %d failed', s:pass, s:fail))
|
|
|
|
|
call writefile(s:results, s:out)
|
|
|
|
|
execute (s:fail == 0 ? 'qall!' : 'cquit!')
|