feat(config): P3 config batch — group 2 (create_link, dir_link, auto_header)

- create_link (default true): server resolve_target_uri now only synthesises a
  missing-page URI when create_link is set; when off, following a link to a
  non-existent page returns no location (no-op).
- dir_link: a [[dir/]] link now resolves to <dir>/<dir_link><ext> when dir_link
  is set, else opens the directory itself. Uses the already-parsed
  LinkTarget.is_directory.
- auto_header (default off): new wiki pages get a `= Stem =` header derived from
  the filename, skipping the wiki index + diary index. Client-side BufNewFile
  autocmd in both clients (init.lua + plugin/nuwiki.vim), with auto_header
  handlers in both command layers (trailing-slash-robust root matching).

Tests: cmd.auto_header_inserts + cmd.auto_header_skips_diary_index (nvim) /
cmd.auto_header_skips_index (vim). Full rust suite + both harnesses green
(Neovim 307, Vim 301/18/21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 12:19:37 +00:00
parent b2f2fc88bd
commit 87d14310fc
7 changed files with 149 additions and 1 deletions
+24
View File
@@ -190,6 +190,30 @@ function! nuwiki#commands#auto_chdir() abort
endif endif
endfunction endfunction
" vimwiki `auto_header`: on a new wiki page, insert a level-1 header derived
" from the filename. Skips the wiki index + diary index pages; only acts on an
" empty new buffer. Wired from a BufNewFile autocmd.
function! nuwiki#commands#auto_header() abort
if line('$') > 1 || getline(1) !=# ''
return
endif
let l:file = expand('%:p')
for l:c in nuwiki#commands#wiki_list()
let l:root = fnamemodify(expand(l:c.root), ':p')
if l:root[len(l:root) - 1:] !=# '/' | let l:root .= '/' | endif
if l:file[: len(l:root) - 1] ==# l:root
let l:index_path = l:root . l:c.idx . l:c.ext
let l:diary_index_path = l:root . l:c.diary_rel . '/' . l:c.diary_idx . l:c.ext
if l:file ==# l:index_path || l:file ==# l:diary_index_path
return
endif
break
endif
endfor
call setline(1, '= ' . expand('%:t:r') . ' =')
call append(1, '')
endfunction
" :VimwikiShowVersion / :NuwikiShowVersion — echo the nuwiki version and the " :VimwikiShowVersion / :NuwikiShowVersion — echo the nuwiki version and the
" host editor, mirroring upstream's version banner. " host editor, mirroring upstream's version banner.
function! nuwiki#commands#show_version() abort function! nuwiki#commands#show_version() abort
+23 -1
View File
@@ -269,7 +269,29 @@ impl Backend {
} }
} }
let path = target.path.as_deref()?; let path = target.path.as_deref()?;
return synthesise_page_uri(&source_wiki.config, path); // vimwiki `dir_link`: a `[[dir/]]` link opens `<dir>/<dir_link>`
// (e.g. `index` → `dir/index.wiki`); empty → the directory itself.
if target.is_directory {
let dl = source_wiki.config.dir_link.trim();
if dl.is_empty() {
let mut p = source_wiki.config.root.clone();
for seg in path.split('/').filter(|s| !s.is_empty()) {
p.push(seg);
}
return Url::from_file_path(p).ok();
}
let joined = format!("{}/{dl}", path.trim_end_matches('/'));
return synthesise_page_uri(&source_wiki.config, &joined);
}
// vimwiki `create_link`: when off, only follow to a page that
// already exists on disk — never synthesise a new one.
let uri = synthesise_page_uri(&source_wiki.config, path)?;
if !source_wiki.config.create_link
&& !uri.to_file_path().map(|p| p.exists()).unwrap_or(false)
{
return None;
}
return Some(uri);
} }
// No wiki registered (no `initializationOptions`, no // No wiki registered (no `initializationOptions`, no
// workspace folder). Fall back to the source buffer's // workspace folder). Fall back to the source buffer's
+17
View File
@@ -728,6 +728,23 @@ endtry
call s:record(s:nm_err ==# '' ? 1 : 0, call s:record(s:nm_err ==# '' ? 1 : 0,
\ 'cmd.VimwikiSearch_no_match_graceful', 'err=' . s:nm_err) \ 'cmd.VimwikiSearch_no_match_graceful', 'err=' . s:nm_err)
" ===== auto_header inserts `= Stem =` on a new wiki page; skips the index =====
let s:ah_root = fnamemodify(expand(nuwiki#commands#wiki_list()[0].root), ':p')
if s:ah_root[len(s:ah_root)-1:] !=# '/' | let s:ah_root .= '/' | endif
new
execute 'file ' . fnameescape('/tmp/nuwiki-ah-plain/MyNewPage.wiki')
call nuwiki#commands#auto_header()
call s:record(getline(1) ==# '= MyNewPage =' ? 1 : 0,
\ 'cmd.auto_header_inserts', 'line1=' . getline(1))
bwipeout!
new
execute 'file ' . fnameescape(s:ah_root . 'index.wiki')
call nuwiki#commands#auto_header()
call s:record(getline(1) ==# '' ? 1 : 0,
\ 'cmd.auto_header_skips_index', 'line1=' . getline(1))
bwipeout!
unlet s:ah_root
" ===== :VimwikiGoto -nargs=* ===== " ===== :VimwikiGoto -nargs=* =====
" Was -nargs=1, so a page name with a space raised E488 (and the empty-arg " Was -nargs=1, so a page name with a space raised E488 (and the empty-arg
" prompt fallback was unreachable). Now -nargs=*: a multi-word arg must parse. " prompt fallback was unreachable). Now -nargs=*: a multi-word arg must parse.
+25
View File
@@ -305,6 +305,31 @@ vim.defer_fn(function()
vim.cmd('bwipeout!') vim.cmd('bwipeout!')
end end
-- auto_header: the function inserts `= Stem =` on an empty new wiki buffer,
-- and skips the wiki index page. Operate on dedicated buffer handles so the
-- harness's own buffer is untouched.
do
local wroot = require('nuwiki.config').wiki_cfg(0).root
local root = vim.fn.fnamemodify(vim.fn.expand(wroot), ':p')
if root:sub(-1) ~= '/' then root = root .. '/' end
local function ah_line(name)
local buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_buf_set_name(buf, name)
require('nuwiki.commands').auto_header(buf)
local line = vim.api.nvim_buf_get_lines(buf, 0, 1, false)[1] or ''
vim.api.nvim_buf_delete(buf, { force = true })
return line
end
-- A normal page (outside any wiki root) → header inserted.
local got = ah_line('/tmp/nuwiki-ah-plain/MyNewPage.wiki')
record(got == '= MyNewPage =', 'cmd.auto_header_inserts', 'line1=' .. got)
-- The diary index page → skipped (no header). (The wiki index itself is the
-- harness's open buffer, so use the diary index to avoid an E95 collision.)
local c = require('nuwiki.config').wiki_cfg(0)
local idx_line = ah_line(root .. c.diary_rel .. '/' .. c.diary_idx .. '.wiki')
record(idx_line == '', 'cmd.auto_header_skips_diary_index', 'line1=' .. idx_line)
end
-- ===== Lists ===== -- ===== Lists =====
run('list.toggle_via_C-Space', { run('list.toggle_via_C-Space', {
+31
View File
@@ -285,6 +285,37 @@ function M.show_version()
}, false, {}) }, false, {})
end end
-- vimwiki `auto_header`: on a new wiki page, insert a level-1 header derived
-- from the filename. Skips the wiki index + diary index pages, and only acts
-- on a genuinely empty buffer. Called from a BufNewFile autocmd.
function M.auto_header(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
if #lines > 1 or (lines[1] or '') ~= '' then
return -- not an empty new buffer
end
local file = vim.api.nvim_buf_get_name(bufnr)
local config = require('nuwiki.config')
for i = 0, 64 do
local c = config.wiki_cfg(i)
if not c then break end
local root = vim.fn.fnamemodify(vim.fn.expand(c.root), ':p')
if root:sub(-1) ~= '/' then root = root .. '/' end
if file:sub(1, #root) == root then
-- Skip the wiki index and the diary index pages.
local index_path = root .. c.idx .. c.ext
local diary_index_path = root .. c.diary_rel .. '/' .. c.diary_idx .. c.ext
if file == index_path or file == diary_index_path then
return
end
break
end
if not (config.options.wikis or vim.g.nuwiki_wikis) then break end
end
local title = vim.fn.fnamemodify(file, ':t:r')
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, { '= ' .. title .. ' =', '' })
end
-- :VimwikiSearch / :VWS {pattern} — search the current wiki's files (upstream -- :VimwikiSearch / :VWS {pattern} — search the current wiki's files (upstream
-- scopes to the wiki, not the editor CWD). Resolves the wiki whose root holds -- scopes to the wiki, not the editor CWD). Resolves the wiki whose root holds
-- the current file, else the first configured wiki, else CWD. Empty pattern -- the current file, else the first configured wiki, else CWD. Empty pattern
+16
View File
@@ -113,6 +113,22 @@ function M.setup(opts)
vim.g.nuwiki_no_calendar = 1 vim.g.nuwiki_no_calendar = 1
end end
-- vimwiki `auto_header` (default off): insert a level-1 header from the
-- filename on new wiki pages. BufNewFile (not FileType) so it only fires for
-- genuinely new files.
if config.options.auto_header == true then
local ext = (config.options.file_extension or '.wiki'):gsub('^%.', '')
local pats = { '*.wiki' }
if ext ~= 'wiki' and ext ~= '' then
pats[#pats + 1] = '*.' .. ext
end
vim.api.nvim_create_autocmd('BufNewFile', {
pattern = pats,
group = vim.api.nvim_create_augroup('nuwiki_auto_header', { clear = true }),
callback = function(a) require('nuwiki.commands').auto_header(a.buf) end,
})
end
lsp.register() lsp.register()
_setup_global_mappings() _setup_global_mappings()
_setup_global_commands() _setup_global_commands()
+13
View File
@@ -69,6 +69,19 @@ augroup nuwiki_vim_lsp_start
autocmd FileType vimwiki call nuwiki#lsp#start() autocmd FileType vimwiki call nuwiki#lsp#start()
augroup END augroup END
" vimwiki `auto_header` (default off): insert a level-1 header from the filename
" on new wiki pages. BufNewFile fires only for genuinely new files. Matches the
" configured extension(s).
if get(g:, 'nuwiki_auto_header', 0)
let s:awh_ext = get(g:, 'nuwiki_file_extension', '.wiki')
let s:awh_ext = s:awh_ext[0] ==# '.' ? s:awh_ext : '.' . s:awh_ext
augroup nuwiki_auto_header
autocmd!
execute 'autocmd BufNewFile *' . s:awh_ext . ' call nuwiki#commands#auto_header()'
augroup END
unlet s:awh_ext
endif
" Global wiki-picker commands — available from any buffer so a wiki can be " Global wiki-picker commands — available from any buffer so a wiki can be
" chosen before a .wiki file exists. The picker reads its list from config " chosen before a .wiki file exists. The picker reads its list from config
" (no LSP); opening the chosen index then auto-starts the server. The " (no LSP); opening the chosen index then auto-starts the server. The