diff --git a/README.md b/README.md index a38bea7..1b6dafc 100644 --- a/README.md +++ b/README.md @@ -323,8 +323,8 @@ The `w` prefix in the `wiki_prefix`, `links`, and `html_export` rows is |-----|---------|----------| | `enabled` | `true` | master switch for the whole keymap layer | | `wiki_prefix` | `true` | `ww`, `wt`, `ws`, `wi`, `ww`, `wy`, `wt`, `wm`, `wi` | -| `links` | `true` | ``, ``, ``, ``, ``, ``, ``, ``, ``, `+` (normal + visual), `wn`, `wd`, `wr`, `wc` (normal + visual) | -| `lists` | `true` | `` (also ``/``), `gnt`, `gln`, `glp`, `glx`, `glh`, `gll`, `gLh`, `gLl`, `gLH`, `gLL`, `glr`, `gLr`, `gLR`, `gl-`, `gl*`, `gl#`, `gl1`, `gli`, `glI`, `gla`, `glA`, `gL-`, `gL*`, `gL#`, `gL1`, `gLi`, `gLI`, `gLa`, `gLA`, `gl`, `gL`, `o`, `O`; insert: ``, ``, ``, ``, ``, `` | +| `links` | `true` | ``, ``, ``, ``, ``, ``, ``, ``, ``, `+` (normal + visual), `` (visual: normalize selection), `wn`, `wd`, `wr`, `wc` (normal + visual) | +| `lists` | `true` | `` (also ``/``), `gnt`, `gln`, `glp`, `glx`, `glh`, `gll`, `gLh`, `gLl`, `gLH`, `gLL`, `glr`, `gLr`, `gLR`, `gl-`, `gl*`, `gl#`, `gl1`, `gli`, `glI`, `gla`, `glA`, `gL-`, `gL*`, `gL#`, `gL1`, `gLi`, `gLI`, `gLa`, `gLA`, `gl`, `gL`, `o`, `O`; insert: ``, ``, ``, ``, ``, ``, `` (multiline item) | | `headers` | `true` | `=`, `-`, `]]`, `[[`, `]=`, `[=`, `]u`, `[u` | | `table_editing` | `true` | `gqq`, `gq1`, `gww`, `gw1`, ``, ``; insert: ``, `` | | `diary` | `true` | ``, `` | diff --git a/autoload/nuwiki/commands.vim b/autoload/nuwiki/commands.vim index b65b97d..2901f4d 100644 --- a/autoload/nuwiki/commands.vim +++ b/autoload/nuwiki/commands.vim @@ -1310,6 +1310,29 @@ function! nuwiki#commands#smart_return() abort return "\" endfunction +" Insert-mode ``: continue the current list item on a new line WITHOUT a +" new marker, indented to align under the item's text — vimwiki's "multiline +" list item" (`VimwikiReturn 2 2`). Off a list item it's a plain ``. +function! nuwiki#commands#smart_shift_return() abort + let l:line = getline('.') + 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) + return "\" + endif + let l:indent = l:m[1] + let l:marker = l:m[2] + let l:has_cb = l:m[3] =~# '^\[[ xXoO.\-]\]' + let l:pad = len(l:marker) + 1 + (l:has_cb ? 4 : 0) + let l:auto = &autoindent || &smartindent || &cindent || !empty(&indentexpr) + return "\" . (l:auto ? '' : l:indent) . repeat(' ', l:pad) +endfunction + " :VimwikiReturn / :NuwikiReturn — the command form of the smart `` " continuation (upstream's mapping-driven `VimwikiReturn`). Enters insert at " end of line and triggers the buffer-local `` mapping (smart_return), diff --git a/development/tests/test-keymaps-vim.vim b/development/tests/test-keymaps-vim.vim index c9a7c74..f796b02 100644 --- a/development/tests/test-keymaps-vim.vim +++ b/development/tests/test-keymaps-vim.vim @@ -186,6 +186,7 @@ let s:mapping_surface = [ \ ['', 'n'], ['', 'n'], ['', 'n'], ['', 'n'], \ ['', 'n'], ['', 'n'], \ ['', 'n'], ['', 'n'], ['', 'n'], ['+', 'nx'], + \ ['', 'x'], \ ['', 'nx'], ['', 'nx'], ['', 'nx'], \ ['gnt', 'n'], ['gln', 'nx'], ['glp', 'nx'], ['glx', 'nx'], \ ['glh', 'n'], ['gll', 'n'], ['gLh', 'n'], ['gLl', 'n'], @@ -212,7 +213,7 @@ let s:mapping_surface = [ \ ['ah', 'ox'], ['ih', 'ox'], ['aH', 'ox'], ['iH', 'ox'], \ ['al', 'ox'], ['il', 'ox'], ['a\', 'ox'], ['i\', 'ox'], \ ['ac', 'ox'], ['ic', 'ox'], - \ ['', 'i'], ['', 'i'], ['', 'i'], + \ ['', 'i'], ['', 'i'], ['', 'i'], ['', 'i'], \ ['', 'i'], ['', 'i'], \ ['', 'i'], ['', 'i'], ['', 'i'], \ ] @@ -335,6 +336,20 @@ call s:run('cr.adds_new_table_row', { \ 'keys': "A\x\", \ 'expect_lines': ['| a | b |', '|x | |'], \ }) +" Insert : multiline list item — continue with NO marker, aligned under +" the text ("- " → 2 cols; "- [ ] " → 6 cols). +call s:run('cr.shift_cr_multiline', { + \ 'lines': ['- item'], + \ 'cursor': [1, 6], + \ 'keys': "A\x\", + \ 'expect_lines': ['- item', ' x'], + \ }) +call s:run('cr.shift_cr_multiline_checkbox', { + \ 'lines': ['- [ ] item'], + \ 'cursor': [1, 10], + \ 'keys': "A\x\", + \ 'expect_lines': ['- [ ] item', ' x'], + \ }) " ===== Insert-mode smart_tab / smart_shift_tab (Cluster 6) ===== @@ -589,6 +604,15 @@ call s:record( \ 'normalize.visual_wraps_selection', \ 'got ' . string(getline(1))) +" ===== Visual mapping wraps the selection (== visual `+`) ===== +call s:set_buf(['alpha beta gamma']) +call cursor(1, 7) +call feedkeys("viw\", 'tx') +call s:record( + \ getline(1) ==# 'alpha [[beta]] gamma' ? 1 : 0, + \ 'map.visual_cr_wraps_selection', + \ 'got ' . string(getline(1))) + " ===== Follow-link resolves + opens in the current window (regression) ===== " follow_link_or_create() must resolve the definition itself and `:edit` the " target in the CURRENT window — including a page that does not exist yet diff --git a/development/tests/test-keymaps.lua b/development/tests/test-keymaps.lua index 428931f..5e7fd64 100644 --- a/development/tests/test-keymaps.lua +++ b/development/tests/test-keymaps.lua @@ -208,6 +208,7 @@ vim.defer_fn(function() { '', 'n' }, { '', 'n' }, { '', 'n' }, { '', 'n' }, { '', 'n' }, { '', 'n' }, { '', 'n' }, { '', 'n' }, { '', 'n' }, { '+', 'nx' }, + { '', 'x' }, -- visual normalize-link (== visual `+`) -- Lists { '', 'nx' }, { '', 'nx' }, { '', 'nx' }, { 'gnt', 'n' }, { 'gln', 'nx' }, { 'glp', 'nx' }, { 'glx', 'nx' }, @@ -242,7 +243,7 @@ vim.defer_fn(function() { 'al', 'ox' }, { 'il', 'ox' }, { 'a\\', 'ox' }, { 'i\\', 'ox' }, { 'ac', 'ox' }, { 'ic', 'ox' }, -- Insert-mode - { '', 'i' }, { '', 'i' }, { '', 'i' }, + { '', 'i' }, { '', 'i' }, { '', 'i' }, { '', 'i' }, { '', 'i' }, { '', 'i' }, { '', 'i' }, { '', 'i' }, { '', 'i' }, } @@ -1009,6 +1010,41 @@ vim.defer_fn(function() end end) + -- Visual mapping wraps the selection (upstream parity with visual `+`). + -- Select via `normal!` (sets the '<,'> marks reliably), then `gv` reselects + -- and the fed fires the x-mode mapping. + tobj_case('map.visual_cr_wraps_selection', function() + set_buf({ 'alpha beta gamma' }) + vim.cmd('normal! 0wviw\27') -- select 'beta', leave visual → marks set + send('gv') + local got = vim.api.nvim_get_current_line() + if got ~= 'alpha [[beta]] gamma' then + error('visual did not wrap selection: ' .. got) + end + end) + + -- Insert continues a list item with no marker, aligned under the text. + -- Test the `` handler directly — `` doesn't round-trip through + -- headless feedkeys, but the handler is what the mapping invokes. + tobj_case('map.insert_shift_cr_multiline', function() + local cr = vim.api.nvim_replace_termcodes('', true, false, true) + set_buf({ '- item' }) -- "- " is 2 cols → continuation aligns at column 2 + local got = require('nuwiki.commands').smart_shift_return() + if got ~= cr .. ' ' then + error('S-CR (bullet) wrong: ' .. vim.inspect(got)) + end + set_buf({ '- [ ] item' }) -- "- [ ] " is 6 cols + got = require('nuwiki.commands').smart_shift_return() + if got ~= cr .. ' ' then + error('S-CR (checkbox) wrong: ' .. vim.inspect(got)) + end + set_buf({ 'plain text' }) -- not a list → plain + got = require('nuwiki.commands').smart_shift_return() + if got ~= cr then + error('S-CR (non-list) should be plain CR: ' .. vim.inspect(got)) + end + end) + -- ===== Configurable map_prefix (vimwiki g:vimwiki_map_prefix) ===== -- Overriding map_prefix must relocate the whole * family. We attach -- to a throwaway scratch buffer with a custom prefix and assert the new diff --git a/development/vimwiki-gap.md b/development/vimwiki-gap.md index 7a38c4b..2710e33 100644 --- a/development/vimwiki-gap.md +++ b/development/vimwiki-gap.md @@ -569,8 +569,20 @@ fix site. - [ ] `list_ignore_newline` / `text_ignore_newline` — export newline handling. ### Mappings -- [ ] Visual `` normalize-link (visual `+` covers the same intent). -- [ ] Insert `` multiline list item. +- [x] **Visual `` normalize-link** _(fixed 2026-06-03)_ — upstream binds + visual `` to `VimwikiNormalizeLinkVisualCR` → `:VimwikiNormalizeLink 1`, + identical to visual `+`. nuwiki had only visual `+`. _Fix:_ added `x ` → + the same wrap-selection handler (`normalize_link(true)`) in both clients + (`lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`). Covered by + `map.visual_cr_wraps_selection` in both harnesses. +- [x] **Insert `` multiline list item** _(fixed 2026-06-03)_ — upstream's + insert `` (`VimwikiReturn 2 2` → `kbd_cr` with no new marker) continues + the current list item on a new line indented to align under the item's text. + _Fix:_ new `smart_shift_return` (both clients, `` insert mapping): on a + list item returns `` + (indent +) `marker-width + 1 (+4 for a checkbox)` + spaces — no marker; off a list item, a plain ``. Mirrors `smart_return`'s + auto-indent handling. Covered by `map.insert_shift_cr_multiline` + (`test-keymaps.lua`) + `cr.shift_cr_multiline{,_checkbox}` (`test-keymaps-vim.vim`). - [x] **`gLH` / `gLL` / `gLR`** _(fixed 2026-06-03)_ — upstream binds these as case-variant **aliases** of `gLh` / `gLl` / `gLr` (dedent / indent whole item, renumber all lists; upstream `ftplugin/vimwiki.vim:553,555,561`). nuwiki had @@ -713,3 +725,8 @@ audits don't re-flag them. `use_calendar` opt-out (`g:nuwiki_no_calendar` / setup `use_calendar = false`). So `use_calendar` is no longer an intentional divergence — removed from that list above. See `development/calendar-vim-integration.md`. +- Cleared the **entire P3 Mappings section** (2026-06-03): visual `` → + normalize-link (same handler as visual `+`), and insert `` → multiline + list item (`smart_shift_return`, continuation with no new marker aligned under + the text). Both client-side, both clients, tested in both harnesses (Neovim + 305, Vim 299/18/21). Only P3 Config items now remain open. diff --git a/doc/nuwiki.txt b/doc/nuwiki.txt index cee5302..b83e123 100644 --- a/doc/nuwiki.txt +++ b/doc/nuwiki.txt @@ -275,6 +275,7 @@ Vim-specific globals ~ `wm`, `wi` `links` ``, ``, ``, ``, ``, ``, ``, ``, ``, `+` (n/x), + `` (x: normalize selection), `wn`, `wd`, `wr`, `wc` (n/x) `lists` `` (also ``/``), `gnt`, `gln`, @@ -283,7 +284,7 @@ Vim-specific globals ~ `gla`, `glA`, `gL-`, `gL*`, `gL#`, `gL1`, `gLi`, `gLI`, `gLa`, `gLA`, `gl`, `gL`, `o`, `O`; insert ``, ``, ``, ``, - ``, `` + ``, ``, `` `headers` `=`, `-`, `]]`, `[[`, `]=`, `[=`, `]u`, `[u` `table_editing` `gqq`, `gq1`, `gww`, `gw1`, ``, ``; insert ``, `` @@ -747,6 +748,10 @@ on context: same column count; cursor lands inside the first cell. * Otherwise — plain ``. +Insert `` continues a list item as a *multiline* item: a new line +with no marker, indented to align under the item's text (or a plain +`` when not on a list item). Mirrors vimwiki's ``. + Table cell navigation ~ Jump to the next cell. Past the last cell, insert a fresh diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim index 570168e..066d6a3 100644 --- a/ftplugin/vimwiki.vim +++ b/ftplugin/vimwiki.vim @@ -260,6 +260,8 @@ if !has('nvim') nnoremap ?\[\[:nohlsearch nnoremap + :call nuwiki#commands#normalize_link() xnoremap + :call nuwiki#commands#normalize_link(1) + " Upstream binds visual to the same normalize-link as visual `+`. + xnoremap :call nuwiki#commands#normalize_link(1) exe 'nnoremap ' . s:prefix . 'n :call nuwiki#commands#wiki_goto_page('''')' exe 'nnoremap ' . s:prefix . 'd :call nuwiki#commands#delete_file()' exe 'nnoremap ' . s:prefix . 'r :call nuwiki#commands#rename_file()' @@ -332,6 +334,7 @@ if !has('nvim') inoremap :call nuwiki#commands#list_cycle_symbol(-1) inoremap :call nuwiki#commands#list_toggle_or_add_checkbox() inoremap nuwiki#commands#smart_return() + inoremap nuwiki#commands#smart_shift_return() endif " Headers diff --git a/lua/nuwiki/commands.lua b/lua/nuwiki/commands.lua index 1e3fc6d..0e06249 100644 --- a/lua/nuwiki/commands.lua +++ b/lua/nuwiki/commands.lua @@ -1000,6 +1000,31 @@ function M.smart_return() return cr end +-- Insert-mode ``: continue the current list item on a new line WITHOUT a +-- new marker, indented to align under the item's text — vimwiki's "multiline +-- list item" (`VimwikiReturn 2 2`). Off a list item it's a plain ``. +function M.smart_shift_return() + local line = vim.api.nvim_get_current_line() + local cr = vim.api.nvim_replace_termcodes('', true, false, true) + 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 not marker then + return cr + end + -- Align under the text: marker width + 1 space (+ 4 for a `[ ] ` checkbox). + local has_cb = after:match('^%[[%sxXoO%.%-]%]') ~= nil + local pad = #marker + 1 + (has_cb and 4 or 0) + -- Mirror smart_return's auto-indent handling: when an auto-indent mechanism + -- is active, Vim already re-inserts the line's indent after ``. + local auto = vim.bo.autoindent or vim.bo.smartindent or vim.bo.cindent + or (vim.bo.indentexpr ~= nil and vim.bo.indentexpr ~= '') + return cr .. (auto and '' or indent) .. string.rep(' ', pad) +end + -- :VimwikiReturn / :NuwikiReturn — the command form of the smart `` -- continuation (upstream's mapping-driven VimwikiReturn). Enters insert at end -- of line and triggers the buffer-local `` mapping (smart_return). Args diff --git a/lua/nuwiki/keymaps.lua b/lua/nuwiki/keymaps.lua index 422bc35..1ca7d7f 100644 --- a/lua/nuwiki/keymaps.lua +++ b/lua/nuwiki/keymaps.lua @@ -181,6 +181,9 @@ function M.attach(bufnr, mappings) map('n', '+', cmd.normalize_link, { desc = 'nuwiki: wrap word as wikilink' }, bufnr) map('x', '+', function() cmd.normalize_link(true) end, { desc = 'nuwiki: wrap selection as wikilink' }, bufnr) + -- Upstream binds visual to the same normalize-link as visual `+`. + map('x', '', function() cmd.normalize_link(true) end, + { desc = 'nuwiki: wrap selection as wikilink' }, bufnr) map('n', p .. 'n', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr) map('n', p .. 'd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr) map('n', p .. 'r', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr) @@ -277,6 +280,9 @@ function M.attach(bufnr, mappings) -- result is the keystrokes fed — keeps undo coherent. map('i', '', cmd.smart_return, { desc = 'nuwiki: smart return (insert)', expr = true }, bufnr) + -- Smart : multiline list item (continuation with no new marker). + map('i', '', cmd.smart_shift_return, + { desc = 'nuwiki: multiline list item (insert)', expr = true }, bufnr) end -- ===== Insert-mode table navigation (Cluster 6) =====