Files
nuwiki/scripts/test-keymaps-vim.vim
T

330 lines
10 KiB
VimL
Raw Normal View History

" scripts/test-keymaps-vim.vim — driven by scripts/test-keymaps-vim.sh.
"
" 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
" their coverage from the Neovim harness in scripts/test-keymaps.lua
" — 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
" ===== 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,
\ })
" ===== 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'],
\ })
" ===== 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 | |'],
\ })
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 | |'],
\ })
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))
" 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)
" ===== 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)
" ===== 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!')