parity(vim): port text objects + folding, fix smart_return textlock
CI / cargo fmt --check (push) Successful in 53s
CI / cargo clippy (push) Successful in 1m27s
CI / cargo test (push) Successful in 1m24s
CI / editor keymaps (push) Successful in 2m27s

Close the remaining Vim-vs-Neovim functional gaps:

  * Text objects — new `autoload/nuwiki/textobjects.vim` mirroring
    `lua/nuwiki/textobjects.lua`. All five pairs (ah/ih, aH/iH,
    al/il, a\/i\, ac/ic) wired in the Vim path of `ftplugin/vimwiki.vim`
    via the classic `:<C-u>call` idiom that works cleanly in both
    operator-pending and visual modes.

  * Folding — new `autoload/nuwiki/folding.vim` providing a regex
    `foldexpr` over headings, plus a tidy `foldtext`. Wired in the
    Vim path; opts out via `let g:nuwiki_no_folding = 1`.

  * `smart_return` was using `setline()` / `append()` inside an
    `<expr>` callback — fine on Neovim but plain Vim's stricter
    textlock raised E565. Rewrote as pure keystrokes (matches the
    Lua version): table rows insert via `<CR>...<Esc>0li`, empty
    list lines break via `<Esc>0DA<CR>`.

  * Function-name parity: added
    `nuwiki#commands#heading_add_level`,
    `nuwiki#commands#heading_remove_level`,
    `nuwiki#commands#table_move_column_{left,right}` as thin
    aliases over the original short names so the public
    `nuwiki#commands#*` surface matches `require('nuwiki.commands').*`.

Test harness:
  * `scripts/test-keymaps-vim.vim` extended from 12 to 30 cases:
    4 smart_return, 3 smart_tab/<S-Tab>, 3 named-command exists,
    4 text-object helpers, 1 `dah` end-to-end, 2 folding, and the
    original 12 pure-VimL cases. (Visual-mode mark inspection in
    `vim -e -s` stays unreliable, so text objects are exercised
    at the helper level plus one operator-pending end-to-end.)

Gates: 456 Rust / 39 Neovim / 30 Vim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 22:30:15 +00:00
parent c8a6fc1172
commit ae96562969
5 changed files with 510 additions and 7 deletions
+127
View File
@@ -162,6 +162,133 @@ call s:run('lists.O_continues_above', {
\ '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.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))
" ===== Wrap up =====
call add(s:results, '')