parity(5): smart <CR> in insert mode (VimwikiReturn)

Bind `<CR>` in insert mode as an `<expr>` map to a `smart_return`
helper. Behaviour matches upstream vimwiki's `:VimwikiReturn`:

  - On a list line with content → continue the list with the same
    marker on a new line (preserving leading checkbox `[ ]` if any).
  - On an empty list line (marker only) → clear the marker and break
    out of the list with a plain newline.
  - Inside a `|…|` table row → insert a fresh empty row below with
    the same column count, cursor lands inside the first cell.
  - Otherwise → plain `<CR>`.

Implementation note: the helper is `<expr>`-bound, which means
textlock is active and the buffer can't be mutated from inside the
callback. The function returns key sequences only — including
`<Esc>0DA<CR>` for the empty-list break and `<Esc>0li` for the
table-row cursor jump — so every effect flows through Vim's normal
keystroke pipeline and stays undo-coherent.

Both Lua and VimL counterparts shipped.

Tests: 4 new in scripts/test-keymaps.lua covering list continuation,
checkbox preservation, empty-marker break-out, and new table row.

Gates: 421 Rust / 35 Neovim / 12 Vim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 21:34:36 +00:00
parent fd4d902fde
commit e347f605ff
5 changed files with 125 additions and 0 deletions
+45
View File
@@ -582,6 +582,51 @@ function M.list_toggle_or_add_checkbox()
vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
end
--- Smart `<CR>` for insert mode — vimwiki parity for `:VimwikiReturn`.
--- Used as an `<expr>` keymap so the function is side-effect-free on
--- the buffer (textlock is active). The returned string is fed into
--- Vim's input stream — we encode everything (including the table-row
--- insertion) as keystrokes so the user's typed keys after `<CR>` land
--- at the right cursor position.
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('<CR>')
local esc = tc('<Esc>')
-- Table row → newline + empty row + cursor inside first cell.
if line:match('^%s*|.*|%s*$') then
local cell_count = 0
for _ in line:gmatch('|') do cell_count = cell_count + 1 end
cell_count = math.max(cell_count - 1, 1)
local new_row = '|' .. string.rep(' |', cell_count)
-- After `<CR>` + new_row, cursor sits at end of new row. Bounce to
-- normal, jump to col 0, step right past the leading `|`, re-enter
-- insert: `<Esc>0li` puts the cursor immediately after the `|`.
return cr .. new_row .. esc .. '0li'
end
-- List item with marker?
local indent, marker, after = line:match('^(%s*)([%-%*%#])%s(.*)$')
if not marker then
local n
indent, n, marker, after = line:match('^(%s*)(%d+)([%.%)])%s(.*)$')
if marker then marker = n .. marker end
end
if marker then
local cb = after:match('^%[[%sxXoO%.%-]%]%s*(.*)$')
local body = cb or after
if body:match('^%s*$') then
-- Empty item → drop to normal, clear the line, re-enter insert,
-- then a fresh newline. Indent is dropped (matches vimwiki's
-- "break out of the list" behaviour on an empty bullet).
return esc .. '0DA' .. cr
end
return cr .. indent .. marker .. ' ' .. (cb and '[ ] ' or '')
end
return cr
end
-- §13.1 Cluster B — table rewriters.
function M.table_insert(cols, rows)
+5
View File
@@ -254,6 +254,11 @@ function M.attach(bufnr, mappings)
{ desc = 'nuwiki: cycle list symbol backward (insert)' }, bufnr)
map('i', '<C-L><C-M>', cmd.list_toggle_or_add_checkbox,
{ desc = 'nuwiki: toggle/add checkbox (insert)' }, bufnr)
-- Smart <CR>: continue list / table rows. `<expr>` so the function
-- result is the keystrokes fed — keeps undo coherent.
map('i', '<CR>', cmd.smart_return,
{ desc = 'nuwiki: smart return (insert)', expr = true }, bufnr)
end
-- ===== Header levels + nav =====