From 0dfe0910fccfafe842ecbd3d469db2debed51347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Tue, 26 May 2026 22:55:40 -0300 Subject: [PATCH] fix(tables): cycle Tab into the next row instead of inserting one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pressing 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 --- autoload/nuwiki/commands.vim | 16 ++++++++++++++-- lua/nuwiki/commands.lua | 28 ++++++++++++++++++++++------ scripts/test-keymaps-vim.vim | 12 ++++++++++++ scripts/test-keymaps.lua | 15 +++++++++++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/autoload/nuwiki/commands.vim b/autoload/nuwiki/commands.vim index ba8dee0..99c5730 100644 --- a/autoload/nuwiki/commands.vim +++ b/autoload/nuwiki/commands.vim @@ -1025,10 +1025,22 @@ function! nuwiki#commands#smart_tab() abort if l:target_idx < len(l:starts) return "\call nuwiki#commands#_table_jump_to_cell(" . line('.') . ", " . l:target_idx . ")\" 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 "\call nuwiki#commands#_table_jump_to_cell(" . l:next . ", 1)\" + endif + let l:next += 1 + endwhile let l:indent = matchstr(l:line, '^\s*') let l:cursor_col = len(l:indent) + 2 - return "\call nuwiki#commands#_table_insert_row_below(" . line('.') . ", " . l:cursor_col . ")\" + return "\call nuwiki#commands#_table_insert_row_below(" . l:row . ", " . l:cursor_col . ")\" endfunction function! nuwiki#commands#smart_shift_tab() abort diff --git a/lua/nuwiki/commands.lua b/lua/nuwiki/commands.lua index 0134d52..a6f9e73 100644 --- a/lua/nuwiki/commands.lua +++ b/lua/nuwiki/commands.lua @@ -826,16 +826,32 @@ 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 + if target_idx < #cell_starts then return string.format( - 'lua require("nuwiki.commands")._table_insert_row_below(%d)', - row + 'lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)', + 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( + 'lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)', + next_row, + 1 + ) + end + next_row = next_row + 1 + end return string.format( - 'lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)', - row, - target_idx + 'lua require("nuwiki.commands")._table_insert_row_below(%d)', + row ) end diff --git a/scripts/test-keymaps-vim.vim b/scripts/test-keymaps-vim.vim index 844d1b9..328f6bd 100644 --- a/scripts/test-keymaps-vim.vim +++ b/scripts/test-keymaps-vim.vim @@ -197,6 +197,18 @@ call s:run('tab.next_cell_in_table', { \ 'keys': "A\x\", \ '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\x\", + \ '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\x\", + \ 'expect_lines': ['| a | b |', '|---|---|', '| c | d |', '|x | |'], + \ }) call s:run('tab.from_first_cell_moves_to_second', { \ 'lines': ['| a | b |'], \ 'cursor': [1, 3], diff --git a/scripts/test-keymaps.lua b/scripts/test-keymaps.lua index beee0c7..db7c62a 100644 --- a/scripts/test-keymaps.lua +++ b/scripts/test-keymaps.lua @@ -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 = 'ix', + -- 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 = 'ix', + 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`