7f6d811a47
The picker fetched the wiki list via workspace/executeCommand, but the LSP only starts once a vimwiki buffer exists — so a wiki could not be selected until one was already open (chicken-and-egg). Read the list straight from config instead (g:nuwiki_wikis / scalar fallback, the same source as open_wiki_path) and open the chosen index directly; that auto-starts the server via the FileType autocmd. - Vim: new public nuwiki#commands#wiki_list(); wiki_ui_select() uses it + inputlist() + :edit. Global :VimwikiUISelect / :NuwikiUISelect commands. - Neovim: config.wiki_cfg(n) + config.wiki_list() (init.lua delegates); commands.wiki_ui_select() uses them + vim.ui.select + :edit; global VimwikiUISelect / NuwikiUISelect user commands registered in setup(). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1126 lines
35 KiB
VimL
1126 lines
35 KiB
VimL
" autoload/nuwiki/commands.vim — Vim-side wrappers around the LSP
|
|
" commands the server advertises (Phase 19).
|
|
"
|
|
" Lives in autoload/ so the cost is paid only when a `:Vimwiki*` /
|
|
" `:Nuwiki*` command is actually invoked. Dispatches via vim-lsp's
|
|
" `lsp#send_request`. coc.nvim's command surface is rich enough that
|
|
" users should just call `:CocCommand nuwiki.x` directly there — this
|
|
" file targets the vim-lsp path.
|
|
|
|
let s:server = 'nuwiki'
|
|
|
|
function! s:buf_uri() abort
|
|
return 'file://' . fnamemodify(expand('%:p'), ':p')
|
|
endfunction
|
|
|
|
function! s:cursor_position() abort
|
|
return {
|
|
\ 'textDocument': { 'uri': s:buf_uri() },
|
|
\ 'position': { 'line': line('.') - 1, 'character': col('.') - 1 },
|
|
\ }
|
|
endfunction
|
|
|
|
function! s:has_vimlsp() abort
|
|
return exists('*lsp#send_request')
|
|
endfunction
|
|
|
|
function! s:has_coc() abort
|
|
return exists('*CocActionAsync')
|
|
endfunction
|
|
|
|
" Adapt a coc.nvim (err, result) callback into the vim-lsp notification
|
|
" shape that existing on_notification handlers expect.
|
|
function! s:coc_wrap(upstream, err, result) abort
|
|
if a:err isnot v:null && a:err !=# 0
|
|
echohl ErrorMsg
|
|
echom 'nuwiki: ' . (type(a:err) == type('') ? a:err : string(a:err))
|
|
echohl None
|
|
return
|
|
endif
|
|
call a:upstream({ 'response': { 'result': a:result } })
|
|
endfunction
|
|
|
|
function! s:exec(command, arguments, ...) abort
|
|
if s:has_vimlsp()
|
|
let l:opts = {
|
|
\ 'method': 'workspace/executeCommand',
|
|
\ 'params': { 'command': a:command, 'arguments': a:arguments },
|
|
\ }
|
|
if a:0 >= 1
|
|
let l:opts['on_notification'] = a:1
|
|
endif
|
|
call lsp#send_request(s:server, l:opts)
|
|
return
|
|
endif
|
|
|
|
if s:has_coc()
|
|
" CocActionAsync('runCommand', cmdid, arg1, arg2, …, callback)
|
|
" The arguments list is spread as individual positional args.
|
|
let l:args = ['runCommand', a:command] + a:arguments
|
|
if a:0 >= 1
|
|
call add(l:args, function('s:coc_wrap', [a:1]))
|
|
endif
|
|
call call('CocActionAsync', l:args)
|
|
return
|
|
endif
|
|
|
|
echohl ErrorMsg
|
|
echom 'nuwiki: no supported LSP client. Install vim-lsp or configure coc.nvim.'
|
|
echohl None
|
|
endfunction
|
|
|
|
" Shared jump-to-definition helper used by follow_link* and the ftplugin.
|
|
function! s:jump_definition() abort
|
|
if exists(':LspDefinition') == 2
|
|
LspDefinition
|
|
elseif s:has_coc()
|
|
call CocActionAsync('jumpDefinition')
|
|
else
|
|
echohl WarningMsg
|
|
echom 'nuwiki: no LSP client available for jump-to-definition'
|
|
echohl None
|
|
endif
|
|
endfunction
|
|
|
|
" Shared references helper used by the ftplugin.
|
|
function! nuwiki#commands#backlinks() abort
|
|
if exists(':LspReferences') == 2
|
|
LspReferences
|
|
elseif s:has_coc()
|
|
call CocActionAsync('references')
|
|
else
|
|
echohl WarningMsg
|
|
echom 'nuwiki: no LSP client available for references'
|
|
echohl None
|
|
endif
|
|
endfunction
|
|
|
|
function! s:open_uri_from(response, ...) abort
|
|
let l:result = get(get(a:response, 'response', {}), 'result', {})
|
|
if type(l:result) != type({}) || !has_key(l:result, 'uri')
|
|
return
|
|
endif
|
|
let l:path = substitute(l:result['uri'], '^file://', '', '')
|
|
let l:cmd = a:0 >= 1 ? a:1 : 'edit'
|
|
execute l:cmd . ' ' . fnameescape(l:path)
|
|
endfunction
|
|
|
|
function! s:notify_deferred(name) abort
|
|
echohl WarningMsg
|
|
echom 'nuwiki: ' . a:name . ' is not yet implemented — see SPEC §13.1'
|
|
echohl None
|
|
endfunction
|
|
|
|
" ===== Wiki picker =====
|
|
|
|
function! nuwiki#commands#wiki_index(count) abort
|
|
let l:args = a:count > 0 ? [{ 'wiki': a:count - 1 }] : [{}]
|
|
call s:exec('nuwiki.wiki.openIndex', l:args,
|
|
\ function('s:open_uri_from'))
|
|
endfunction
|
|
|
|
" `:VimwikiUISelect` — pick a wiki from the configured list and open its
|
|
" index. The list is read straight from config (see wiki_list below), not
|
|
" from the server, so the picker works from any buffer before a wiki file is
|
|
" ever opened — opening the chosen index then auto-starts the LSP via the
|
|
" FileType autocmd. Plain Vim has no `vim.ui.select`, so use `inputlist()`.
|
|
function! nuwiki#commands#wiki_ui_select() abort
|
|
let l:wikis = nuwiki#commands#wiki_list()
|
|
if empty(l:wikis)
|
|
echohl WarningMsg | echom 'nuwiki: no wikis configured' | echohl None
|
|
return
|
|
endif
|
|
let l:prompt = ['Select a wiki:']
|
|
let l:i = 0
|
|
for l:w in l:wikis
|
|
let l:i += 1
|
|
call add(l:prompt, printf('%d. %s (%s)', l:i, l:w.name, l:w.root))
|
|
endfor
|
|
let l:choice = inputlist(l:prompt)
|
|
if l:choice < 1 || l:choice > len(l:wikis)
|
|
return
|
|
endif
|
|
let l:w = l:wikis[l:choice - 1]
|
|
execute 'edit ' . fnameescape(l:w.root . '/' . l:w.idx . l:w.ext)
|
|
endfunction
|
|
|
|
" `:VimwikiNextLink` / `:VimwikiPrevLink` — pure-VimL `[[…]]` jumper.
|
|
function! nuwiki#commands#link_next() abort
|
|
call search('\[\[', 'W')
|
|
endfunction
|
|
function! nuwiki#commands#link_prev() abort
|
|
call search('\[\[', 'bW')
|
|
endfunction
|
|
|
|
" `:VimwikiBaddLink` — add the link target file to the buffer list
|
|
" without focus. Routes through vim-lsp's textDocument/definition.
|
|
function! nuwiki#commands#badd_link() abort
|
|
if s:has_vimlsp()
|
|
call lsp#send_request(s:server, {
|
|
\ 'method': 'textDocument/definition',
|
|
\ 'params': s:cursor_position(),
|
|
\ 'on_notification': function('s:badd_from_response'),
|
|
\ })
|
|
return
|
|
endif
|
|
if s:has_coc()
|
|
let l:locs = CocAction('getDefinition')
|
|
if type(l:locs) == type([]) && !empty(l:locs)
|
|
let l:uri = get(l:locs[0], 'uri', get(l:locs[0], 'targetUri', ''))
|
|
if !empty(l:uri)
|
|
let l:path = substitute(l:uri, '^file://', '', '')
|
|
execute 'badd ' . fnameescape(l:path)
|
|
echom 'nuwiki: added ' . l:path
|
|
endif
|
|
endif
|
|
return
|
|
endif
|
|
echohl ErrorMsg | echom 'nuwiki: no supported LSP client for badd_link' | echohl None
|
|
endfunction
|
|
|
|
function! s:badd_from_response(notification) abort
|
|
let l:result = get(get(a:notification, 'response', {}), 'result', {})
|
|
let l:uri = ''
|
|
if type(l:result) == type({})
|
|
let l:uri = get(l:result, 'uri', get(l:result, 'targetUri', ''))
|
|
elseif type(l:result) == type([]) && !empty(l:result)
|
|
let l:uri = get(l:result[0], 'uri', get(l:result[0], 'targetUri', ''))
|
|
endif
|
|
if l:uri ==# ''
|
|
return
|
|
endif
|
|
let l:path = substitute(l:uri, '^file://', '', '')
|
|
execute 'badd ' . fnameescape(l:path)
|
|
echom 'nuwiki: added ' . l:path
|
|
endfunction
|
|
|
|
" ===== Smart <CR>: follow or wrap-then-follow =====
|
|
|
|
function! s:cursor_inside_wikilink() abort
|
|
let l:line = getline('.')
|
|
let l:col = col('.')
|
|
let l:i = 1
|
|
let l:open = 0
|
|
while l:i < strlen(l:line)
|
|
if strpart(l:line, l:i - 1, 2) ==# '[['
|
|
let l:open = l:i
|
|
let l:i += 2
|
|
elseif strpart(l:line, l:i - 1, 2) ==# ']]'
|
|
let l:open = 0
|
|
let l:i += 2
|
|
else
|
|
let l:i += 1
|
|
endif
|
|
if l:i > l:col
|
|
break
|
|
endif
|
|
endwhile
|
|
return l:open > 0
|
|
endfunction
|
|
|
|
function! s:wrap_cword_as_wikilink() abort
|
|
let l:word = expand('<cword>')
|
|
if l:word ==# ''
|
|
return 0
|
|
endif
|
|
let l:line = getline('.')
|
|
let l:col = col('.')
|
|
" Find word boundaries around cursor (1-based).
|
|
let l:left = l:col
|
|
while l:left > 1 && strpart(l:line, l:left - 2, 1) =~# '[A-Za-z0-9_-]'
|
|
let l:left -= 1
|
|
endwhile
|
|
let l:right = l:col
|
|
while l:right <= strlen(l:line) && strpart(l:line, l:right - 1, 1) =~# '[A-Za-z0-9_-]'
|
|
let l:right += 1
|
|
endwhile
|
|
let l:word_real = strpart(l:line, l:left - 1, l:right - l:left)
|
|
if l:word_real ==# ''
|
|
return 0
|
|
endif
|
|
let l:new_line = strpart(l:line, 0, l:left - 1) . '[[' . l:word_real . ']]' . strpart(l:line, l:right - 1)
|
|
call setline('.', l:new_line)
|
|
call cursor(line('.'), l:left + 2)
|
|
return 1
|
|
endfunction
|
|
|
|
" Two-step behaviour matching vimwiki:
|
|
" 1. First press on a bare word → wrap it as `[[word]]` and stop.
|
|
" 2. Cursor is now inside `[[…]]`; next press follows the link.
|
|
" 3. Press inside an existing `[[…]]` follows immediately.
|
|
function! nuwiki#commands#follow_link_or_create() abort
|
|
if s:cursor_inside_wikilink()
|
|
call s:jump_definition()
|
|
return
|
|
endif
|
|
if s:wrap_cword_as_wikilink()
|
|
" Wrapped — wait for the user's second <CR> to follow.
|
|
return
|
|
endif
|
|
" Nothing to wrap — fall through to plain definition.
|
|
call s:jump_definition()
|
|
endfunction
|
|
|
|
" Pure-VimL bullet-continuation for `o` / `O` — mirrors the Lua side
|
|
" so re-pressing the key keeps the list marker (and checkbox if any).
|
|
function! nuwiki#commands#open_below_with_bullet() abort
|
|
let l:line = getline('.')
|
|
let l:match = matchlist(l:line, '^\(\s*\)\([-*#]\)\s')
|
|
if empty(l:match)
|
|
call feedkeys('o', 'n')
|
|
return
|
|
endif
|
|
let l:prefix = l:match[1] . l:match[2] . ' '
|
|
if l:line =~# '^\s*[-*#]\s\+\[[ xXoO.\-]\]'
|
|
let l:prefix .= '[ ] '
|
|
endif
|
|
call feedkeys('o' . l:prefix, 'n')
|
|
endfunction
|
|
|
|
function! nuwiki#commands#open_above_with_bullet() abort
|
|
let l:line = getline('.')
|
|
let l:match = matchlist(l:line, '^\(\s*\)\([-*#]\)\s')
|
|
if empty(l:match)
|
|
call feedkeys('O', 'n')
|
|
return
|
|
endif
|
|
let l:prefix = l:match[1] . l:match[2] . ' '
|
|
call feedkeys('O' . l:prefix, 'n')
|
|
endfunction
|
|
|
|
" ===== Heading navigation (pure VimL) =====
|
|
|
|
function! s:heading_level(line) abort
|
|
let l:lead = matchstr(a:line, '^\s*\zs=\+\ze\s')
|
|
if l:lead ==# ''
|
|
return 0
|
|
endif
|
|
let l:trail = matchstr(a:line, '\s\zs=\+\ze\s*$')
|
|
if len(l:trail) != len(l:lead)
|
|
return 0
|
|
endif
|
|
return len(l:lead)
|
|
endfunction
|
|
|
|
function! s:current_heading_level() abort
|
|
let l:lnum = line('.')
|
|
while l:lnum > 0
|
|
let l:lvl = s:heading_level(getline(l:lnum))
|
|
if l:lvl > 0
|
|
return [l:lvl, l:lnum]
|
|
endif
|
|
let l:lnum -= 1
|
|
endwhile
|
|
return [0, 0]
|
|
endfunction
|
|
|
|
function! s:jump_heading(direction, predicate) abort
|
|
let l:total = line('$')
|
|
let l:i = line('.') + a:direction
|
|
while l:i >= 1 && l:i <= l:total
|
|
let l:lvl = s:heading_level(getline(l:i))
|
|
if l:lvl > 0 && a:predicate(l:lvl, l:i)
|
|
call cursor(l:i, 1)
|
|
return
|
|
endif
|
|
let l:i += a:direction
|
|
endwhile
|
|
endfunction
|
|
|
|
function! nuwiki#commands#next_header() abort
|
|
call s:jump_heading(1, {l, n -> 1})
|
|
endfunction
|
|
function! nuwiki#commands#prev_header() abort
|
|
call s:jump_heading(-1, {l, n -> 1})
|
|
endfunction
|
|
function! nuwiki#commands#next_sibling_header() abort
|
|
let [l:lvl, _] = s:current_heading_level()
|
|
call s:jump_heading(1, {l, n -> l <= l:lvl})
|
|
endfunction
|
|
function! nuwiki#commands#prev_sibling_header() abort
|
|
let [l:lvl, _] = s:current_heading_level()
|
|
call s:jump_heading(-1, {l, n -> l <= l:lvl})
|
|
endfunction
|
|
function! nuwiki#commands#parent_header() abort
|
|
let [l:lvl, _] = s:current_heading_level()
|
|
if l:lvl <= 1
|
|
return
|
|
endif
|
|
call s:jump_heading(-1, {l, n -> l < l:lvl})
|
|
endfunction
|
|
|
|
function! nuwiki#commands#wiki_tab_index(count) abort
|
|
let l:args = a:count > 0 ? [{ 'wiki': a:count - 1 }] : [{}]
|
|
call s:exec('nuwiki.wiki.tabOpenIndex', l:args,
|
|
\ {n -> s:open_uri_from(n, 'tabedit')})
|
|
endfunction
|
|
|
|
function! nuwiki#commands#wiki_goto_page(name) abort
|
|
let l:name = a:name
|
|
if l:name ==# ''
|
|
let l:name = input('Goto page: ')
|
|
endif
|
|
if l:name ==# ''
|
|
return
|
|
endif
|
|
call s:exec('nuwiki.wiki.gotoPage', [{ 'page': l:name }],
|
|
\ function('s:open_uri_from'))
|
|
endfunction
|
|
|
|
" ===== Diary =====
|
|
|
|
function! s:diary_open(cmd_name) abort
|
|
call s:exec(a:cmd_name, [{ 'uri': s:buf_uri() }],
|
|
\ function('s:open_uri_from'))
|
|
endfunction
|
|
|
|
function! nuwiki#commands#diary_today() abort
|
|
call s:diary_open('nuwiki.diary.openToday')
|
|
endfunction
|
|
function! nuwiki#commands#diary_yesterday() abort
|
|
call s:diary_open('nuwiki.diary.openYesterday')
|
|
endfunction
|
|
function! nuwiki#commands#diary_tomorrow() abort
|
|
call s:diary_open('nuwiki.diary.openTomorrow')
|
|
endfunction
|
|
function! nuwiki#commands#diary_index() abort
|
|
call s:diary_open('nuwiki.diary.openIndex')
|
|
endfunction
|
|
function! nuwiki#commands#diary_next() abort
|
|
call s:diary_open('nuwiki.diary.next')
|
|
endfunction
|
|
function! nuwiki#commands#diary_prev() abort
|
|
call s:diary_open('nuwiki.diary.prev')
|
|
endfunction
|
|
function! nuwiki#commands#diary_generate_index() abort
|
|
call s:exec('nuwiki.diary.generateIndex', [{ 'uri': s:buf_uri() }])
|
|
endfunction
|
|
|
|
" ===== Global entry-point helpers (no LSP required) =====
|
|
"
|
|
" These are used by the global mappings in plugin/nuwiki.vim so users can
|
|
" open their wiki from any buffer. Files are opened directly from config;
|
|
" the LSP auto-starts via the FileType autocmd once the buffer loads.
|
|
|
|
" Return a dict with {root, ext, idx, diary_rel, diary_idx} for wiki #n
|
|
" (0-based). Prefers g:nuwiki_wikis[n]; falls back to scalar g:nuwiki_*
|
|
" vars and finally to built-in defaults.
|
|
function! s:wiki_cfg(n) abort
|
|
let l:w = {}
|
|
if exists('g:nuwiki_wikis') && len(g:nuwiki_wikis) > a:n
|
|
let l:w = g:nuwiki_wikis[a:n]
|
|
endif
|
|
let l:root = expand(get(l:w, 'root',
|
|
\ get(g:, 'nuwiki_wiki_root', '~/vimwiki')))
|
|
let l:ext = get(l:w, 'file_extension',
|
|
\ get(g:, 'nuwiki_file_extension', '.wiki'))
|
|
if l:ext[0] !=# '.' | let l:ext = '.' . l:ext | endif
|
|
let l:idx = get(l:w, 'index', 'index')
|
|
let l:diary_rel = get(l:w, 'diary_rel_path',
|
|
\ get(g:, 'nuwiki_diary_rel_path', 'diary'))
|
|
let l:diary_idx = get(l:w, 'diary_index', 'diary')
|
|
return { 'root': l:root, 'ext': l:ext, 'idx': l:idx,
|
|
\ 'diary_rel': l:diary_rel, 'diary_idx': l:diary_idx }
|
|
endfunction
|
|
|
|
" Build the list of configured wikis as {name, root, ext, idx, diary_rel,
|
|
" diary_idx} dicts, straight from config — no LSP round-trip. Drives the wiki
|
|
" picker so it works before any wiki buffer (and therefore the server) exists.
|
|
" Falls back to a single entry from the scalar `g:nuwiki_wiki_root` config.
|
|
function! nuwiki#commands#wiki_list() abort
|
|
let l:list = []
|
|
if exists('g:nuwiki_wikis') && !empty(g:nuwiki_wikis)
|
|
let l:n = 0
|
|
for l:w in g:nuwiki_wikis
|
|
let l:c = s:wiki_cfg(l:n)
|
|
let l:c['name'] = get(l:w, 'name', fnamemodify(l:c.root, ':t'))
|
|
call add(l:list, l:c)
|
|
let l:n += 1
|
|
endfor
|
|
else
|
|
let l:c = s:wiki_cfg(0)
|
|
let l:c['name'] = fnamemodify(l:c.root, ':t')
|
|
call add(l:list, l:c)
|
|
endif
|
|
return l:list
|
|
endfunction
|
|
|
|
function! nuwiki#commands#open_wiki_path(tab) abort
|
|
let l:c = s:wiki_cfg(0)
|
|
execute (a:tab ? 'tabedit' : 'edit') . ' '
|
|
\ . fnameescape(l:c.root . '/' . l:c.idx . l:c.ext)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#open_diary_path(day_offset) abort
|
|
let l:c = s:wiki_cfg(0)
|
|
let l:date = strftime('%Y-%m-%d', localtime() + a:day_offset * 86400)
|
|
execute 'edit ' . fnameescape(l:c.root . '/' . l:c.diary_rel
|
|
\ . '/' . l:date . l:c.ext)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#open_diary_index_path() abort
|
|
let l:c = s:wiki_cfg(0)
|
|
execute 'edit ' . fnameescape(l:c.root . '/' . l:c.diary_rel
|
|
\ . '/' . l:c.diary_idx . l:c.ext)
|
|
endfunction
|
|
|
|
" ===== List + heading editing =====
|
|
|
|
function! s:exec_pos(cmd) abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
|
call s:exec(a:cmd, l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#toggle_list_item() abort
|
|
call s:exec_pos('nuwiki.list.toggleCheckbox')
|
|
endfunction
|
|
function! nuwiki#commands#cycle_list_item() abort
|
|
call s:exec_pos('nuwiki.list.cycleCheckbox')
|
|
endfunction
|
|
function! nuwiki#commands#reject_list_item() abort
|
|
call s:exec_pos('nuwiki.list.rejectCheckbox')
|
|
endfunction
|
|
function! nuwiki#commands#heading_add() abort
|
|
call s:exec_pos('nuwiki.heading.addLevel')
|
|
endfunction
|
|
function! nuwiki#commands#heading_remove() abort
|
|
call s:exec_pos('nuwiki.heading.removeLevel')
|
|
endfunction
|
|
function! nuwiki#commands#next_task() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
|
call s:exec('nuwiki.list.nextTask', l:args,
|
|
\ {n -> s:goto_location(n)})
|
|
endfunction
|
|
|
|
function! s:goto_location(notification) abort
|
|
let l:loc = get(get(a:notification, 'response', {}), 'result', {})
|
|
if type(l:loc) != type({}) || !has_key(l:loc, 'range')
|
|
return
|
|
endif
|
|
let l:line = l:loc['range']['start']['line'] + 1
|
|
let l:col = l:loc['range']['start']['character'] + 1
|
|
call cursor(l:line, l:col)
|
|
endfunction
|
|
|
|
" ===== Generation + workspace =====
|
|
|
|
function! nuwiki#commands#toc_generate() abort
|
|
call s:exec('nuwiki.toc.generate', [{'uri': s:buf_uri()}])
|
|
endfunction
|
|
function! nuwiki#commands#links_generate() abort
|
|
call s:exec('nuwiki.links.generate', [{'uri': s:buf_uri()}])
|
|
endfunction
|
|
function! nuwiki#commands#check_links() abort
|
|
call s:exec('nuwiki.workspace.checkLinks', [{ 'uri': s:buf_uri() }],
|
|
\ {n -> s:results_to_qf(n, 'broken links')})
|
|
endfunction
|
|
|
|
function! nuwiki#commands#find_orphans() abort
|
|
call s:exec('nuwiki.workspace.findOrphans', [{ 'uri': s:buf_uri() }],
|
|
\ {n -> s:results_to_qf(n, 'orphans')})
|
|
endfunction
|
|
|
|
function! s:results_to_qf(notification, title) abort
|
|
let l:rows = get(get(a:notification, 'response', {}), 'result', [])
|
|
if type(l:rows) != type([]) || empty(l:rows)
|
|
echom 'nuwiki: no ' . a:title
|
|
return
|
|
endif
|
|
let l:items = []
|
|
for l:row in l:rows
|
|
let l:path = substitute(get(l:row, 'uri', ''), '^file://', '', '')
|
|
let l:lnum = 1
|
|
let l:col = 1
|
|
if has_key(l:row, 'range')
|
|
let l:lnum = l:row['range']['start']['line'] + 1
|
|
let l:col = l:row['range']['start']['character'] + 1
|
|
endif
|
|
let l:text = get(l:row, 'message', get(l:row, 'name', ''))
|
|
call add(l:items, { 'filename': l:path, 'lnum': l:lnum, 'col': l:col, 'text': l:text })
|
|
endfor
|
|
call setqflist([], ' ', { 'title': 'nuwiki ' . a:title, 'items': l:items })
|
|
copen
|
|
endfunction
|
|
|
|
" ===== Tags =====
|
|
|
|
function! nuwiki#commands#tags_search(query) abort
|
|
let l:q = a:query
|
|
if l:q ==# ''
|
|
let l:q = input('Search tags: ')
|
|
endif
|
|
call s:exec('nuwiki.tags.search', [{ 'uri': s:buf_uri(), 'query': l:q }],
|
|
\ {n -> s:results_to_qf(n, 'tag matches')})
|
|
endfunction
|
|
|
|
function! nuwiki#commands#tags_generate_links(tag) abort
|
|
let l:args = { 'uri': s:buf_uri() }
|
|
if a:tag !=# ''
|
|
let l:args['tag'] = a:tag
|
|
endif
|
|
call s:exec('nuwiki.tags.generateLinks', [l:args])
|
|
endfunction
|
|
|
|
function! nuwiki#commands#tags_rebuild() abort
|
|
call s:exec('nuwiki.tags.rebuild', [{ 'uri': s:buf_uri() }])
|
|
endfunction
|
|
|
|
" ===== HTML export =====
|
|
|
|
function! nuwiki#commands#export_current() abort
|
|
call s:exec('nuwiki.export.currentToHtml', [{'uri': s:buf_uri()}])
|
|
endfunction
|
|
function! nuwiki#commands#export_all() abort
|
|
call s:exec('nuwiki.export.allToHtml', [{'uri': s:buf_uri()}])
|
|
endfunction
|
|
function! nuwiki#commands#export_all_force() abort
|
|
call s:exec('nuwiki.export.allToHtmlForce', [{'uri': s:buf_uri()}])
|
|
endfunction
|
|
function! nuwiki#commands#export_rss() abort
|
|
call s:exec('nuwiki.export.rss', [{'uri': s:buf_uri()}])
|
|
endfunction
|
|
function! nuwiki#commands#export_browse() abort
|
|
call s:exec('nuwiki.export.browse', [{ 'uri': s:buf_uri() }],
|
|
\ {n -> s:open_browser(n)})
|
|
endfunction
|
|
|
|
function! s:open_browser(notification) abort
|
|
let l:result = get(get(a:notification, 'response', {}), 'result', {})
|
|
let l:url = get(l:result, 'browse', '')
|
|
if l:url ==# ''
|
|
return
|
|
endif
|
|
if has('mac')
|
|
silent execute '!open ' . shellescape(l:url, 1) . ' &'
|
|
elseif has('unix')
|
|
silent execute '!xdg-open ' . shellescape(l:url, 1) . ' &'
|
|
else
|
|
echom 'nuwiki: exported → ' . l:url
|
|
endif
|
|
endfunction
|
|
|
|
" ===== File ops =====
|
|
|
|
function! nuwiki#commands#delete_file() abort
|
|
if input('Delete current page? [y/N] ') !~? '^y'
|
|
return
|
|
endif
|
|
call s:exec('nuwiki.file.delete', [{ 'uri': s:buf_uri() }])
|
|
endfunction
|
|
|
|
function! nuwiki#commands#rename_file() abort
|
|
if exists(':LspRename') == 2
|
|
LspRename
|
|
elseif s:has_coc()
|
|
call CocActionAsync('rename')
|
|
else
|
|
echohl WarningMsg
|
|
echom 'nuwiki: no LSP client available for rename'
|
|
echohl None
|
|
endif
|
|
endfunction
|
|
|
|
" ===== §13.1 deferred =====
|
|
|
|
" §13.1 Cluster A — list rewriters.
|
|
|
|
function! nuwiki#commands#list_remove_done() abort
|
|
let l:args = [{ 'uri': s:buf_uri() }]
|
|
call s:exec('nuwiki.list.removeDone', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#list_renumber() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
|
call s:exec('nuwiki.list.renumber', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#list_renumber_all() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{
|
|
\ 'uri': l:p['textDocument']['uri'],
|
|
\ 'position': l:p['position'],
|
|
\ 'whole_file': v:true,
|
|
\ }]
|
|
call s:exec('nuwiki.list.renumber', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#list_change_symbol(symbol, whole_list) abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{
|
|
\ 'uri': l:p['textDocument']['uri'],
|
|
\ 'position': l:p['position'],
|
|
\ 'symbol': a:symbol,
|
|
\ 'whole_list': a:whole_list ? v:true : v:false,
|
|
\ }]
|
|
call s:exec('nuwiki.list.changeSymbol', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#list_change_level(delta, whole_subtree) abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{
|
|
\ 'uri': l:p['textDocument']['uri'],
|
|
\ 'position': l:p['position'],
|
|
\ 'delta': a:delta,
|
|
\ 'whole_subtree': a:whole_subtree ? v:true : v:false,
|
|
\ }]
|
|
call s:exec('nuwiki.list.changeLevel', l:args)
|
|
endfunction
|
|
|
|
" `:VimwikiListChangeLvl decrease|increase 0` compat entry.
|
|
function! nuwiki#commands#list_change_lvl(...) abort
|
|
let l:dir = a:0 >= 1 ? a:1 : 'increase'
|
|
let l:delta = (l:dir ==# 'increase' || l:dir ==# 'indent') ? 1 : -1
|
|
call nuwiki#commands#list_change_level(l:delta, 0)
|
|
endfunction
|
|
|
|
" Cycle the list marker through vimwiki's canonical order.
|
|
let s:symbol_order = ['-', '*', '#', '1.', '1)', 'a)', 'A)', 'i)', 'I)']
|
|
|
|
function! s:detect_symbol() abort
|
|
let l:line = getline('.')
|
|
let l:m = matchlist(l:line, '^\s*\([-*#]\)\s')
|
|
if !empty(l:m) | return l:m[1] | endif
|
|
let l:m = matchlist(l:line, '^\s*\d\+\([.)]\)\s')
|
|
if !empty(l:m) | return '1' . l:m[1] | endif
|
|
if l:line =~# '^\s*[ivxlcdm]\+)\s' | return 'i)' | endif
|
|
if l:line =~# '^\s*[IVXLCDM]\+)\s' | return 'I)' | endif
|
|
if l:line =~# '^\s*[a-z]\+)\s' | return 'a)' | endif
|
|
if l:line =~# '^\s*[A-Z]\+)\s' | return 'A)' | endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! nuwiki#commands#list_cycle_symbol(direction) abort
|
|
let l:cur = s:detect_symbol()
|
|
if l:cur ==# '' | return | endif
|
|
let l:idx = index(s:symbol_order, l:cur)
|
|
if l:idx < 0 | return | endif
|
|
let l:n = (l:idx + a:direction) % len(s:symbol_order)
|
|
if l:n < 0 | let l:n += len(s:symbol_order) | endif
|
|
call nuwiki#commands#list_change_symbol(s:symbol_order[l:n], 0)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#list_toggle_or_add_checkbox() abort
|
|
let l:line = getline('.')
|
|
if l:line =~# '^\s*[-*#]\s\+\[[ xXoO.\-]\]' || l:line =~# '^\s*\w\+[.)]\s\+\[[ xXoO.\-]\]'
|
|
call nuwiki#commands#toggle_list_item()
|
|
return
|
|
endif
|
|
let l:end = matchend(l:line, '^\s*[-*#] ')
|
|
if l:end < 0
|
|
let l:end = matchend(l:line, '^\s*\w\+[.)] ')
|
|
endif
|
|
if l:end < 0 | return | endif
|
|
let l:new = strpart(l:line, 0, l:end) . '[ ] ' . strpart(l:line, l:end)
|
|
call setline('.', l:new)
|
|
endfunction
|
|
" §13.1 Cluster B — table rewriters.
|
|
|
|
function! nuwiki#commands#table_insert(...) abort
|
|
let l:cols = a:0 >= 1 ? str2nr(a:1) : 3
|
|
let l:rows = a:0 >= 2 ? str2nr(a:2) : 2
|
|
if l:cols <= 0
|
|
let l:cols = 3
|
|
endif
|
|
if l:rows <= 0
|
|
let l:rows = 2
|
|
endif
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{
|
|
\ 'uri': l:p['textDocument']['uri'],
|
|
\ 'position': l:p['position'],
|
|
\ 'cols': l:cols,
|
|
\ 'rows': l:rows,
|
|
\ }]
|
|
call s:exec('nuwiki.table.insert', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#table_align() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
|
call s:exec('nuwiki.table.align', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#table_move_left() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{
|
|
\ 'uri': l:p['textDocument']['uri'],
|
|
\ 'position': l:p['position'],
|
|
\ 'dir': 'left',
|
|
\ }]
|
|
call s:exec('nuwiki.table.moveColumn', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#table_move_right() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{
|
|
\ 'uri': l:p['textDocument']['uri'],
|
|
\ 'position': l:p['position'],
|
|
\ 'dir': 'right',
|
|
\ }]
|
|
call s:exec('nuwiki.table.moveColumn', l:args)
|
|
endfunction
|
|
" `:VimwikiColorize {color}` — pure-VimL wrap as inline span tag,
|
|
" matching vimwiki's `<span style="color:%c">%s</span>` template.
|
|
function! nuwiki#commands#colorize(...) abort
|
|
let l:color = a:0 >= 1 ? a:1 : ''
|
|
if l:color ==# ''
|
|
let l:color = input('Colour: ')
|
|
endif
|
|
if l:color ==# ''
|
|
return
|
|
endif
|
|
let l:open_tag = '<span style="color:' . l:color . '">'
|
|
let l:close_tag = '</span>'
|
|
let l:line = getline('.')
|
|
let l:col = col('.')
|
|
" Word boundaries around cursor (1-based).
|
|
let l:left = l:col
|
|
while l:left > 1 && strpart(l:line, l:left - 2, 1) =~# '[A-Za-z0-9_-]'
|
|
let l:left -= 1
|
|
endwhile
|
|
let l:right = l:col
|
|
while l:right <= strlen(l:line) && strpart(l:line, l:right - 1, 1) =~# '[A-Za-z0-9_-]'
|
|
let l:right += 1
|
|
endwhile
|
|
let l:word = strpart(l:line, l:left - 1, l:right - l:left)
|
|
if l:word ==# ''
|
|
return
|
|
endif
|
|
let l:new = strpart(l:line, 0, l:left - 1)
|
|
\ . l:open_tag . l:word . l:close_tag
|
|
\ . strpart(l:line, l:right - 1)
|
|
call setline('.', l:new)
|
|
endfunction
|
|
" §13.1 Cluster C — link helpers.
|
|
|
|
function! nuwiki#commands#paste_link() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
|
call s:exec('nuwiki.link.pasteWikilink', l:args)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#paste_url() abort
|
|
let l:p = s:cursor_position()
|
|
let l:args = [{ 'uri': l:p['textDocument']['uri'], 'position': l:p['position'] }]
|
|
call s:exec('nuwiki.link.pasteUrl', l:args)
|
|
endfunction
|
|
|
|
" ===== Pure-VimL table alignment =====
|
|
"
|
|
" Mirrors `lua/nuwiki/commands.lua`'s `align_table_at` so plain Vim
|
|
" buffers get the same "tighten columns as cells grow" behaviour the
|
|
" Neovim path enjoys, without needing a vim-lsp roundtrip. Algorithm
|
|
" matches `crates/nuwiki-lsp/src/commands.rs::ops::render_aligned_table`.
|
|
|
|
function! s:is_table_row(line) abort
|
|
return a:line =~# '^\s*|.*|\s*$'
|
|
endfunction
|
|
|
|
function! s:table_cell_starts(line) abort
|
|
" 1-based byte positions of each `|`. Cursor lands one past pipe N at
|
|
" 1-based byte (starts[N] + 1) — that's the first space inside the
|
|
" cell the pipe opens.
|
|
let l:out = []
|
|
let l:pos = 0
|
|
while 1
|
|
let l:bar = stridx(a:line, '|', l:pos)
|
|
if l:bar < 0 | break | endif
|
|
call add(l:out, l:bar + 1)
|
|
let l:pos = l:bar + 1
|
|
endwhile
|
|
return l:out
|
|
endfunction
|
|
|
|
function! s:find_table_range(row) abort
|
|
let l:total = line('$')
|
|
let l:s = a:row
|
|
while l:s > 1 && s:is_table_row(getline(l:s - 1))
|
|
let l:s -= 1
|
|
endwhile
|
|
let l:e = a:row
|
|
while l:e < l:total && s:is_table_row(getline(l:e + 1))
|
|
let l:e += 1
|
|
endwhile
|
|
return [l:s, l:e]
|
|
endfunction
|
|
|
|
function! s:parse_table_row(line) abort
|
|
let l:indent = matchstr(a:line, '^\s*')
|
|
let l:body = strpart(a:line, len(l:indent))
|
|
let l:segments = []
|
|
let l:pos = 0
|
|
while 1
|
|
let l:bar = stridx(l:body, '|', l:pos)
|
|
if l:bar < 0
|
|
call add(l:segments, strpart(l:body, l:pos))
|
|
break
|
|
endif
|
|
call add(l:segments, strpart(l:body, l:pos, l:bar - l:pos))
|
|
let l:pos = l:bar + 1
|
|
endwhile
|
|
" Drop the empty piece before the opening `|` and the trailing piece
|
|
" after the closing `|`.
|
|
if len(l:segments) > 0 && l:segments[0] ==# ''
|
|
call remove(l:segments, 0)
|
|
endif
|
|
if len(l:segments) > 0 && l:segments[-1] =~# '^\s*$'
|
|
call remove(l:segments, -1)
|
|
endif
|
|
call map(l:segments, {_, c -> substitute(substitute(c, '^\s\+', '', ''), '\s\+$', '', '')})
|
|
return [l:indent, l:segments]
|
|
endfunction
|
|
|
|
function! s:is_sep_cell(s) abort
|
|
return a:s !=# '' && a:s =~# '^[-:[:space:]]\+$'
|
|
endfunction
|
|
|
|
function! s:is_sep_row(cells) abort
|
|
if empty(a:cells) | return 0 | endif
|
|
for l:c in a:cells
|
|
if !s:is_sep_cell(l:c) | return 0 | endif
|
|
endfor
|
|
return 1
|
|
endfunction
|
|
|
|
function! s:compute_widths(rows) abort
|
|
let l:max_cols = 0
|
|
for l:r in a:rows
|
|
if len(l:r.cells) > l:max_cols
|
|
let l:max_cols = len(l:r.cells)
|
|
endif
|
|
endfor
|
|
let l:widths = repeat([1], l:max_cols)
|
|
for l:r in a:rows
|
|
if !s:is_sep_row(l:r.cells)
|
|
let l:i = 0
|
|
for l:c in l:r.cells
|
|
let l:w = strchars(l:c)
|
|
if l:w > l:widths[l:i]
|
|
let l:widths[l:i] = l:w
|
|
endif
|
|
let l:i += 1
|
|
endfor
|
|
endif
|
|
endfor
|
|
return l:widths
|
|
endfunction
|
|
|
|
function! s:render_table_row(indent, cells, widths, all_sep) abort
|
|
let l:out = a:indent . '|'
|
|
let l:i = 0
|
|
for l:w in a:widths
|
|
if a:all_sep
|
|
let l:out .= repeat('-', l:w + 2)
|
|
else
|
|
let l:c = l:i < len(a:cells) ? a:cells[l:i] : ''
|
|
let l:pad = l:w - strchars(l:c)
|
|
if l:pad < 0 | let l:pad = 0 | endif
|
|
let l:out .= ' ' . l:c . repeat(' ', l:pad) . ' '
|
|
endif
|
|
let l:out .= '|'
|
|
let l:i += 1
|
|
endfor
|
|
return l:out
|
|
endfunction
|
|
|
|
function! s:align_table_at(row) abort
|
|
let [l:s, l:e] = s:find_table_range(a:row)
|
|
let l:rows = []
|
|
let l:i = l:s
|
|
while l:i <= l:e
|
|
let [l:indent, l:cells] = s:parse_table_row(getline(l:i))
|
|
call add(l:rows, { 'indent': l:indent, 'cells': l:cells })
|
|
let l:i += 1
|
|
endwhile
|
|
let l:widths = s:compute_widths(l:rows)
|
|
if empty(l:widths) | return | endif
|
|
let l:new_lines = []
|
|
for l:r in l:rows
|
|
call add(l:new_lines, s:render_table_row(l:r.indent, l:r.cells, l:widths, s:is_sep_row(l:r.cells)))
|
|
endfor
|
|
call setline(l:s, l:new_lines)
|
|
endfunction
|
|
|
|
" Public helpers invoked via `<Cmd>` from the `<expr>` mappings. We
|
|
" can't mutate the buffer from inside the `<expr>` body (textlock), so
|
|
" the mappings hand off here via `<Cmd>:call …<CR>`, which runs an Ex
|
|
" command without leaving insert mode.
|
|
|
|
function! nuwiki#commands#_table_insert_row_below(row, cursor_col) abort
|
|
call s:align_table_at(a:row)
|
|
let l:line = getline(a:row)
|
|
let l:indent = matchstr(l:line, '^\s*')
|
|
let l:starts = s:table_cell_starts(l:line)
|
|
let l:cells = max([len(l:starts) - 1, 1])
|
|
let l:new_row = l:indent . '|' . repeat(' |', l:cells)
|
|
call append(a:row, l:new_row)
|
|
call cursor(a:row + 1, a:cursor_col)
|
|
endfunction
|
|
|
|
function! nuwiki#commands#_table_jump_to_cell(row, target_idx) abort
|
|
call s:align_table_at(a:row)
|
|
let l:starts = s:table_cell_starts(getline(a:row))
|
|
if a:target_idx < 1 || a:target_idx >= len(l:starts)
|
|
return
|
|
endif
|
|
" `starts[N]` is the 1-based byte position of the N-th `|`. The first
|
|
" space inside the cell that pipe opens sits at byte `starts[N] + 1`
|
|
" (1-based), which is what `cursor()` wants.
|
|
call cursor(a:row, l:starts[a:target_idx - 1] + 1)
|
|
endfunction
|
|
|
|
" Smart <CR> for insert mode — vimwiki `:VimwikiReturn` parity.
|
|
" Used as `inoremap <expr> <CR> 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('.')
|
|
" `<expr>` runs under textlock, so the function must be side-effect-
|
|
" free on the buffer. Buffer-mutating branches return a `<Cmd>:call
|
|
" …<CR>` keystroke that hands off to a regular function.
|
|
if l:line =~# '^\s*|.*|\s*$'
|
|
let l:indent = matchstr(l:line, '^\s*')
|
|
let l:cursor_col = len(l:indent) + 2
|
|
return "\<Cmd>call nuwiki#commands#_table_insert_row_below(" . line('.') . ", " . l:cursor_col . ")\<CR>"
|
|
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*$'
|
|
" Empty item — drop to normal, clear the line, re-enter insert,
|
|
" newline. Indent gets dropped (matches vimwiki's break-out).
|
|
return "\<Esc>0DA\<CR>"
|
|
endif
|
|
" If any auto-indent mechanism is active (autoindent, smartindent,
|
|
" cindent, or indentexpr), Vim already inserts the previous line's
|
|
" indent after `<CR>`. Skipping our own indent in that case avoids
|
|
" doubling it and pushing the new bullet one level deeper.
|
|
let l:auto = &autoindent || &smartindent || &cindent || !empty(&indentexpr)
|
|
let l:cont = (l:auto ? '' : l:indent) . l:marker . ' '
|
|
if l:has_cb
|
|
let l:cont .= '[ ] '
|
|
endif
|
|
return "\<CR>" . l:cont
|
|
endif
|
|
return "\<CR>"
|
|
endfunction
|
|
|
|
" Insert-mode `<Tab>` / `<S-Tab>` table navigation — vimwiki parity.
|
|
" Used as `<expr>` mappings so we can pass `<Tab>` / `<S-Tab>` through
|
|
" verbatim when the cursor isn't on a table row.
|
|
|
|
function! nuwiki#commands#smart_tab() abort
|
|
let l:line = getline('.')
|
|
if !s:is_table_row(l:line)
|
|
return "\<Tab>"
|
|
endif
|
|
let l:starts = s:table_cell_starts(l:line)
|
|
if len(l:starts) < 2
|
|
return "\<Tab>"
|
|
endif
|
|
let l:col = col('.') - 1
|
|
let l:current_idx = 1
|
|
let l:i = 1
|
|
for l:s in l:starts
|
|
if l:s <= l:col + 1 | let l:current_idx = l:i | endif
|
|
let l:i += 1
|
|
endfor
|
|
let l:target_idx = l:current_idx + 1
|
|
if l:target_idx < len(l:starts)
|
|
return "\<Cmd>call nuwiki#commands#_table_jump_to_cell(" . line('.') . ", " . l:target_idx . ")\<CR>"
|
|
endif
|
|
" 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 "\<Cmd>call nuwiki#commands#_table_jump_to_cell(" . l:next . ", 1)\<CR>"
|
|
endif
|
|
let l:next += 1
|
|
endwhile
|
|
let l:indent = matchstr(l:line, '^\s*')
|
|
let l:cursor_col = len(l:indent) + 2
|
|
return "\<Cmd>call nuwiki#commands#_table_insert_row_below(" . l:row . ", " . l:cursor_col . ")\<CR>"
|
|
endfunction
|
|
|
|
function! nuwiki#commands#smart_shift_tab() abort
|
|
let l:line = getline('.')
|
|
if !s:is_table_row(l:line)
|
|
return "\<S-Tab>"
|
|
endif
|
|
let l:starts = s:table_cell_starts(l:line)
|
|
if len(l:starts) < 2
|
|
return "\<S-Tab>"
|
|
endif
|
|
let l:col = col('.') - 1
|
|
let l:current_idx = 0
|
|
let l:i = 1
|
|
for l:s in l:starts
|
|
if l:s <= l:col + 1 | let l:current_idx = l:i | endif
|
|
let l:i += 1
|
|
endfor
|
|
if l:current_idx <= 1
|
|
return "\<S-Tab>"
|
|
endif
|
|
let l:target_idx = l:current_idx - 1
|
|
return "\<Cmd>call nuwiki#commands#_table_jump_to_cell(" . line('.') . ", " . l:target_idx . ")\<CR>"
|
|
endfunction
|
|
|
|
" `:VimwikiNormalizeLink` — wrap the word at cursor as `[[word]]`
|
|
" without following. Pure-VimL.
|
|
function! nuwiki#commands#normalize_link() abort
|
|
if s:cursor_inside_wikilink()
|
|
return
|
|
endif
|
|
call s:wrap_cword_as_wikilink()
|
|
endfunction
|
|
|
|
" ===== Name-parity aliases =====
|
|
"
|
|
" The Lua surface and the VimL surface diverged on a handful of names:
|
|
"
|
|
" Lua → VimL
|
|
" heading_add_level heading_add
|
|
" heading_remove_level heading_remove
|
|
" table_move_column_left table_move_left
|
|
" table_move_column_right table_move_right
|
|
"
|
|
" Real callers always go through the keymaps / `:Vimwiki*` commands
|
|
" defined in `ftplugin/vimwiki.vim`, which already route correctly.
|
|
" These aliases exist so the *public* `nuwiki#commands#*` surface
|
|
" matches Lua's `require('nuwiki.commands').*` 1:1 for any user who
|
|
" calls them by name from their own config.
|
|
|
|
function! nuwiki#commands#heading_add_level() abort
|
|
call nuwiki#commands#heading_add()
|
|
endfunction
|
|
|
|
function! nuwiki#commands#heading_remove_level() abort
|
|
call nuwiki#commands#heading_remove()
|
|
endfunction
|
|
|
|
function! nuwiki#commands#table_move_column_left() abort
|
|
call nuwiki#commands#table_move_left()
|
|
endfunction
|
|
|
|
function! nuwiki#commands#table_move_column_right() abort
|
|
call nuwiki#commands#table_move_right()
|
|
endfunction |