diff --git a/lua/nuwiki/commands.lua b/lua/nuwiki/commands.lua index 6583d2a..14de31c 100644 --- a/lua/nuwiki/commands.lua +++ b/lua/nuwiki/commands.lua @@ -725,49 +725,53 @@ local function align_table_at(row) vim.api.nvim_buf_set_lines(0, s - 1, e, false, new_lines) end -local function ensure_insert_mode() - if vim.api.nvim_get_mode().mode ~= 'i' then - vim.cmd('startinsert') - end +-- Public helpers dispatched via `` from the `` mappings. +-- The mappings return a `lua require('nuwiki.commands')._foo(…)` +-- keystroke so these run synchronously inside the same keypress — +-- crucial when the user types something *immediately* after Tab/, +-- which would otherwise land at the pre-jump cursor (the bug a prior +-- `vim.schedule`-based version shipped with). + +function M._table_jump_to_cell(row, target_idx) + align_table_at(row) + local new_line = vim.fn.getline(row) + local starts = table_cell_starts(new_line) + if target_idx < 1 or target_idx >= #starts then return end + -- starts[i] is the 1-based byte position of the i-th `|`. Placing the + -- cursor at 0-based byte `starts[i]` lands on the space immediately + -- after that pipe — the first character inside the cell it opens. + vim.api.nvim_win_set_cursor(0, { row, starts[target_idx] }) end -local function schedule_insert_table_row_below(row) - -- After `align_table_at`, the row count of the table containing - -- `row` is fixed and `getline(row)` reflects the aligned content. - vim.schedule(function() - align_table_at(row) - local new_line = vim.fn.getline(row) - local starts = table_cell_starts(new_line) - if #starts < 2 then return end - local indent = new_line:match('^(%s*)') or '' - local cell_count = #starts - 1 - local new_row = indent .. '|' .. string.rep(' |', cell_count) - vim.api.nvim_buf_set_lines(0, row, row, false, { new_row }) - vim.api.nvim_win_set_cursor(0, { row + 1, #indent + 1 }) - ensure_insert_mode() - end) +function M._table_insert_row_below(row) + align_table_at(row) + local new_line = vim.fn.getline(row) + local starts = table_cell_starts(new_line) + if #starts < 2 then return end + local indent = new_line:match('^(%s*)') or '' + local cell_count = #starts - 1 + local fresh = indent .. '|' .. string.rep(' |', cell_count) + vim.api.nvim_buf_set_lines(0, row, row, false, { fresh }) + vim.api.nvim_win_set_cursor(0, { row + 1, #indent + 1 }) end --- Smart `` for insert mode — vimwiki parity for `:VimwikiReturn`. --- Used as an `` keymap so the function is side-effect-free on ---- the buffer (textlock is active). Non-table branches encode their ---- behaviour as returned keystrokes; the table-row branch schedules a ---- post-textlock callback that aligns the table, inserts a fresh row, ---- and places the cursor inside the new row's first cell. +--- the buffer (textlock is active). The table-row branch hands off to +--- `_table_insert_row_below` via `` so the new row is in place +--- before the user's next keystroke is processed. function M.smart_return() local line = vim.api.nvim_get_current_line() local function tc(s) return vim.api.nvim_replace_termcodes(s, true, false, true) end local cr = tc('') local esc = tc('') - -- Table row → align existing rows, then add a fresh empty row below. - -- Doing this via `` + typed row keystrokes would split the - -- current line at the cursor and drag the trailing `|` onto the new - -- line — so we punt to `vim.schedule` instead. if is_table_row(line) then - local cur = vim.api.nvim_win_get_cursor(0) - schedule_insert_table_row_below(cur[1]) - return '' + local row = vim.api.nvim_win_get_cursor(0)[1] + return string.format( + 'lua require("nuwiki.commands")._table_insert_row_below(%d)', + row + ) end -- List item with marker? @@ -814,26 +818,17 @@ function M.smart_tab() if start <= col + 1 then current_idx = i end end local target_idx = current_idx + 1 - - vim.schedule(function() - align_table_at(row) - local new_line = vim.fn.getline(row) - local starts = table_cell_starts(new_line) - if target_idx < #starts then - -- Land at the first space of the target cell (byte position - -- equal to the 1-based pipe position). - vim.api.nvim_win_set_cursor(0, { row, starts[target_idx] }) - ensure_insert_mode() - else - local indent = new_line:match('^(%s*)') or '' - local cell_count = math.max(#starts - 1, 1) - local new_row = indent .. '|' .. string.rep(' |', cell_count) - vim.api.nvim_buf_set_lines(0, row, row, false, { new_row }) - vim.api.nvim_win_set_cursor(0, { row + 1, #indent + 1 }) - ensure_insert_mode() - end - end) - return '' + if target_idx >= #cell_starts then + return string.format( + 'lua require("nuwiki.commands")._table_insert_row_below(%d)', + row + ) + end + return string.format( + 'lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)', + row, + target_idx + ) end function M.smart_shift_tab() @@ -853,16 +848,11 @@ function M.smart_shift_tab() end if not current_idx or current_idx <= 1 then return stab end local target_idx = current_idx - 1 - - vim.schedule(function() - align_table_at(row) - local new_line = vim.fn.getline(row) - local starts = table_cell_starts(new_line) - if target_idx < 1 or target_idx >= #starts then return end - vim.api.nvim_win_set_cursor(0, { row, starts[target_idx] }) - ensure_insert_mode() - end) - return '' + return string.format( + 'lua require("nuwiki.commands")._table_jump_to_cell(%d, %d)', + row, + target_idx + ) end -- §13.1 Cluster B — table rewriters.