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
+40 -7
View File
@@ -690,13 +690,14 @@ endfunction
" finishes, keeping the undo block coherent. " finishes, keeping the undo block coherent.
function! nuwiki#commands#smart_return() abort function! nuwiki#commands#smart_return() abort
let l:line = getline('.') let l:line = getline('.')
" `<expr>` runs under textlock, so the function must be side-effect-
" free on the buffer. The returned string is fed into Vim's input
" stream after we unwind — we encode the table-row insertion as
" explicit keystrokes (matches lua/nuwiki/commands.lua).
if l:line =~# '^\s*|.*|\s*$' if l:line =~# '^\s*|.*|\s*$'
let l:n = max([len(split(l:line, '|', 1)) - 2, 1]) let l:n = max([len(split(l:line, '|', 1)) - 2, 1])
let l:new_row = '|' . repeat(' |', l:n) let l:new_row = '|' . repeat(' |', l:n)
let l:row = line('.') return "\<CR>" . l:new_row . "\<Esc>0li"
call append(l:row, l:new_row)
call cursor(l:row + 1, 3)
return ''
endif endif
let l:m = matchlist(l:line, '^\(\s*\)\([-*#]\)\s\(.*\)$') let l:m = matchlist(l:line, '^\(\s*\)\([-*#]\)\s\(.*\)$')
if empty(l:m) if empty(l:m)
@@ -713,9 +714,9 @@ function! nuwiki#commands#smart_return() abort
let l:has_cb = !empty(l:cb) let l:has_cb = !empty(l:cb)
let l:body = l:has_cb ? l:cb[2] : l:rest let l:body = l:has_cb ? l:cb[2] : l:rest
if l:body =~# '^\s*$' if l:body =~# '^\s*$'
call setline('.', '') " Empty item — drop to normal, clear the line, re-enter insert,
call cursor(line('.'), 1) " newline. Indent gets dropped (matches vimwiki's break-out).
return "\<CR>" return "\<Esc>0DA\<CR>"
endif endif
let l:cont = l:indent . l:marker . ' ' let l:cont = l:indent . l:marker . ' '
if l:has_cb if l:has_cb
@@ -792,3 +793,35 @@ function! nuwiki#commands#normalize_link() abort
endif endif
call s:wrap_cword_as_wikilink() call s:wrap_cword_as_wikilink()
endfunction endfunction
" ===== Name-parity aliases =====
"
" The Lua surface and the VimL surface diverged on a handful of names:
"
" Lua → VimL
" heading_add_level heading_add
" heading_remove_level heading_remove
" table_move_column_left table_move_left
" table_move_column_right table_move_right
"
" Real callers always go through the keymaps / `:Vimwiki*` commands
" defined in `ftplugin/vimwiki.vim`, which already route correctly.
" These aliases exist so the *public* `nuwiki#commands#*` surface
" matches Lua's `require('nuwiki.commands').*` 1:1 for any user who
" calls them by name from their own config.
function! nuwiki#commands#heading_add_level() abort
call nuwiki#commands#heading_add()
endfunction
function! nuwiki#commands#heading_remove_level() abort
call nuwiki#commands#heading_remove()
endfunction
function! nuwiki#commands#table_move_column_left() abort
call nuwiki#commands#table_move_left()
endfunction
function! nuwiki#commands#table_move_column_right() abort
call nuwiki#commands#table_move_right()
endfunction
+43
View File
@@ -0,0 +1,43 @@
" autoload/nuwiki/folding.vim — `foldexpr` for vimwiki buffers.
"
" Mirrors lua/nuwiki/folding.lua. Pure regex over `getline()`, so works
" without a language server. Activated in `ftplugin/vimwiki.vim`:
"
" setlocal foldmethod=expr
" setlocal foldexpr=nuwiki#folding#expr()
" setlocal foldtext=nuwiki#folding#foldtext()
"
" Fold structure mirrors the document outline: each `= H1 =` opens a
" level-1 fold, each `== H2 ==` opens a level-2 fold, etc., and folds
" close implicitly at the next heading of same-or-shallower level (or
" end of buffer).
function! s:heading_level(line) abort
let l:lead = matchstr(a:line, '^\s*\zs=\+\ze\s')
if empty(l:lead) | return 0 | endif
let l:lvl = strlen(l:lead)
if l:lvl > 6 | return 0 | endif
let l:trail = matchstr(a:line, '\s\zs=\+\ze\s*$')
if empty(l:trail) || strlen(l:trail) != l:lvl
return 0
endif
return l:lvl
endfunction
function! nuwiki#folding#expr() abort
let l:line = getline(v:lnum)
let l:lvl = s:heading_level(l:line)
if l:lvl > 0
return '>' . l:lvl
endif
return '='
endfunction
function! nuwiki#folding#foldtext() abort
let l:line = getline(v:foldstart)
let l:title = substitute(l:line, '=', '', 'g')
let l:title = substitute(l:title, '^\s\+', '', '')
let l:title = substitute(l:title, '\s\+$', '', '')
let l:count = v:foldend - v:foldstart + 1
return '▸ ' . l:title . ' … (' . l:count . ' lines)'
endfunction
+266
View File
@@ -0,0 +1,266 @@
" autoload/nuwiki/textobjects.vim — vimwiki parity text objects for plain Vim.
"
" Mirrors lua/nuwiki/textobjects.lua. Five operator-pending + visual pairs:
"
" ah / ih heading section only (stops at any next heading)
" aH / iH heading section + sub-tree (stops at same-or-shallower)
" al / il list item (line, with / without marker prefix + checkbox)
" a\ / i\ table cell (with / without surrounding `|` separators)
" ac / ic table column (with / without the header separator row)
"
" Pure regex / `getline()` — no LSP round-trip. Each public function
" computes the (start_line, start_col, end_line, end_col) rectangle and
" hands it to `s:select_range` which issues a single `normal!` command
" with either `V` (linewise) or `v` / `<C-v>` (charwise / blockwise).
"
" The keymaps wire up via the standard Vim pattern:
" xnoremap <silent><buffer> ah :<C-u>call nuwiki#textobjects#heading(0, 0)<CR>
" onoremap <silent><buffer> ah :<C-u>call nuwiki#textobjects#heading(0, 0)<CR>
" `:<C-u>` clears the prefix count (and exits visual cleanly for xmap);
" the function then re-establishes the visual selection. From
" operator-pending mode Vim absorbs the resulting visual range as the
" pending operator's motion.
" ===== Heading helpers =====
function! s:heading_level(line) abort
let l:lead = matchstr(a:line, '^\s*\zs=\+\ze\s')
if empty(l:lead) | return 0 | endif
let l:lvl = strlen(l:lead)
if l:lvl > 6 | return 0 | endif
let l:trail = matchstr(a:line, '\s\zs=\+\ze\s*$')
if empty(l:trail) || strlen(l:trail) != l:lvl
return 0
endif
return l:lvl
endfunction
" Returns `[start, end]` line numbers, or `[]` if no heading found.
function! s:heading_block(lnum, with_subtree) abort
let l:total = line('$')
let l:start = 0
let l:i = a:lnum
while l:i >= 1
if s:heading_level(getline(l:i)) > 0
let l:start = l:i
break
endif
let l:i -= 1
endwhile
if l:start == 0 | return [] | endif
let l:level = s:heading_level(getline(l:start))
let l:stop = l:total
let l:j = l:start + 1
while l:j <= l:total
let l:lvl = s:heading_level(getline(l:j))
if l:lvl > 0
if a:with_subtree
if l:lvl <= l:level
let l:stop = l:j - 1
break
endif
else
let l:stop = l:j - 1
break
endif
endif
let l:j += 1
endwhile
return [l:start, l:stop]
endfunction
" Drive a fresh visual selection covering the given range.
" - mode='V' (linewise) — only s_line / e_line matter
" - mode='v' (charwise) — s_col / e_col are 1-based
" - mode='\<C-v>' (blockwise) — used by `ac` / `ic`
" Always issues a clean `<Esc>` first so any in-progress visual anchor
" doesn't stick.
function! s:select_range(s_line, s_col, e_line, e_col, mode) abort
let l:cur_mode = mode(1)
let l:prefix = ''
if l:cur_mode =~# "^[vV\<C-v>]"
let l:prefix = "\<Esc>"
endif
if a:mode ==# 'V'
let l:keys = a:s_line . 'GV' . a:e_line . 'G'
elseif a:mode ==# "\<C-v>"
let l:keys = a:s_line . 'G' . a:s_col . '|' . "\<C-v>" . a:e_line . 'G' . a:e_col . '|'
else
let l:keys = a:s_line . 'G' . a:s_col . '|v' . a:e_line . 'G' . a:e_col . '|'
endif
call feedkeys(l:prefix . l:keys, 'nx')
endfunction
function! nuwiki#textobjects#heading(inner, with_subtree) abort
let l:r = s:heading_block(line('.'), a:with_subtree)
if empty(l:r) | return | endif
let [l:s, l:e] = l:r
if a:inner | let l:s += 1 | endif
if l:e < l:s | return | endif
call s:select_range(l:s, 0, l:e, 0, 'V')
endfunction
" ===== List item =====
function! s:is_list_line(line) abort
return a:line =~# '^\s*[-*#]\s' || a:line =~# '^\s*\w\+[.)]\s'
endfunction
function! nuwiki#textobjects#list_item(inner) abort
let l:lnum = line('.')
let l:line = getline(l:lnum)
if !s:is_list_line(l:line) | return | endif
let l:line_len = strlen(l:line)
if a:inner
let l:m = matchend(l:line, '^\s*[-*#]\s\+')
if l:m < 0
let l:m = matchend(l:line, '^\s*\w\+[.)]\s\+')
endif
if l:m < 0 | return | endif
let l:cb_len = matchend(l:line[l:m :], '^\[[ xXoO.\-]\]\s\+')
if l:cb_len > 0
let l:m += l:cb_len
endif
call s:select_range(l:lnum, l:m + 1, l:lnum, l:line_len, 'v')
else
call s:select_range(l:lnum, 1, l:lnum, l:line_len, 'v')
endif
endfunction
" ===== Table cell / column =====
function! s:is_table_row(line) abort
return a:line =~# '^\s*|.*|\s*$'
endfunction
" Returns a list of `[start_col, end_col]` byte positions (1-based,
" inclusive) for the *content* of each cell on the row — i.e. the
" character span between two `|` separators, not including the
" separators themselves.
function! s:cell_ranges(line) abort
let l:out = []
let l:lead = matchend(a:line, '^\s*|')
if l:lead < 0 | return l:out | endif
let l:pos = l:lead + 1
while 1
let l:next = stridx(a:line, '|', l:pos - 1)
if l:next < 0 | break | endif
" 1-based, inclusive: content runs from l:pos (after prior |) to l:next.
call add(l:out, [l:pos, l:next])
let l:pos = l:next + 2
endwhile
return l:out
endfunction
" Find which cell index (1-based) the cursor is in. Returns `[idx, range]`
" or `[0, []]` if not on a table row.
function! s:current_cell(lnum, col) abort
let l:line = getline(a:lnum)
if !s:is_table_row(l:line) | return [0, []] | endif
let l:ranges = s:cell_ranges(l:line)
let l:i = 0
for l:r in l:ranges
let l:i += 1
if a:col >= l:r[0] && a:col <= l:r[1] + 1
return [l:i, l:r]
endif
endfor
if !empty(l:ranges)
return [1, l:ranges[0]]
endif
return [0, []]
endfunction
function! nuwiki#textobjects#table_cell(inner) abort
let l:lnum = line('.')
let l:col = col('.')
let [l:idx, l:range] = s:current_cell(l:lnum, l:col)
if l:idx == 0 | return | endif
if a:inner
let l:line = getline(l:lnum)
let l:s = l:range[0]
let l:e = l:range[1]
while l:s <= l:e && l:line[l:s - 1] ==# ' '
let l:s += 1
endwhile
while l:e >= l:s && l:line[l:e - 1] ==# ' '
let l:e -= 1
endwhile
if l:e < l:s | return | endif
call s:select_range(l:lnum, l:s, l:lnum, l:e, 'v')
else
call s:select_range(l:lnum, l:range[0], l:lnum, l:range[1] + 1, 'v')
endif
endfunction
" Walk up/down from `lnum` to find the contiguous block of table rows.
function! s:table_bounds(lnum) abort
if !s:is_table_row(getline(a:lnum)) | return [] | endif
let l:total = line('$')
let l:s = a:lnum
let l:e = a:lnum
while l:s > 1 && s:is_table_row(getline(l:s - 1))
let l:s -= 1
endwhile
while l:e < l:total && s:is_table_row(getline(l:e + 1))
let l:e += 1
endwhile
return [l:s, l:e]
endfunction
function! nuwiki#textobjects#table_column(inner) abort
let l:lnum = line('.')
let l:col = col('.')
let [l:idx, l:_r] = s:current_cell(l:lnum, l:col)
if l:idx == 0 | return | endif
let l:bnd = s:table_bounds(l:lnum)
if empty(l:bnd) | return | endif
let [l:s, l:e] = l:bnd
if a:inner
" Drop a header-separator row (`|---|---|`) from the inner column.
while l:s <= l:e && getline(l:s) =~# '^\s*|[-:|\s]*|\s*$'
let l:s += 1
endwhile
while l:e >= l:s && getline(l:e) =~# '^\s*|[-:|\s]*|\s*$'
let l:e -= 1
endwhile
endif
if l:e < l:s | return | endif
let l:top = s:cell_ranges(getline(l:s))
let l:bot = s:cell_ranges(getline(l:e))
if l:idx > len(l:top) || l:idx > len(l:bot) | return | endif
let l:left = l:top[l:idx - 1][0]
let l:right = max([l:top[l:idx - 1][1], l:bot[l:idx - 1][1]])
call s:select_range(l:s, l:left, l:e, l:right, "\<C-v>")
endfunction
" ===== Debug exports (test harness) =====
"
" Mirror the `M._*` re-exports the Lua side has. Test code calls these
" so failures land at the calculation, not the visual-mode pipeline.
function! nuwiki#textobjects#_heading_block(lnum, with_subtree) abort
return s:heading_block(a:lnum, a:with_subtree)
endfunction
function! nuwiki#textobjects#_cell_ranges(line) abort
return s:cell_ranges(a:line)
endfunction
function! nuwiki#textobjects#_current_cell_for_line(line, col) abort
if !s:is_table_row(a:line) | return 0 | endif
let l:ranges = s:cell_ranges(a:line)
let l:i = 0
for l:r in l:ranges
let l:i += 1
if a:col >= l:r[0] && a:col <= l:r[1] + 1
return l:i
endif
endfor
if !empty(l:ranges) | return 1 | endif
return 0
endfunction
function! nuwiki#textobjects#_table_bounds(lnum) abort
return s:table_bounds(a:lnum)
endfunction
+34
View File
@@ -210,6 +210,40 @@ if !has('nvim')
nnoremap <silent><buffer> gw1 :call nuwiki#commands#table_align()<CR> nnoremap <silent><buffer> gw1 :call nuwiki#commands#table_align()<CR>
nnoremap <silent><buffer> <A-Left> :call nuwiki#commands#table_move_left()<CR> nnoremap <silent><buffer> <A-Left> :call nuwiki#commands#table_move_left()<CR>
nnoremap <silent><buffer> <A-Right> :call nuwiki#commands#table_move_right()<CR> nnoremap <silent><buffer> <A-Right> :call nuwiki#commands#table_move_right()<CR>
" Folding — heading-block fold structure (matches lua/nuwiki/folding.lua).
" Opt-out via `let g:nuwiki_no_folding = 1` (mirrors the Lua-side
" `folding = false` toggle on `setup()`).
if !get(g:, 'nuwiki_no_folding', 0)
setlocal foldmethod=expr
setlocal foldexpr=nuwiki#folding#expr()
setlocal foldtext=nuwiki#folding#foldtext()
let b:undo_ftplugin .= ' | setlocal foldmethod< foldexpr< foldtext<'
endif
" Text objects — vimwiki parity (matches lua/nuwiki/textobjects.lua).
" The `:<C-u>` clears any prefix count and the visual mode; the
" autoload function re-establishes the selection at the right range.
xnoremap <silent><buffer> ah :<C-u>call nuwiki#textobjects#heading(0, 0)<CR>
onoremap <silent><buffer> ah :<C-u>call nuwiki#textobjects#heading(0, 0)<CR>
xnoremap <silent><buffer> ih :<C-u>call nuwiki#textobjects#heading(1, 0)<CR>
onoremap <silent><buffer> ih :<C-u>call nuwiki#textobjects#heading(1, 0)<CR>
xnoremap <silent><buffer> aH :<C-u>call nuwiki#textobjects#heading(0, 1)<CR>
onoremap <silent><buffer> aH :<C-u>call nuwiki#textobjects#heading(0, 1)<CR>
xnoremap <silent><buffer> iH :<C-u>call nuwiki#textobjects#heading(1, 1)<CR>
onoremap <silent><buffer> iH :<C-u>call nuwiki#textobjects#heading(1, 1)<CR>
xnoremap <silent><buffer> al :<C-u>call nuwiki#textobjects#list_item(0)<CR>
onoremap <silent><buffer> al :<C-u>call nuwiki#textobjects#list_item(0)<CR>
xnoremap <silent><buffer> il :<C-u>call nuwiki#textobjects#list_item(1)<CR>
onoremap <silent><buffer> il :<C-u>call nuwiki#textobjects#list_item(1)<CR>
xnoremap <silent><buffer> a\ :<C-u>call nuwiki#textobjects#table_cell(0)<CR>
onoremap <silent><buffer> a\ :<C-u>call nuwiki#textobjects#table_cell(0)<CR>
xnoremap <silent><buffer> i\ :<C-u>call nuwiki#textobjects#table_cell(1)<CR>
onoremap <silent><buffer> i\ :<C-u>call nuwiki#textobjects#table_cell(1)<CR>
xnoremap <silent><buffer> ac :<C-u>call nuwiki#textobjects#table_column(0)<CR>
onoremap <silent><buffer> ac :<C-u>call nuwiki#textobjects#table_column(0)<CR>
xnoremap <silent><buffer> ic :<C-u>call nuwiki#textobjects#table_column(1)<CR>
onoremap <silent><buffer> ic :<C-u>call nuwiki#textobjects#table_column(1)<CR>
endif endif
finish finish
+127
View File
@@ -162,6 +162,133 @@ call s:run('lists.O_continues_above', {
\ 'expect_lines': ['- ', '- second'], \ '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 ===== " ===== Wrap up =====
call add(s:results, '') call add(s:results, '')