From e347f605ff1ffb3be72c5880e3ea78aa8c41c0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Tue, 12 May 2026 21:34:36 +0000 Subject: [PATCH] parity(5): smart in insert mode (VimwikiReturn) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bind `` in insert mode as an `` 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 ``. Implementation note: the helper is ``-bound, which means textlock is active and the buffer can't be mutated from inside the callback. The function returns key sequences only — including `0DA` for the empty-list break and `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) --- autoload/nuwiki/commands.vim | 42 +++++++++++++++++++++++++++++++++ ftplugin/vimwiki.vim | 1 + lua/nuwiki/commands.lua | 45 ++++++++++++++++++++++++++++++++++++ lua/nuwiki/keymaps.lua | 5 ++++ scripts/test-keymaps.lua | 32 +++++++++++++++++++++++++ 5 files changed, 125 insertions(+) diff --git a/autoload/nuwiki/commands.vim b/autoload/nuwiki/commands.vim index 275c830..0b735a6 100644 --- a/autoload/nuwiki/commands.vim +++ b/autoload/nuwiki/commands.vim @@ -684,6 +684,48 @@ function! nuwiki#commands#paste_url() abort call s:exec('nuwiki.link.pasteUrl', l:args) endfunction +" Smart for insert mode — vimwiki `:VimwikiReturn` parity. +" Used as `inoremap nuwiki#commands#smart_return()`. The +" returned string is fed into Vim's input stream after the function +" finishes, keeping the undo block coherent. +function! nuwiki#commands#smart_return() abort + let l:line = getline('.') + if l:line =~# '^\s*|.*|\s*$' + let l:n = max([len(split(l:line, '|', 1)) - 2, 1]) + let l:new_row = '|' . repeat(' |', l:n) + let l:row = line('.') + call append(l:row, l:new_row) + call cursor(l:row + 1, 3) + return '' + endif + let l:m = matchlist(l:line, '^\(\s*\)\([-*#]\)\s\(.*\)$') + if empty(l:m) + let l:m2 = matchlist(l:line, '^\(\s*\)\(\d\+\)\([.)]\)\s\(.*\)$') + if !empty(l:m2) + let l:m = [l:line, l:m2[1], l:m2[2] . l:m2[3], l:m2[4]] + endif + endif + if !empty(l:m) + let l:indent = l:m[1] + let l:marker = l:m[2] + let l:rest = l:m[3] + let l:cb = matchlist(l:rest, '^\(\[[ xXoO.\-]\]\)\s*\(.*\)$') + let l:has_cb = !empty(l:cb) + let l:body = l:has_cb ? l:cb[2] : l:rest + if l:body =~# '^\s*$' + call setline('.', '') + call cursor(line('.'), 1) + return "\" + endif + let l:cont = l:indent . l:marker . ' ' + if l:has_cb + let l:cont .= '[ ] ' + endif + return "\" . l:cont + endif + return "\" +endfunction + " `:VimwikiNormalizeLink` — wrap the word at cursor as `[[word]]` " without following. Pure-VimL. function! nuwiki#commands#normalize_link() abort diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim index d1752b9..74e66ce 100644 --- a/ftplugin/vimwiki.vim +++ b/ftplugin/vimwiki.vim @@ -175,6 +175,7 @@ if !has('nvim') inoremap :call nuwiki#commands#list_cycle_symbol(1) inoremap :call nuwiki#commands#list_cycle_symbol(-1) inoremap :call nuwiki#commands#list_toggle_or_add_checkbox() + inoremap nuwiki#commands#smart_return() " Headers nnoremap = :call nuwiki#commands#heading_add() diff --git a/lua/nuwiki/commands.lua b/lua/nuwiki/commands.lua index 725e047..19db2a6 100644 --- a/lua/nuwiki/commands.lua +++ b/lua/nuwiki/commands.lua @@ -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 `` for insert mode — vimwiki parity for `:VimwikiReturn`. +--- Used as an `` 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 `` 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('') + local esc = tc('') + + -- 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 `` + new_row, cursor sits at end of new row. Bounce to + -- normal, jump to col 0, step right past the leading `|`, re-enter + -- insert: `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) diff --git a/lua/nuwiki/keymaps.lua b/lua/nuwiki/keymaps.lua index 76961be..6fa070a 100644 --- a/lua/nuwiki/keymaps.lua +++ b/lua/nuwiki/keymaps.lua @@ -254,6 +254,11 @@ function M.attach(bufnr, mappings) { desc = 'nuwiki: cycle list symbol backward (insert)' }, bufnr) map('i', '', cmd.list_toggle_or_add_checkbox, { desc = 'nuwiki: toggle/add checkbox (insert)' }, bufnr) + + -- Smart : continue list / table rows. `` so the function + -- result is the keystrokes fed — keeps undo coherent. + map('i', '', cmd.smart_return, + { desc = 'nuwiki: smart return (insert)', expr = true }, bufnr) end -- ===== Header levels + nav ===== diff --git a/scripts/test-keymaps.lua b/scripts/test-keymaps.lua index 4db752b..18e927e 100644 --- a/scripts/test-keymaps.lua +++ b/scripts/test-keymaps.lua @@ -401,6 +401,38 @@ vim.defer_fn(function() end end) + -- ===== Smart (Cluster 5) ===== + + run('cr.continues_list_marker', { + lines = { '- first' }, + cursor = { 1, 7 }, -- end of line + keys = 'Asecond', + expect_lines = { '- first', '- second' }, + wait_ms = 100, + }) + run('cr.continues_checkbox', { + lines = { '- [ ] first' }, + cursor = { 1, 11 }, + keys = 'Asecond', + expect_lines = { '- [ ] first', '- [ ] second' }, + wait_ms = 100, + }) + run('cr.breaks_on_empty_list_line', { + lines = { '- ' }, + cursor = { 1, 2 }, + keys = 'Aplain', + expect_lines = { '', 'plain' }, + wait_ms = 100, + }) + run('cr.adds_new_table_row', { + lines = { '| a | b |' }, + cursor = { 1, 4 }, -- inside `a` + keys = 'Ax', + -- New row inserted below with empty cells; cursor jumps to first. + expect_lines = { '| a | b |', '|x | |' }, + wait_ms = 100, + }) + -- ===== Diary nav ===== -- Tested via the LSP open-* responses; can't fully validate without -- a populated diary, but at least confirm the maps fire without