fix(tables): cycle Tab into the next row instead of inserting one

Pressing <Tab> past the last cell unconditionally appended a new row
below, even when there was already a next row to jump into. Tabbing
through a header → separator → body table from the header row
inserted a blank row between header and separator instead of landing
in the first body cell.

After the final cell, walk forward from the current row to the end
of the table, skip any separator row, and jump to the first cell of
the next data row. Only insert a fresh row when the cursor is on the
table's last row (or only separator rows follow). Applied to both
the Vim (autoload/nuwiki/commands.vim) and Neovim (lua/nuwiki/
commands.lua) sides so the two harnesses stay in sync; covered by
new keymap tests in scripts/test-keymaps-vim.vim and test-keymaps.lua.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 22:55:40 -03:00
parent e5b0042a90
commit 0dfe0910fc
4 changed files with 63 additions and 8 deletions
+14 -2
View File
@@ -1025,10 +1025,22 @@ function! nuwiki#commands#smart_tab() abort
if l:target_idx < len(l:starts)
return "\<Cmd>call nuwiki#commands#_table_jump_to_cell(" . line('.') . ", " . l:target_idx . ")\<CR>"
endif
" Past the final cell → add a fresh row below, cursor in its first cell.
" Past the final cell. Prefer cycling into the next existing row (skipping
" any separator row); only insert a fresh row when we're already on the
" table's last row.
let l:row = line('.')
let [l:s_row, l:e_row] = s:find_table_range(l:row)
let l:next = l:row + 1
while l:next <= l:e_row
let [l:_, l:next_cells] = s:parse_table_row(getline(l:next))
if !s:is_sep_row(l:next_cells)
return "\<Cmd>call nuwiki#commands#_table_jump_to_cell(" . l:next . ", 1)\<CR>"
endif
let l:next += 1
endwhile
let l:indent = matchstr(l:line, '^\s*')
let l:cursor_col = len(l:indent) + 2
return "\<Cmd>call nuwiki#commands#_table_insert_row_below(" . line('.') . ", " . l:cursor_col . ")\<CR>"
return "\<Cmd>call nuwiki#commands#_table_insert_row_below(" . l:row . ", " . l:cursor_col . ")\<CR>"
endfunction
function! nuwiki#commands#smart_shift_tab() abort
+22 -6
View File
@@ -826,18 +826,34 @@ function M.smart_tab()
if start <= col + 1 then current_idx = i end
end
local target_idx = current_idx + 1
if target_idx >= #cell_starts then
return string.format(
'<Cmd>lua require("nuwiki.commands")._table_insert_row_below(%d)<CR>',
row
)
end
if target_idx < #cell_starts then
return string.format(
'<Cmd>lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)<CR>',
row,
target_idx
)
end
-- Past the final cell. Prefer cycling into the next existing row
-- (skipping any separator); only insert a fresh row when we're on the
-- table's last row.
local _, e_row = find_table_range(row)
local next_row = row + 1
while next_row <= e_row do
local _, next_cells = parse_table_row(vim.fn.getline(next_row))
if not is_separator_row(next_cells) then
return string.format(
'<Cmd>lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)<CR>',
next_row,
1
)
end
next_row = next_row + 1
end
return string.format(
'<Cmd>lua require("nuwiki.commands")._table_insert_row_below(%d)<CR>',
row
)
end
function M.smart_shift_tab()
local line = vim.api.nvim_get_current_line()
+12
View File
@@ -197,6 +197,18 @@ call s:run('tab.next_cell_in_table', {
\ '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],
+15
View File
@@ -454,6 +454,21 @@ vim.defer_fn(function()
expect_lines = { '| a |x b |' },
wait_ms = 100,
})
run('tab.cycles_to_next_row_when_not_last', {
lines = { '| a | b |', '|---|---|', '| c | d |' },
cursor = { 1, 6 }, -- inside last cell of header row
keys = 'i<Tab>x<Esc>',
-- Past the final cell of row 1: skip separator, land in first cell of row 3.
expect_lines = { '| a | b |', '|---|---|', '|x c | d |' },
wait_ms = 100,
})
run('tab.inserts_row_only_when_on_last_row', {
lines = { '| a | b |', '|---|---|', '| c | d |' },
cursor = { 3, 6 }, -- inside last cell of last data row
keys = 'i<Tab>x<Esc>',
expect_lines = { '| a | b |', '|---|---|', '| c | d |', '|x | |' },
wait_ms = 100,
})
run('shift_tab.prev_cell_in_table', {
lines = { '| a | b |' },
cursor = { 1, 6 }, -- inside cell `b`