From 37c7d28d19fa36f5d87befe97896fda67f10d149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Yngve=20Lerv=C3=A5g?= Date: Sun, 28 May 2023 20:12:04 +0200 Subject: [PATCH 1/4] test: added initial test specs for refile --- test/test-refile/Makefile | 14 +++++++++ test/test-refile/test-bad-input.vim | 24 +++++++++++++++ test/test-refile/test-by-anchor-before-1.vim | 11 +++++++ test/test-refile/test-by-anchor-before-2.vim | 14 +++++++++ test/test-refile/test-by-lnum-1.vim | 18 +++++++++++ test/test-refile/test-by-lnum-2.vim | 30 +++++++++++++++++++ test/test-refile/test-by-lnum-3.vim | 27 +++++++++++++++++ test/test-refile/wiki/index.wiki | 15 ++++++++++ test/test-refile/wiki/links.wiki | 11 +++++++ .../wiki/ref-by-anchor-before-1.wiki | 15 ++++++++++ .../wiki/ref-by-anchor-before-2.wiki | 16 ++++++++++ .../wiki/ref-by-lnum-2-source.wiki | 11 +++++++ .../wiki/ref-by-lnum-2-target.wiki | 15 ++++++++++ .../wiki/ref-by-lnum-3-source.wiki | 13 ++++++++ .../wiki/ref-by-lnum-3-target.wiki | 13 ++++++++ test/test-refile/wiki/ref-same-file.wiki | 26 ++++++++++++++++ test/test-refile/wiki/source-1.wiki | 26 ++++++++++++++++ test/test-refile/wiki/source-2.wiki | 11 +++++++ test/test-refile/wiki/target-1.wiki | 11 +++++++ test/test-refile/wiki/target-2.wiki | 11 +++++++ 20 files changed, 332 insertions(+) create mode 100644 test/test-refile/Makefile create mode 100644 test/test-refile/test-bad-input.vim create mode 100644 test/test-refile/test-by-anchor-before-1.vim create mode 100644 test/test-refile/test-by-anchor-before-2.vim create mode 100644 test/test-refile/test-by-lnum-1.vim create mode 100644 test/test-refile/test-by-lnum-2.vim create mode 100644 test/test-refile/test-by-lnum-3.vim create mode 100644 test/test-refile/wiki/index.wiki create mode 100644 test/test-refile/wiki/links.wiki create mode 100644 test/test-refile/wiki/ref-by-anchor-before-1.wiki create mode 100644 test/test-refile/wiki/ref-by-anchor-before-2.wiki create mode 100644 test/test-refile/wiki/ref-by-lnum-2-source.wiki create mode 100644 test/test-refile/wiki/ref-by-lnum-2-target.wiki create mode 100644 test/test-refile/wiki/ref-by-lnum-3-source.wiki create mode 100644 test/test-refile/wiki/ref-by-lnum-3-target.wiki create mode 100644 test/test-refile/wiki/ref-same-file.wiki create mode 100644 test/test-refile/wiki/source-1.wiki create mode 100644 test/test-refile/wiki/source-2.wiki create mode 100644 test/test-refile/wiki/target-1.wiki create mode 100644 test/test-refile/wiki/target-2.wiki diff --git a/test/test-refile/Makefile b/test/test-refile/Makefile new file mode 100644 index 00000000..07a41010 --- /dev/null +++ b/test/test-refile/Makefile @@ -0,0 +1,14 @@ +MYVIM ?= nvim --clean --headless +export QUIT = 1 + +tests := $(wildcard test*.vim) + +.PHONY: cleanup $(tests) + +test: $(tests) + +$(tests): + @rm -rf wiki-tmp + @cp -r wiki wiki-tmp + @$(MYVIM) -u $@ + @rm -rf wiki-tmp diff --git a/test/test-refile/test-bad-input.vim b/test/test-refile/test-bad-input.vim new file mode 100644 index 00000000..ae32e29e --- /dev/null +++ b/test/test-refile/test-bad-input.vim @@ -0,0 +1,24 @@ +source ../init.vim +runtime plugin/wiki.vim + +let g:wiki_log_verbose = 0 + +silent edit wiki-tmp/index.wiki + +" Refile something not within a section should return with error message +normal 2G +silent call wiki#page#refile() +let s:log = wiki#log#get() +call assert_equal(1, len(s:log)) +call assert_equal('error', s:log[0].type) +call assert_equal('No source section recognized!', s:log[0].msg[0]) + +" Refile to nonexisting target should return with error message +normal! 13G +silent call wiki#page#refile(#{target_page: 'targetDoesNotExist'}) +let s:log = wiki#log#get() +call assert_equal(2, len(s:log)) +call assert_equal('error', s:log[1].type) +call assert_equal('Target page was not found!', s:log[1].msg[0]) + +call wiki#test#finished() diff --git a/test/test-refile/test-by-anchor-before-1.vim b/test/test-refile/test-by-anchor-before-1.vim new file mode 100644 index 00000000..b805b541 --- /dev/null +++ b/test/test-refile/test-by-anchor-before-1.vim @@ -0,0 +1,11 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/index.wiki +normal! 13G +silent call wiki#page#refile(#{target_anchor_before: '#Intro'}) +call assert_equal( + \ readfile('wiki-tmp/ref-by-anchor-before-1.wiki'), + \ readfile('wiki-tmp/index.wiki')) + +call wiki#test#finished() diff --git a/test/test-refile/test-by-anchor-before-2.vim b/test/test-refile/test-by-anchor-before-2.vim new file mode 100644 index 00000000..c0a83b02 --- /dev/null +++ b/test/test-refile/test-by-anchor-before-2.vim @@ -0,0 +1,14 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/source-2.wiki +normal! 8G +silent call wiki#page#refile(#{ + \ target_page: 'target-2', + \ target_anchor_before: '#First#Foo' + \}) +call assert_equal( + \ readfile('wiki-tmp/ref-by-anchor-before-2.wiki'), + \ readfile('wiki-tmp/target-2.wiki')) + +call wiki#test#finished() diff --git a/test/test-refile/test-by-lnum-1.vim b/test/test-refile/test-by-lnum-1.vim new file mode 100644 index 00000000..9ff6fbcc --- /dev/null +++ b/test/test-refile/test-by-lnum-1.vim @@ -0,0 +1,18 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/source-1.wiki +normal! 10G +silent call wiki#page#refile(#{target_lnum: 20}) + +" Check that content was properly removed from index and moved to targetA +call assert_equal( + \ readfile('wiki-tmp/ref-same-file.wiki'), + \ readfile('wiki-tmp/source-1.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ '[[source-1#Tasks#Bar#Subheading]]', + \ readfile('wiki-tmp/links.wiki')[10]) + +call wiki#test#finished() diff --git a/test/test-refile/test-by-lnum-2.vim b/test/test-refile/test-by-lnum-2.vim new file mode 100644 index 00000000..e15abbee --- /dev/null +++ b/test/test-refile/test-by-lnum-2.vim @@ -0,0 +1,30 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/index.wiki +normal! 13G +silent call wiki#page#refile(#{target_page: 'target-1'}) + +" Check that content was properly moved +call assert_equal( + \ readfile('wiki-tmp/ref-by-lnum-2-source.wiki'), + \ readfile('wiki-tmp/index.wiki')) +call assert_equal( + \ readfile('wiki-tmp/ref-by-lnum-2-target.wiki'), + \ readfile('wiki-tmp/target-1.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ '[[target-1#Section 1]]', + \ readfile('wiki-tmp/index.wiki')[6]) +call assert_equal( + \ '[[target-1#Section 1#Foo bar Baz]]', + \ readfile('wiki-tmp/index.wiki')[7]) +call assert_equal( + \ '[[target-1#Section 1]]', + \ readfile('wiki-tmp/links.wiki')[7]) +call assert_equal( + \ '[[target-1#Section 1#Foo bar Baz]]', + \ readfile('wiki-tmp/links.wiki')[8]) + +call wiki#test#finished() diff --git a/test/test-refile/test-by-lnum-3.vim b/test/test-refile/test-by-lnum-3.vim new file mode 100644 index 00000000..14b7b93c --- /dev/null +++ b/test/test-refile/test-by-lnum-3.vim @@ -0,0 +1,27 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/index.wiki +normal! 15G +silent call wiki#page#refile(#{ + \ target_page: 'target-2', + \ target_lnum: 10 + \}) + +" Check that content was properly moved +call assert_equal( + \ readfile('wiki-tmp/ref-by-lnum-3-source.wiki'), + \ readfile('wiki-tmp/index.wiki')) +call assert_equal( + \ readfile('wiki-tmp/ref-by-lnum-3-target.wiki'), + \ readfile('wiki-tmp/target-2.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ '[[target-2#Second#Foo bar Baz]]', + \ readfile('wiki-tmp/index.wiki')[7]) +call assert_equal( + \ '[[target-2#Second#Foo bar Baz]]', + \ readfile('wiki-tmp/links.wiki')[8]) + +call wiki#test#finished() diff --git a/test/test-refile/wiki/index.wiki b/test/test-refile/wiki/index.wiki new file mode 100644 index 00000000..fdaa3fd5 --- /dev/null +++ b/test/test-refile/wiki/index.wiki @@ -0,0 +1,15 @@ +Intro text here. + +# Intro + +This is a wiki. + +[[#Section 1]] +[[#Section 1#Foo bar Baz]] + +This-is-a-wiki. + +# Section 1 + +## Foo bar Baz + diff --git a/test/test-refile/wiki/links.wiki b/test/test-refile/wiki/links.wiki new file mode 100644 index 00000000..5abbaeb3 --- /dev/null +++ b/test/test-refile/wiki/links.wiki @@ -0,0 +1,11 @@ +# Intro + +This is a wiki. + +[[BadName]] +[[subdir/BadName]] + +[[index#Section 1]] +[[index#Section 1#Foo bar Baz]] + +[[source-1#Inbox#Bar#Subheading]] diff --git a/test/test-refile/wiki/ref-by-anchor-before-1.wiki b/test/test-refile/wiki/ref-by-anchor-before-1.wiki new file mode 100644 index 00000000..a9ee1190 --- /dev/null +++ b/test/test-refile/wiki/ref-by-anchor-before-1.wiki @@ -0,0 +1,15 @@ +Intro text here. + +# Section 1 + +## Foo bar Baz + +# Intro + +This is a wiki. + +[[#Section 1]] +[[#Section 1#Foo bar Baz]] + +This-is-a-wiki. + diff --git a/test/test-refile/wiki/ref-by-anchor-before-2.wiki b/test/test-refile/wiki/ref-by-anchor-before-2.wiki new file mode 100644 index 00000000..7c567d66 --- /dev/null +++ b/test/test-refile/wiki/ref-by-anchor-before-2.wiki @@ -0,0 +1,16 @@ +# First + +## Section 2 + +### Subsection A + +### Subsection B +## Foo + +## Bar + +# Second + +## Baz + +## Foobar diff --git a/test/test-refile/wiki/ref-by-lnum-2-source.wiki b/test/test-refile/wiki/ref-by-lnum-2-source.wiki new file mode 100644 index 00000000..65e2d1a8 --- /dev/null +++ b/test/test-refile/wiki/ref-by-lnum-2-source.wiki @@ -0,0 +1,11 @@ +Intro text here. + +# Intro + +This is a wiki. + +[[target-1#Section 1]] +[[target-1#Section 1#Foo bar Baz]] + +This-is-a-wiki. + diff --git a/test/test-refile/wiki/ref-by-lnum-2-target.wiki b/test/test-refile/wiki/ref-by-lnum-2-target.wiki new file mode 100644 index 00000000..931e84eb --- /dev/null +++ b/test/test-refile/wiki/ref-by-lnum-2-target.wiki @@ -0,0 +1,15 @@ +# Section 1 + +## Foo bar Baz + +# Section 1 + +## Subsection A + +## Subsection B + +# Section 2 + +## Subsection A + +## Subsection B diff --git a/test/test-refile/wiki/ref-by-lnum-3-source.wiki b/test/test-refile/wiki/ref-by-lnum-3-source.wiki new file mode 100644 index 00000000..2b05bcc1 --- /dev/null +++ b/test/test-refile/wiki/ref-by-lnum-3-source.wiki @@ -0,0 +1,13 @@ +Intro text here. + +# Intro + +This is a wiki. + +[[#Section 1]] +[[target-2#Second#Foo bar Baz]] + +This-is-a-wiki. + +# Section 1 + diff --git a/test/test-refile/wiki/ref-by-lnum-3-target.wiki b/test/test-refile/wiki/ref-by-lnum-3-target.wiki new file mode 100644 index 00000000..b99dedbb --- /dev/null +++ b/test/test-refile/wiki/ref-by-lnum-3-target.wiki @@ -0,0 +1,13 @@ +# First + +## Foo + +## Bar + +# Second + +## Baz + +## Foo bar Baz + +## Foobar diff --git a/test/test-refile/wiki/ref-same-file.wiki b/test/test-refile/wiki/ref-same-file.wiki new file mode 100644 index 00000000..78ad9882 --- /dev/null +++ b/test/test-refile/wiki/ref-same-file.wiki @@ -0,0 +1,26 @@ +# Inbox +Link to: [[#Tasks#Bar]] +## Foo + +Random text here. + +# Tasks + +## Baz + +Even more random text here. + +## Bar + +More random text here. + +### Subheading + +More here. + +# Someday + +## Qux + +More text. + diff --git a/test/test-refile/wiki/source-1.wiki b/test/test-refile/wiki/source-1.wiki new file mode 100644 index 00000000..b193024f --- /dev/null +++ b/test/test-refile/wiki/source-1.wiki @@ -0,0 +1,26 @@ +# Inbox +Link to: [[#Inbox#Bar]] +## Foo + +Random text here. + +## Bar + +More random text here. + +### Subheading + +More here. + +# Tasks + +## Baz + +Even more random text here. + +# Someday + +## Qux + +More text. + diff --git a/test/test-refile/wiki/source-2.wiki b/test/test-refile/wiki/source-2.wiki new file mode 100644 index 00000000..2ef92b81 --- /dev/null +++ b/test/test-refile/wiki/source-2.wiki @@ -0,0 +1,11 @@ +# Section 1 + +## Subsection A + +## Subsection B + +# Section 2 + +## Subsection A + +## Subsection B diff --git a/test/test-refile/wiki/target-1.wiki b/test/test-refile/wiki/target-1.wiki new file mode 100644 index 00000000..2ef92b81 --- /dev/null +++ b/test/test-refile/wiki/target-1.wiki @@ -0,0 +1,11 @@ +# Section 1 + +## Subsection A + +## Subsection B + +# Section 2 + +## Subsection A + +## Subsection B diff --git a/test/test-refile/wiki/target-2.wiki b/test/test-refile/wiki/target-2.wiki new file mode 100644 index 00000000..3f692012 --- /dev/null +++ b/test/test-refile/wiki/target-2.wiki @@ -0,0 +1,11 @@ +# First + +## Foo + +## Bar + +# Second + +## Baz + +## Foobar From aad13d283b98e3662acc31cb1a2f1b12ad3eadfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Yngve=20Lerv=C3=A5g?= Date: Sun, 28 May 2023 20:29:29 +0200 Subject: [PATCH 2/4] feat: add initial version of WikiPageRefile refer: #58 --- autoload/wiki/buffer.vim | 3 + autoload/wiki/page.vim | 45 +++++++++++- autoload/wiki/page/refile.vim | 133 ++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 autoload/wiki/page/refile.vim diff --git a/autoload/wiki/buffer.vim b/autoload/wiki/buffer.vim index 3184da0e..74759fe4 100644 --- a/autoload/wiki/buffer.vim +++ b/autoload/wiki/buffer.vim @@ -66,6 +66,7 @@ function! s:init_buffer_commands() abort " {{{1 command! -buffer WikiLinkIncomingToggle call wiki#link#incoming_display_toggle() command! -buffer WikiLinkIncomingHover call wiki#link#incoming_hover() command! -buffer WikiPageDelete call wiki#page#delete() + command! -buffer WikiPageRefile call wiki#page#refile() command! -buffer WikiPageRename call wiki#page#rename() command! -buffer WikiPageRenameSection call wiki#page#rename_section() command! -buffer WikiToc call g:wiki_select_method.toc() @@ -119,6 +120,7 @@ function! s:init_buffer_mappings() abort " {{{1 nnoremap (wiki-link-incoming-toggle) :WikiLinkIncomingToggle nnoremap (wiki-link-incoming-hover) :WikiLinkIncomingHover nnoremap (wiki-page-delete) :WikiPageDelete + nnoremap (wiki-page-refile) :WikiPageRefile nnoremap (wiki-page-rename) :WikiPageRename nnoremap (wiki-page-rename-section) :WikiPageRenameSection nnoremap (wiki-toc-generate) :WikiTocGenerate @@ -183,6 +185,7 @@ function! s:init_buffer_mappings() abort " {{{1 \ '(wiki-link-incoming-toggle)': 'li', \ '(wiki-link-incoming-hover)': 'lI', \ '(wiki-page-delete)': 'd', + \ '(wiki-page-refile)' : 'q', \ '(wiki-page-rename)': 'r', \ '(wiki-page-rename-section)': '', \ '(wiki-toc-generate)': 't', diff --git a/autoload/wiki/page.vim b/autoload/wiki/page.vim index 3dbafb8a..bb0f5165 100644 --- a/autoload/wiki/page.vim +++ b/autoload/wiki/page.vim @@ -162,6 +162,39 @@ function! wiki#page#rename_section(...) abort "{{{1 call s:update_links_external(l:source, l:target) endfunction +" }}}1 +function! wiki#page#refile(...) abort "{{{1 + let l:opts = extend(#{ + \ target_page: '', + \ target_lnum: 0, + \}, a:0 > 0 ? a:1 : {}) + + " target_page could be given by user input with something like this: + " let l:opts.target_page = input('> ', '', 'customlist,wiki#complete#url') + + let l:source = wiki#page#refile#collect_source(l:opts) + if empty(l:source) + return wiki#log#error('No source section recognized!') + endif + + try + let l:target = wiki#page#refile#collect_target(l:opts, l:source) + catch /wiki.vim: target page not found/ + return wiki#log#error('Target page was not found!') + catch /wiki.vim: anchor not recognized/ + return wiki#log#error('Target anchor not recognized!') + endtry + + call wiki#log#info( + \ printf('Moving section "%s" into "%s"', + \ l:source.header, wiki#paths#to_node(l:target.path))) + + call wiki#page#refile#move(l:source, l:target) + + call s:update_links_local(l:source, l:target) + call s:update_links_external(l:source, l:target) +endfunction + " }}}1 function! wiki#page#export(line1, line2, ...) abort " {{{1 let l:cfg = deepcopy(g:wiki_export) @@ -297,14 +330,20 @@ endfunction " }}}1 function! s:update_links_local(old, new) abort "{{{1 " Arguments: - " old: dict(anchor) - " new: dict(anchor) + " old: dict(anchor, path?) + " new: dict(anchor, path?) let l:pos = getcurpos() + + let l:anchor = !has_key(a:old, 'path') || a:old.path ==# a:new.path + \ ? a:new.anchor + \ : wiki#paths#to_wiki_url(a:new.path) . a:new.anchor + keeppattern keepjumps execute printf('%%s/\V%s/%s/e%s', \ a:old.anchor, - \ a:new.anchor, + \ l:anchor, \ &gdefault ? '' : 'g') silent update + call cursor(l:pos[1:]) endfunction diff --git a/autoload/wiki/page/refile.vim b/autoload/wiki/page/refile.vim new file mode 100644 index 00000000..f3100b16 --- /dev/null +++ b/autoload/wiki/page/refile.vim @@ -0,0 +1,133 @@ +" A wiki plugin for Vim +" +" Maintainer: Karl Yngve Lervåg +" Email: karl.yngve@gmail.com +" + +function! wiki#page#refile#collect_source(...) abort " {{{1 + " Returns: + " source: dict(path, lnum, lnum_end, header, anchor, level) + + let l:source = wiki#toc#get_section() + if !empty(l:source) + let l:source.path = expand('%:p') + endif + + return l:source +endfunction + +" }}}1 +function! wiki#page#refile#collect_target(opts, source) abort " {{{1 + " Arguments: + " opts: dict( + " target_page, + " target_lnum, + " target_anchor_before?, + " ) + " source: dict(path, lnum, lnum_end, header, anchor) + " Returns: + " target: dict(path, lnum, anchor, level) + + let l:path = wiki#u#eval_filename(a:opts.target_page) + if !filereadable(l:path) + throw 'wiki.vim: target page not found' + endif + + + if has_key(a:opts, 'target_anchor_before') + return s:collect_target_by_anchor_before( + \ l:path, + \ a:opts.target_anchor_before, + \ a:source + \) + endif + + return s:collect_target_by_lnum(l:path, a:opts.target_lnum, a:source) +endfunction + +" }}}1 +function! wiki#page#refile#move(source, target) abort " {{{1 + " Arguments: + " source: dict(path, lnum, lnum_end) + " target: dict(path, lnum) + + if a:target.level < a:source.level + call execute(printf('%d,%dg/^#/normal! 0%dx', + \ a:source.lnum, a:source.lnum_end, a:source.level - a:target.level)) + elseif a:target.level > a:source.level + call execute(printf('%d,%dg/^#/normal! 0%di#', + \ a:source.lnum, a:source.lnum_end, a:target.level - a:source.level)) + endif + + if a:target.path ==# a:source.path + call execute(printf('%d,%dm %d', + \ a:source.lnum, a:source.lnum_end, a:target.lnum)) + silent write + else + let l:lines = getline(a:source.lnum, a:source.lnum_end) + call deletebufline('', a:source.lnum, a:source.lnum_end) + silent write + + let l:current_bufnr = bufnr('') + let l:was_loaded = bufloaded(a:target.path) + keepalt execute 'silent edit' fnameescape(a:target.path) + call append(a:target.lnum, l:lines) + silent write + if !l:was_loaded + keepalt execute 'bwipeout' + endif + keepalt execute 'buffer' l:current_bufnr + endif +endfunction + +" }}}1 + +function! s:collect_target_by_anchor_before(path, anchor, source) abort " {{{1 + let l:section = {} + for l:section in wiki#toc#gather_entries(#{ path: a:path }) + if l:section.anchor ==# a:anchor + break + endif + endfor + + if empty(l:section) + throw 'wiki.vim: anchor not recognized' + endif + + let l:anchors = get(l:section, 'anchors', []) + if len(l:anchors) > 0 + call remove(l:anchors, -1) + endif + let l:anchors += [a:source.anchors[-1]] + + return #{ + \ path: a:path, + \ lnum: l:section.lnum - 1, + \ anchor: '#' . join(l:anchors, '#'), + \ level: len(l:anchors) + \} +endfunction + +" }}}1 +function! s:collect_target_by_lnum(path, lnum, source) abort " {{{1 + let l:anchors = [a:source.anchors[-1]] + + if a:source.level > 1 + let l:section = wiki#toc#get_section(#{ path: a:path, at_lnum: a:lnum }) + let l:target_anchors = get(l:section, 'anchors', []) + call extend( + \ l:anchors, + \ l:target_anchors[:a:source.level - 2], + \ 0 + \) + endif + + return #{ + \ path: a:path, + \ lnum: a:lnum, + \ anchor: '#' . join(l:anchors, '#'), + \ level: len(l:anchors) + \} +endfunction + +" }}}1 From ab69af9440c355aef2c92619f71f0cf206ab2b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Yngve=20Lerv=C3=A5g?= Date: Sun, 27 Sep 2026 11:46:41 +0200 Subject: [PATCH 3/4] fix: anchor pattern was too loose --- autoload/wiki/page.vim | 35 ++++++++++++++++++++++++---------- test/wiki-basic/pageA.wiki | 6 ++++++ test/wiki-basic/pageA.wiki.ref | 6 ++++++ test/wiki-basic/pageB.wiki | 2 ++ test/wiki-basic/pageB.wiki.ref | 2 ++ 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/autoload/wiki/page.vim b/autoload/wiki/page.vim index bb0f5165..fc9c518e 100644 --- a/autoload/wiki/page.vim +++ b/autoload/wiki/page.vim @@ -338,15 +338,24 @@ function! s:update_links_local(old, new) abort "{{{1 \ ? a:new.anchor \ : wiki#paths#to_wiki_url(a:new.path) . a:new.anchor - keeppattern keepjumps execute printf('%%s/\V%s/%s/e%s', - \ a:old.anchor, - \ l:anchor, + keeppattern keepjumps execute printf('%%s/%s/%s/e%s', + \ s:get_anchor_pattern(a:old.anchor), + \ escape(l:anchor, '\/&~'), \ &gdefault ? '' : 'g') silent update call cursor(l:pos[1:]) endfunction +" }}}1 +function! s:get_anchor_pattern(anchor) abort "{{{1 + " Create a pattern that matches a:anchor as a whole anchor, i.e. where it is + " either followed by a subanchor or by the end of the link. Without this, + " e.g. "#Foo" would also match within "#Foobar". + + return escape(a:anchor, '.*[]^$~\/') .. '\([]#|),]\|$\)\@=' +endfunction + " }}}1 function! s:update_links_external(old, new) abort "{{{1 " Arguments: @@ -370,7 +379,9 @@ function! s:update_links_external(old, new) abort "{{{1 let l:graph = wiki#graph#builder#get() let l:links = l:graph.get_links_to(l:old.path, {'nudge': v:true}) if !empty(l:old.anchor) - call filter(l:links, { _, x -> x.anchor =~# '^' .. l:old.anchor }) + call filter(l:links, { _, x -> + \ x.anchor ==# l:old.anchor + \ || stridx(x.anchor, l:old.anchor .. '#') == 0 }) endif let l:files_with_links = wiki#u#group_by(l:links, 'filename_from') @@ -380,19 +391,23 @@ function! s:update_links_external(old, new) abort "{{{1 for [l:file, l:file_links] in items(l:files_with_links) let l:lines = readfile(l:file) - for l:link in l:file_links + " Note: The substitutions are global, so each line must be visited only + " once, even if it contains several of the relevant links. + for l:lnum in uniq(sort(map(copy(l:file_links), 'v:val.lnum'), 'N')) " Update file for [l:pattern, l:replace] in l:replacement_patterns - let l:lines[l:link.lnum - 1] = substitute( - \ l:lines[l:link.lnum - 1], + let l:lines[l:lnum - 1] = substitute( + \ l:lines[l:lnum - 1], \ l:pattern, l:replace, 'g') endfor " Update anchor if !empty(l:old.anchor) - let l:lines[l:link.lnum - 1] = substitute( - \ l:lines[l:link.lnum - 1], - \ l:old.anchor, l:new.anchor, 'g') + let l:lines[l:lnum - 1] = substitute( + \ l:lines[l:lnum - 1], + \ s:get_anchor_pattern(l:old.anchor), + \ escape(l:new.anchor, '\&~'), + \ 'g') endif endfor diff --git a/test/wiki-basic/pageA.wiki b/test/wiki-basic/pageA.wiki index 2ef92b81..204fee11 100644 --- a/test/wiki-basic/pageA.wiki +++ b/test/wiki-basic/pageA.wiki @@ -9,3 +9,9 @@ ## Subsection A ## Subsection B + +# Section 20 + +Link: [[#Section 2]] and [[#Section 20]] and [[#Section 2 extra]] + +# Section 2 extra diff --git a/test/wiki-basic/pageA.wiki.ref b/test/wiki-basic/pageA.wiki.ref index a9ae1deb..22656827 100644 --- a/test/wiki-basic/pageA.wiki.ref +++ b/test/wiki-basic/pageA.wiki.ref @@ -9,3 +9,9 @@ ## Subsection A ## New sub section + +# Section 20 + +Link: [[#Section II]] and [[#Section 20]] and [[#Section 2 extra]] + +# Section 2 extra diff --git a/test/wiki-basic/pageB.wiki b/test/wiki-basic/pageB.wiki index 172c9f80..a2114762 100644 --- a/test/wiki-basic/pageB.wiki +++ b/test/wiki-basic/pageB.wiki @@ -4,4 +4,6 @@ Links before changing section name! * [[pageA#Section 1#Subsection B]] * [[pageA#Section 2#Subsection A]] * [[pageA#Section 2#Subsection B]] +* [[pageA#Section 20]] +* [[pageA#Section 2 extra]] diff --git a/test/wiki-basic/pageB.wiki.ref b/test/wiki-basic/pageB.wiki.ref index 57929cef..7c6ce739 100644 --- a/test/wiki-basic/pageB.wiki.ref +++ b/test/wiki-basic/pageB.wiki.ref @@ -4,4 +4,6 @@ Links after changing section name! * [[pageA#Section 1#Subsection ii]] * [[pageA#Section II#Subsection A]] * [[pageA#Section II#New sub section]] +* [[pageA#Section 20]] +* [[pageA#Section 2 extra]] From 3467d624339432f9d422bf83aaac3f728ae1efdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Yngve=20Lerv=C3=A5g?= Date: Sun, 27 Sep 2026 15:45:57 +0200 Subject: [PATCH 4/4] feat: add refile command with completion resolves: #309, #58 --- autoload/wiki/buffer.vim | 3 +- autoload/wiki/complete.vim | 75 ++++- autoload/wiki/page.vim | 41 ++- autoload/wiki/page/refile.vim | 307 ++++++++++++++---- autoload/wiki/toc.vim | 8 + doc/wiki.txt | 38 +++ test/init.vim | 1 + test/stub/autoload/wiki/ui/test.vim | 16 + test/test-complete/test-url.vim | 21 ++ test/test-refile/test-bad-input.vim | 53 ++- test/test-refile/test-by-lnum-2.vim | 19 +- test/test-refile/test-by-lnum-3.vim | 27 -- test/test-refile/test-code-block.vim | 17 + test/test-refile/test-command.vim | 30 ++ test/test-refile/test-completion.vim | 51 +++ test/test-refile/test-interactive-1.vim | 28 ++ test/test-refile/test-interactive-2.vim | 45 +++ test/test-refile/test-link-boundaries.vim | 32 ++ test/test-refile/test-relation-after-1.vim | 24 ++ ...efore-1.vim => test-relation-before-1.vim} | 7 +- ...efore-2.vim => test-relation-before-2.vim} | 5 +- test/test-refile/test-relation-inside-1.vim | 24 ++ test/test-refile/test-relation-inside-2.vim | 18 + test/test-refile/test-to-page.vim | 30 ++ test/test-refile/wiki/boundaries-links.wiki | 5 + test/test-refile/wiki/boundaries.wiki | 20 ++ test/test-refile/wiki/deep.wiki | 16 + test/test-refile/wiki/nested.wiki | 16 + .../wiki/ref-by-lnum-2-source.wiki | 6 +- .../wiki/ref-by-lnum-2-target.wiki | 16 +- test/test-refile/wiki/ref-code-block.wiki | 17 + test/test-refile/wiki/ref-command-source.wiki | 11 + ...-3-target.wiki => ref-command-target.wiki} | 6 +- test/test-refile/wiki/ref-interactive-2.wiki | 15 + .../wiki/ref-interactive-source.wiki | 11 + .../wiki/ref-interactive-target.wiki | 15 + .../wiki/ref-relation-after-1.wiki | 26 ++ ...fore-1.wiki => ref-relation-before-1.wiki} | 0 ...fore-2.wiki => ref-relation-before-2.wiki} | 0 .../wiki/ref-relation-inside-1.wiki | 26 ++ .../wiki/ref-relation-inside-2.wiki | 16 + ...-3-source.wiki => ref-to-page-source.wiki} | 6 +- test/test-refile/wiki/ref-to-page-target.wiki | 16 + test/test-refile/wiki/source-3.wiki | 17 + 44 files changed, 1035 insertions(+), 146 deletions(-) create mode 100644 test/stub/autoload/wiki/ui/test.vim create mode 100644 test/test-complete/test-url.vim delete mode 100644 test/test-refile/test-by-lnum-3.vim create mode 100644 test/test-refile/test-code-block.vim create mode 100644 test/test-refile/test-command.vim create mode 100644 test/test-refile/test-completion.vim create mode 100644 test/test-refile/test-interactive-1.vim create mode 100644 test/test-refile/test-interactive-2.vim create mode 100644 test/test-refile/test-link-boundaries.vim create mode 100644 test/test-refile/test-relation-after-1.vim rename test/test-refile/{test-by-anchor-before-1.vim => test-relation-before-1.vim} (51%) rename test/test-refile/{test-by-anchor-before-2.vim => test-relation-before-2.vim} (66%) create mode 100644 test/test-refile/test-relation-inside-1.vim create mode 100644 test/test-refile/test-relation-inside-2.vim create mode 100644 test/test-refile/test-to-page.vim create mode 100644 test/test-refile/wiki/boundaries-links.wiki create mode 100644 test/test-refile/wiki/boundaries.wiki create mode 100644 test/test-refile/wiki/deep.wiki create mode 100644 test/test-refile/wiki/nested.wiki create mode 100644 test/test-refile/wiki/ref-code-block.wiki create mode 100644 test/test-refile/wiki/ref-command-source.wiki rename test/test-refile/wiki/{ref-by-lnum-3-target.wiki => ref-command-target.wiki} (64%) create mode 100644 test/test-refile/wiki/ref-interactive-2.wiki create mode 100644 test/test-refile/wiki/ref-interactive-source.wiki create mode 100644 test/test-refile/wiki/ref-interactive-target.wiki create mode 100644 test/test-refile/wiki/ref-relation-after-1.wiki rename test/test-refile/wiki/{ref-by-anchor-before-1.wiki => ref-relation-before-1.wiki} (100%) rename test/test-refile/wiki/{ref-by-anchor-before-2.wiki => ref-relation-before-2.wiki} (100%) create mode 100644 test/test-refile/wiki/ref-relation-inside-1.wiki create mode 100644 test/test-refile/wiki/ref-relation-inside-2.wiki rename test/test-refile/wiki/{ref-by-lnum-3-source.wiki => ref-to-page-source.wiki} (50%) create mode 100644 test/test-refile/wiki/ref-to-page-target.wiki create mode 100644 test/test-refile/wiki/source-3.wiki diff --git a/autoload/wiki/buffer.vim b/autoload/wiki/buffer.vim index 74759fe4..fcd59a44 100644 --- a/autoload/wiki/buffer.vim +++ b/autoload/wiki/buffer.vim @@ -66,7 +66,8 @@ function! s:init_buffer_commands() abort " {{{1 command! -buffer WikiLinkIncomingToggle call wiki#link#incoming_display_toggle() command! -buffer WikiLinkIncomingHover call wiki#link#incoming_hover() command! -buffer WikiPageDelete call wiki#page#delete() - command! -buffer WikiPageRefile call wiki#page#refile() + command! -buffer -nargs=* -complete=customlist,wiki#complete#refile + \ WikiPageRefile call wiki#page#refile() command! -buffer WikiPageRename call wiki#page#rename() command! -buffer WikiPageRenameSection call wiki#page#rename_section() command! -buffer WikiToc call g:wiki_select_method.toc() diff --git a/autoload/wiki/complete.vim b/autoload/wiki/complete.vim index 3b75617d..87c008f1 100644 --- a/autoload/wiki/complete.vim +++ b/autoload/wiki/complete.vim @@ -15,10 +15,10 @@ endfunction " }}}1 function! wiki#complete#url(lead, line, pos) abort " {{{1 let l:parts = split(a:lead, '::') - if empty(l:parts) || len(l:parts) > 1 | return [] | endif + if len(l:parts) > 1 | return [] | endif let l:base = '' - let l:input = l:parts[0] + let l:input = get(l:parts, 0, '') let l:cnum = s:completer_wikilink.findstart('[[' . l:input) - 2 if l:cnum > 0 @@ -78,6 +78,70 @@ let s:graph_export_values = { \ '--output=': 'file', \} +" }}}1 +function! wiki#complete#refile(lead, line, pos) abort " {{{1 + " Gather the arguments that are already complete, i.e. all arguments before + " the one that is currently being typed + let l:line = strpart(a:line, 0, a:pos) + let l:args = split(l:line, '\%(\\\)\@= 0 && l:i < len(l:args) - 1 + \ ? substitute(l:args[l:i + 1], '\\\(.\)', '\1', 'g') + \ : '' + return s:filter_prefix( + \ map(wiki#toc#gather_anchors(l:page), { _, x -> escape(x, '\ ') }), + \ a:lead) + elseif l:option ==# '-lnum' + return [] + endif + + " Only complete the options that may still be used + let l:used = s:refile_used_options(l:args) + let l:candidates = filter(copy(s:refile_options), + \ { _, x -> index(l:used, x) < 0 }) + + " -lnum can not be combined with -anchor or -relation + if index(l:used, '-lnum') >= 0 + call filter(l:candidates, + \ { _, x -> index(['-anchor', '-relation'], x) < 0 }) + elseif index(l:used, '-anchor') >= 0 || index(l:used, '-relation') >= 0 + call filter(l:candidates, { _, x -> x !=# '-lnum' }) + endif + + return s:filter_prefix(l:candidates, a:lead) +endfunction + +let s:refile_options = ['-page', '-anchor', '-relation', '-lnum'] + +function! s:refile_used_options(args) abort + " Return the options that occur in a:args, skipping over their values + + let l:used = [] + let l:i = 0 + while l:i < len(a:args) + if index(s:refile_options, a:args[l:i]) >= 0 + call add(l:used, a:args[l:i]) + let l:i += 2 + else + let l:i += 1 + endif + endwhile + + return l:used +endfunction + " }}}1 function! wiki#complete#pages(lead, line, pos) abort " {{{1 return wiki#page#get_all(#{ @@ -282,11 +346,16 @@ endfunction function s:filter_candidates(cands, regex) " {{{1 " Filter list a:cands for a match with a:regex anywhere in items, with " case-sensitivity depending on the value of g:wiki_completion_case_sensitive - call filter(a:cands, {_,x -> match(x, + " The list is filtered in place and is also returned. + return filter(a:cands, {_,x -> match(x, \ (g:wiki_completion_case_sensitive ? '\C' : '\c') \ . a:regex) >= 0}) endfunction " }}}1 +function s:filter_prefix(cands, lead) " {{{1 + return filter(copy(a:cands), { _, x -> stridx(x, a:lead) == 0 }) +endfunction +" }}}1 " " Initialize module diff --git a/autoload/wiki/page.vim b/autoload/wiki/page.vim index fc9c518e..c0378507 100644 --- a/autoload/wiki/page.vim +++ b/autoload/wiki/page.vim @@ -164,25 +164,36 @@ endfunction " }}}1 function! wiki#page#refile(...) abort "{{{1 - let l:opts = extend(#{ - \ target_page: '', - \ target_lnum: 0, - \}, a:0 > 0 ? a:1 : {}) + " Move the section under the cursor to another location. + " + " Input: Either an option dictionary (see wiki#page#refile#default_opts()) + " or a list of command line arguments (see wiki-mappings-reference). + " + " If no target is specified, then the user is asked for a target page and + " a target section within that page. - " target_page could be given by user input with something like this: - " let l:opts.target_page = input('> ', '', 'customlist,wiki#complete#url') + try + let l:opts = a:0 > 0 && type(a:1) == v:t_dict + \ ? extend(wiki#page#refile#default_opts(), a:1) + \ : wiki#page#refile#parse_args(a:000) + call wiki#page#refile#validate_opts(l:opts) + + let l:source = wiki#page#refile#collect_source() + if empty(l:source) + throw 'wiki.vim: No source section recognized!' + endif - let l:source = wiki#page#refile#collect_source(l:opts) - if empty(l:source) - return wiki#log#error('No source section recognized!') - endif + if empty(l:opts.target_page) + \ && empty(l:opts.target_anchor) + \ && l:opts.target_lnum < 0 + let l:opts = wiki#page#refile#ask_for_target(l:opts, l:source) + if empty(l:opts) | return | endif + endif - try let l:target = wiki#page#refile#collect_target(l:opts, l:source) - catch /wiki.vim: target page not found/ - return wiki#log#error('Target page was not found!') - catch /wiki.vim: anchor not recognized/ - return wiki#log#error('Target anchor not recognized!') + call wiki#page#refile#check_levels(l:source, l:target) + catch /^wiki\.vim: / + return wiki#log#error(matchstr(v:exception, '^wiki\.vim: \zs.*')) endtry call wiki#log#info( diff --git a/autoload/wiki/page/refile.vim b/autoload/wiki/page/refile.vim index f3100b16..183804be 100644 --- a/autoload/wiki/page/refile.vim +++ b/autoload/wiki/page/refile.vim @@ -4,9 +4,77 @@ " Email: karl.yngve@gmail.com " -function! wiki#page#refile#collect_source(...) abort " {{{1 +function! wiki#page#refile#default_opts() abort " {{{1 + return #{ + \ target_page: '', + \ target_anchor: '', + \ target_relation: '', + \ target_lnum: -1, + \} +endfunction + +" }}}1 +function! wiki#page#refile#parse_args(args) abort " {{{1 + " Arguments: + " args: list of command line arguments + " Returns: + " opts: see wiki#page#refile#default_opts() + + let l:opts = wiki#page#refile#default_opts() + + let l:args = copy(a:args) + while !empty(l:args) + let l:arg = remove(l:args, 0) + + let l:key = get(s:arg_to_key, l:arg, '') + if empty(l:key) + throw printf( + \ 'wiki.vim: WikiPageRefile argument "%s" not recognized!', l:arg) + endif + + if empty(l:args) + throw printf('wiki.vim: Missing value for "%s"!', l:arg) + endif + + let l:value = remove(l:args, 0) + let l:opts[l:key] = l:key ==# 'target_lnum' ? str2nr(l:value) : l:value + endwhile + + return l:opts +endfunction + +let s:arg_to_key = { + \ '-page': 'target_page', + \ '-anchor': 'target_anchor', + \ '-relation': 'target_relation', + \ '-lnum': 'target_lnum', + \} + +" }}}1 +function! wiki#page#refile#validate_opts(opts) abort " {{{1 + if a:opts.target_lnum >= 0 + \ && (!empty(a:opts.target_anchor) || !empty(a:opts.target_relation)) + throw 'wiki.vim: Cannot combine -lnum with -anchor or -relation!' + endif + + if empty(a:opts.target_relation) | return | endif + + if empty(a:opts.target_anchor) + throw 'wiki.vim: Option -relation requires -anchor!' + endif + + if index(s:relations, a:opts.target_relation) < 0 + throw 'wiki.vim: Option -relation must be "inside", "before" or "after"!' + endif +endfunction + +let s:relations = ['inside', 'before', 'after'] + +" }}}1 + +function! wiki#page#refile#collect_source() abort " {{{1 " Returns: - " source: dict(path, lnum, lnum_end, header, anchor, level) + " source: dict(path, lnum, lnum_end, header, anchor, anchors, level) let l:source = wiki#toc#get_section() if !empty(l:source) @@ -16,118 +84,241 @@ function! wiki#page#refile#collect_source(...) abort " {{{1 return l:source endfunction +" }}}1 +function! wiki#page#refile#ask_for_target(opts, source) abort " {{{1 + " Ask the user for a target page and a target section within that page. + " + " Returns: + " opts: see wiki#page#refile#default_opts(); empty if the user aborted + + let l:page = wiki#ui#input(#{ + \ info: 'Refile to page [empty for current page]:', + \ completion: 'customlist,wiki#complete#url', + \}) + + let l:path = wiki#u#eval_filename(l:page) + if !filereadable(l:path) + throw 'wiki.vim: Target page was not found!' + endif + + let l:choice = wiki#ui#select( + \ wiki#page#refile#get_target_choices(l:path, a:source), #{ + \ prompt: 'Refile into section:', + \ auto_select: v:false, + \}) + if empty(l:choice) | return {} | endif + + let a:opts.target_page = l:page + let a:opts.target_anchor = l:choice ==# s:choice_page ? '' : l:choice + let a:opts.target_relation = empty(a:opts.target_anchor) ? '' : 'inside' + + return a:opts +endfunction + +let s:choice_page = '[end of page]' + +" }}}1 +function! wiki#page#refile#get_target_choices(path, source) abort " {{{1 + " Returns: + " The sections of a:path that are valid refile targets, i.e. all sections + " except the source section and its subsections + + let l:entries = wiki#toc#gather_entries(#{ path: a:path }) + if a:path ==# a:source.path + call filter(l:entries, + \ { _, x -> x.lnum < a:source.lnum || x.lnum > a:source.lnum_end }) + endif + + return [s:choice_page] + map(l:entries, 'v:val.anchor') +endfunction + " }}}1 function! wiki#page#refile#collect_target(opts, source) abort " {{{1 " Arguments: - " opts: dict( - " target_page, - " target_lnum, - " target_anchor_before?, - " ) - " source: dict(path, lnum, lnum_end, header, anchor) + " opts: see wiki#page#refile#default_opts() + " source: see wiki#page#refile#collect_source() " Returns: - " target: dict(path, lnum, anchor, level) + " target: dict(path, lnum, anchor, level, pad_before) let l:path = wiki#u#eval_filename(a:opts.target_page) if !filereadable(l:path) - throw 'wiki.vim: target page not found' + throw 'wiki.vim: Target page was not found!' endif + let l:lines = l:path ==# expand('%:p') ? getline(1, '$') : readfile(l:path) - if has_key(a:opts, 'target_anchor_before') - return s:collect_target_by_anchor_before( - \ l:path, - \ a:opts.target_anchor_before, - \ a:source - \) + if !empty(a:opts.target_anchor) + let l:target = s:collect_target_by_anchor(l:path, a:opts, a:source) + elseif a:opts.target_lnum >= 0 + let l:target = s:collect_target_by_lnum(l:path, a:opts.target_lnum, a:source) + else + let l:target = s:collect_target_at_end(l:path, l:lines, a:source) endif - return s:collect_target_by_lnum(l:path, a:opts.target_lnum, a:source) + " Separate the refiled section from the preceding line + let l:target.pad_before = l:target.lnum > 0 + \ && !empty(get(l:lines, l:target.lnum - 1, '')) + + return l:target +endfunction + +" }}}1 +function! wiki#page#refile#check_levels(source, target) abort " {{{1 + " Ensure that the adjusted header levels stay within the valid range. + + let l:delta = a:target.level - a:source.level + if l:delta <= 0 | return | endif + + let l:max_level = 0 + for l:entry in wiki#toc#gather_entries(#{ + \ lines: getline(a:source.lnum, a:source.lnum_end) }) + let l:max_level = max([l:max_level, l:entry.level]) + endfor + + if l:max_level + l:delta > 6 + throw 'wiki.vim: Refiling would exceed the maximum header level!' + endif endfunction " }}}1 function! wiki#page#refile#move(source, target) abort " {{{1 " Arguments: - " source: dict(path, lnum, lnum_end) - " target: dict(path, lnum) + " source: see wiki#page#refile#collect_source() + " target: see wiki#page#refile#collect_target() - if a:target.level < a:source.level - call execute(printf('%d,%dg/^#/normal! 0%dx', - \ a:source.lnum, a:source.lnum_end, a:source.level - a:target.level)) - elseif a:target.level > a:source.level - call execute(printf('%d,%dg/^#/normal! 0%di#', - \ a:source.lnum, a:source.lnum_end, a:target.level - a:source.level)) + let l:lines = getline(a:source.lnum, a:source.lnum_end) + call s:adjust_levels(l:lines, a:target.level - a:source.level) + if a:target.pad_before + let l:lines = [''] + l:lines endif if a:target.path ==# a:source.path - call execute(printf('%d,%dm %d', - \ a:source.lnum, a:source.lnum_end, a:target.lnum)) - silent write - else - let l:lines = getline(a:source.lnum, a:source.lnum_end) + " Account for the lines that are removed above the target position + let l:lnum = a:target.lnum >= a:source.lnum + \ ? a:target.lnum - (a:source.lnum_end - a:source.lnum + 1) + \ : a:target.lnum + call deletebufline('', a:source.lnum, a:source.lnum_end) + call append(l:lnum, l:lines) silent write + return + endif - let l:current_bufnr = bufnr('') - let l:was_loaded = bufloaded(a:target.path) - keepalt execute 'silent edit' fnameescape(a:target.path) - call append(a:target.lnum, l:lines) - silent write - if !l:was_loaded - keepalt execute 'bwipeout' - endif - keepalt execute 'buffer' l:current_bufnr + call deletebufline('', a:source.lnum, a:source.lnum_end) + silent write + + let l:current_bufnr = bufnr('') + let l:was_loaded = bufloaded(a:target.path) + keepalt execute 'silent edit' fnameescape(a:target.path) + call append(a:target.lnum, l:lines) + silent write + if !l:was_loaded + keepalt execute 'bwipeout' endif + keepalt execute 'buffer' l:current_bufnr endfunction " }}}1 -function! s:collect_target_by_anchor_before(path, anchor, source) abort " {{{1 - let l:section = {} - for l:section in wiki#toc#gather_entries(#{ path: a:path }) - if l:section.anchor ==# a:anchor +function! s:collect_target_by_anchor(path, opts, source) abort " {{{1 + let l:relation = empty(a:opts.target_relation) + \ ? 'inside' + \ : a:opts.target_relation + + let l:entries = wiki#toc#gather_entries(#{ path: a:path }) + + let l:index = -1 + for l:i in range(len(l:entries)) + if l:entries[l:i].anchor ==# a:opts.target_anchor + let l:index = l:i break endif endfor - if empty(l:section) - throw 'wiki.vim: anchor not recognized' + if l:index < 0 + throw 'wiki.vim: Target anchor not recognized!' endif + let l:section = l:entries[l:index] - let l:anchors = get(l:section, 'anchors', []) - if len(l:anchors) > 0 - call remove(l:anchors, -1) + if a:path ==# a:source.path + \ && l:section.lnum >= a:source.lnum + \ && l:section.lnum <= a:source.lnum_end + throw 'wiki.vim: Cannot refile a section into itself!' + endif + + if l:relation ==# 'inside' + " Place the section as the first subsection of the target section, i.e. + " below the text that belongs to the target section itself. The next entry + " is the first subsection if it is at a deeper level. + let l:next = get(l:entries, l:index + 1, {}) + let l:lnum = !empty(l:next) && l:next.level > l:section.level + \ ? l:next.lnum - 1 + \ : l:section.lnum_end + let l:anchors = l:section.anchors + [a:source.header] + elseif l:relation ==# 'before' + let l:lnum = l:section.lnum - 1 + let l:anchors = l:section.anchors[:-2] + [a:source.header] + else + let l:lnum = l:section.lnum_end + let l:anchors = l:section.anchors[:-2] + [a:source.header] endif - let l:anchors += [a:source.anchors[-1]] return #{ \ path: a:path, - \ lnum: l:section.lnum - 1, + \ lnum: l:lnum, \ anchor: '#' . join(l:anchors, '#'), - \ level: len(l:anchors) + \ level: len(l:anchors), \} endfunction " }}}1 function! s:collect_target_by_lnum(path, lnum, source) abort " {{{1 - let l:anchors = [a:source.anchors[-1]] + if a:path ==# a:source.path + \ && a:lnum >= a:source.lnum + \ && a:lnum < a:source.lnum_end + throw 'wiki.vim: Cannot refile a section into itself!' + endif + " Keep the source level, but promote the section if the target position is + " not nested deeply enough + let l:anchors = [a:source.header] if a:source.level > 1 let l:section = wiki#toc#get_section(#{ path: a:path, at_lnum: a:lnum }) let l:target_anchors = get(l:section, 'anchors', []) - call extend( - \ l:anchors, - \ l:target_anchors[:a:source.level - 2], - \ 0 - \) + call extend(l:anchors, l:target_anchors[:a:source.level - 2], 0) endif return #{ \ path: a:path, \ lnum: a:lnum, \ anchor: '#' . join(l:anchors, '#'), - \ level: len(l:anchors) + \ level: len(l:anchors), \} endfunction " }}}1 +function! s:collect_target_at_end(path, lines, source) abort " {{{1 + return #{ + \ path: a:path, + \ lnum: len(a:lines), + \ anchor: '#' . a:source.header, + \ level: 1, + \} +endfunction + +" }}}1 +function! s:adjust_levels(lines, delta) abort " {{{1 + " Shift the header level of every section in a:lines by a:delta + + if a:delta == 0 | return | endif + + let l:char = wiki#toc#get_header_char() + for l:entry in wiki#toc#gather_entries(#{ lines: a:lines }) + let l:index = l:entry.lnum - 1 + let a:lines[l:index] = a:delta > 0 + \ ? repeat(l:char, a:delta) . a:lines[l:index] + \ : a:lines[l:index][-a:delta :] + endfor +endfunction + +" }}}1 diff --git a/autoload/wiki/toc.vim b/autoload/wiki/toc.vim index 7dc7015b..32befb1f 100644 --- a/autoload/wiki/toc.vim +++ b/autoload/wiki/toc.vim @@ -188,6 +188,14 @@ let s:header_spec = { \} +function! wiki#toc#get_header_char(...) abort + " Returns: The character used to denote header levels, e.g. '#' + + let l:filetype = a:0 > 0 ? s:get_filetype(a:1) : s:get_filetype() + return get(get(s:header_spec, l:filetype, {}), 'anchor_char', '#') +endfunction + + function! wiki#toc#gather_anchors(...) abort let l:cache = wiki#cache#open('anchors', { \ 'local': 1, diff --git a/doc/wiki.txt b/doc/wiki.txt index 2528a830..e983fbe1 100644 --- a/doc/wiki.txt +++ b/doc/wiki.txt @@ -1767,6 +1767,7 @@ Note: For those who want to adjust the mappings, it may be useful to know that `n` wgo |(wiki-graph-out)| `n` wf |(wiki-link-transform)| `n` wd |(wiki-page-delete)| + `n` wq |(wiki-page-refile)| `n` wr |(wiki-page-rename)| `n` |(wiki-page-rename-section)| `n` |(wiki-toc)| @@ -2140,6 +2141,43 @@ MAPPINGS AND COMMANDS REFERENCE *wiki-mappings-reference* *WikiPageDelete* Delete wiki page. +*(wiki-page-refile)* +*WikiPageRefile* +`:WikiPageRefile [options]` + Move the section under the cursor, including its subsections, to another + location. All links to the moved section and its subsections are updated. + + Without options, the command asks for a target page and then for a target + section within that page. The section is moved into the selected section, + i.e. as if `-relation inside` was given. + + Available options are: + + -page PAGE ~ + Move the section to `PAGE`. Default is the current page. + + -anchor ANCHOR ~ + Move the section relative to the section given by `ANCHOR`, e.g. + `#Foo#Bar`. Without this option, the section is appended at the end of the + target page as a level 1 section. + + -relation REL ~ + Specify the placement relative to `-anchor`. `REL` may be one of: + + - `inside` As the first subsection of the target section, below + any text of the target section itself (default) + - `before` As a sibling immediately above the target section + - `after` As a sibling immediately below the target section + + The header levels of the moved section and its subsections are adjusted + to match the new position. This option requires `-anchor`. + + -lnum N ~ + Move the section below line `N` of the target page. The header levels are + kept, except that the section is promoted to fit the depth of the section + that contains line `N`. This option can not be combined with `-anchor` or + `-relation`. + *(wiki-page-rename)* *WikiPageRename* Rename wiki page (will update all links to the page). diff --git a/test/init.vim b/test/init.vim index bcef3825..188346c4 100644 --- a/test/init.vim +++ b/test/init.vim @@ -1,6 +1,7 @@ set nocompatible let &runtimepath = \ simplify(fnamemodify(expand(''), ':h') . '/..') + \ . ',' . simplify(fnamemodify(expand(''), ':h') . '/stub') \ . ',' . &runtimepath set noswapfile set nomore diff --git a/test/stub/autoload/wiki/ui/test.vim b/test/stub/autoload/wiki/ui/test.vim new file mode 100644 index 00000000..b932914a --- /dev/null +++ b/test/stub/autoload/wiki/ui/test.vim @@ -0,0 +1,16 @@ +" A wiki plugin for Vim -- test stub +" +" Maintainer: Karl Yngve Lervåg +" Email: karl.yngve@gmail.com +" +" Select this with `let g:wiki_ui_method.input = 'test'` to answer input +" prompts from a prepared list instead of asking the user. This is necessary +" because the prompt of input() is echoed even under `silent` and `redir`, +" which would pollute the output of the test suite. +" + +function! wiki#ui#test#input(options) abort " {{{1 + return get(g:, 'wiki_test_input', '') +endfunction + +" }}}1 diff --git a/test/test-complete/test-url.vim b/test/test-complete/test-url.vim new file mode 100644 index 00000000..1ad9ee94 --- /dev/null +++ b/test/test-complete/test-url.vim @@ -0,0 +1,21 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit ../wiki-basic/index.wiki + +" Complete page names. Note that this must return the list of candidates, not +" merely filter them in place. +call assert_equal(['pageA', 'pageB'], wiki#complete#url('page', 'page', 4)) + +" An empty lead completes every page +let s:candidates = wiki#complete#url('', '', 0) +call assert_equal(v:t_list, type(s:candidates)) +call assert_true(index(s:candidates, 'pageA') >= 0) +call assert_true(index(s:candidates, 'index') >= 0) + +" Complete anchors within a page +call assert_equal( + \ ['index#Intro', 'index#Section 1'], + \ wiki#complete#url('index#', 'index#', 6)) + +call wiki#test#finished() diff --git a/test/test-refile/test-bad-input.vim b/test/test-refile/test-bad-input.vim index ae32e29e..6209260e 100644 --- a/test/test-refile/test-bad-input.vim +++ b/test/test-refile/test-bad-input.vim @@ -3,22 +3,55 @@ runtime plugin/wiki.vim let g:wiki_log_verbose = 0 +let s:n = 0 +function! s:assert_error(msg) abort + let s:n += 1 + let l:log = wiki#log#get() + call assert_equal(s:n, len(l:log), 'Unexpected number of log entries') + if len(l:log) < s:n | return | endif + call assert_equal('error', l:log[s:n - 1].type) + call assert_equal(a:msg, l:log[s:n - 1].msg[0]) +endfunction + silent edit wiki-tmp/index.wiki -" Refile something not within a section should return with error message +" Refile something not within a section normal 2G silent call wiki#page#refile() -let s:log = wiki#log#get() -call assert_equal(1, len(s:log)) -call assert_equal('error', s:log[0].type) -call assert_equal('No source section recognized!', s:log[0].msg[0]) +call s:assert_error('No source section recognized!') -" Refile to nonexisting target should return with error message normal! 13G + +" Refile to a nonexisting page silent call wiki#page#refile(#{target_page: 'targetDoesNotExist'}) -let s:log = wiki#log#get() -call assert_equal(2, len(s:log)) -call assert_equal('error', s:log[1].type) -call assert_equal('Target page was not found!', s:log[1].msg[0]) +call s:assert_error('Target page was not found!') + +" Refile to a nonexisting anchor +silent call wiki#page#refile(#{target_anchor: '#No#Such#Section'}) +call s:assert_error('Target anchor not recognized!') + +" Refile a section into itself +silent call wiki#page#refile(#{target_anchor: '#Section 1'}) +call s:assert_error('Cannot refile a section into itself!') +silent call wiki#page#refile(#{target_anchor: '#Section 1#Foo bar Baz'}) +call s:assert_error('Cannot refile a section into itself!') +silent call wiki#page#refile(#{target_lnum: 13}) +call s:assert_error('Cannot refile a section into itself!') + +" Refile beyond the maximum header level +silent edit wiki-tmp/deep.wiki +normal! 3G +silent call wiki#page#refile(#{ + \ target_anchor: '#Target#Deep target', + \ target_relation: 'inside' + \}) +call s:assert_error('Refiling would exceed the maximum header level!') + +" None of the above should have modified any file +for s:file in ['index', 'deep'] + call assert_equal( + \ readfile('wiki/' . s:file . '.wiki'), + \ readfile('wiki-tmp/' . s:file . '.wiki')) +endfor call wiki#test#finished() diff --git a/test/test-refile/test-by-lnum-2.vim b/test/test-refile/test-by-lnum-2.vim index e15abbee..2b950c40 100644 --- a/test/test-refile/test-by-lnum-2.vim +++ b/test/test-refile/test-by-lnum-2.vim @@ -2,8 +2,11 @@ source ../init.vim runtime plugin/wiki.vim silent edit wiki-tmp/index.wiki -normal! 13G -silent call wiki#page#refile(#{target_page: 'target-1'}) +normal! 15G +silent call wiki#page#refile(#{ + \ target_page: 'target-2', + \ target_lnum: 10 + \}) " Check that content was properly moved call assert_equal( @@ -11,20 +14,14 @@ call assert_equal( \ readfile('wiki-tmp/index.wiki')) call assert_equal( \ readfile('wiki-tmp/ref-by-lnum-2-target.wiki'), - \ readfile('wiki-tmp/target-1.wiki')) + \ readfile('wiki-tmp/target-2.wiki')) " Check that all links to the previous location are updated call assert_equal( - \ '[[target-1#Section 1]]', - \ readfile('wiki-tmp/index.wiki')[6]) -call assert_equal( - \ '[[target-1#Section 1#Foo bar Baz]]', + \ '[[target-2#Second#Foo bar Baz]]', \ readfile('wiki-tmp/index.wiki')[7]) call assert_equal( - \ '[[target-1#Section 1]]', - \ readfile('wiki-tmp/links.wiki')[7]) -call assert_equal( - \ '[[target-1#Section 1#Foo bar Baz]]', + \ '[[target-2#Second#Foo bar Baz]]', \ readfile('wiki-tmp/links.wiki')[8]) call wiki#test#finished() diff --git a/test/test-refile/test-by-lnum-3.vim b/test/test-refile/test-by-lnum-3.vim deleted file mode 100644 index 14b7b93c..00000000 --- a/test/test-refile/test-by-lnum-3.vim +++ /dev/null @@ -1,27 +0,0 @@ -source ../init.vim -runtime plugin/wiki.vim - -silent edit wiki-tmp/index.wiki -normal! 15G -silent call wiki#page#refile(#{ - \ target_page: 'target-2', - \ target_lnum: 10 - \}) - -" Check that content was properly moved -call assert_equal( - \ readfile('wiki-tmp/ref-by-lnum-3-source.wiki'), - \ readfile('wiki-tmp/index.wiki')) -call assert_equal( - \ readfile('wiki-tmp/ref-by-lnum-3-target.wiki'), - \ readfile('wiki-tmp/target-2.wiki')) - -" Check that all links to the previous location are updated -call assert_equal( - \ '[[target-2#Second#Foo bar Baz]]', - \ readfile('wiki-tmp/index.wiki')[7]) -call assert_equal( - \ '[[target-2#Second#Foo bar Baz]]', - \ readfile('wiki-tmp/links.wiki')[8]) - -call wiki#test#finished() diff --git a/test/test-refile/test-code-block.vim b/test/test-refile/test-code-block.vim new file mode 100644 index 00000000..6e8bc42f --- /dev/null +++ b/test/test-refile/test-code-block.vim @@ -0,0 +1,17 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/source-3.wiki +normal! 3G +silent call wiki#page#refile(#{ + \ target_anchor: '#Other#Sub', + \ target_relation: 'inside' + \}) + +" Header levels are adjusted, but header-like lines inside fenced code blocks +" are left alone +call assert_equal( + \ readfile('wiki-tmp/ref-code-block.wiki'), + \ readfile('wiki-tmp/source-3.wiki')) + +call wiki#test#finished() diff --git a/test/test-refile/test-command.vim b/test/test-refile/test-command.vim new file mode 100644 index 00000000..6e361443 --- /dev/null +++ b/test/test-refile/test-command.vim @@ -0,0 +1,30 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/index.wiki +normal! 13G +silent WikiPageRefile -page target-2 -anchor #First#Foo -relation after + +" Check that content was properly moved +call assert_equal( + \ readfile('wiki-tmp/ref-command-source.wiki'), + \ readfile('wiki-tmp/index.wiki')) +call assert_equal( + \ readfile('wiki-tmp/ref-command-target.wiki'), + \ readfile('wiki-tmp/target-2.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ '[[target-2#First#Section 1]]', + \ readfile('wiki-tmp/index.wiki')[6]) +call assert_equal( + \ '[[target-2#First#Section 1#Foo bar Baz]]', + \ readfile('wiki-tmp/index.wiki')[7]) +call assert_equal( + \ '[[target-2#First#Section 1]]', + \ readfile('wiki-tmp/links.wiki')[7]) +call assert_equal( + \ '[[target-2#First#Section 1#Foo bar Baz]]', + \ readfile('wiki-tmp/links.wiki')[8]) + +call wiki#test#finished() diff --git a/test/test-refile/test-completion.vim b/test/test-refile/test-completion.vim new file mode 100644 index 00000000..00277c0b --- /dev/null +++ b/test/test-refile/test-completion.vim @@ -0,0 +1,51 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/index.wiki + +function! s:complete(line) abort + let l:lead = matchstr(a:line, '\%(\\\)\@ s:msg | silent call wiki#page#refile() | redir END + +" Check that content was properly moved +call assert_equal( + \ readfile('wiki-tmp/ref-interactive-source.wiki'), + \ readfile('wiki-tmp/index.wiki')) +call assert_equal( + \ readfile('wiki-tmp/ref-interactive-target.wiki'), + \ readfile('wiki-tmp/target-2.wiki')) + +call wiki#test#finished() diff --git a/test/test-refile/test-interactive-2.vim b/test/test-refile/test-interactive-2.vim new file mode 100644 index 00000000..0505225e --- /dev/null +++ b/test/test-refile/test-interactive-2.vim @@ -0,0 +1,45 @@ +source ../init.vim +runtime plugin/wiki.vim + +let g:wiki_log_verbose = 0 + +" Stub the page prompt. The echo of input() can not be silenced, so the real +" prompt would pollute the output of the test suite. +let g:wiki_ui_method = #{ + \ confirm: 'legacy', + \ input: 'test', + \ select: 'legacy', + \} + +silent edit wiki-tmp/index.wiki +normal! 13G + +" The source section and its subsections are not offered as targets +let s:source = wiki#page#refile#collect_source() +call assert_equal( + \ ['[end of page]', '#Intro'], + \ wiki#page#refile#get_target_choices(s:source.path, s:source)) + +" Abort the section menu +call feedkeys('x', 'nt') +redir => s:msg | silent call wiki#page#refile() | redir END + +call assert_equal([], wiki#log#get()) +call assert_equal( + \ readfile('wiki/index.wiki'), + \ readfile('wiki-tmp/index.wiki')) + +" Select "#Intro" from the section menu +call feedkeys('2', 'nt') +redir => s:msg | silent call wiki#page#refile() | redir END + +call assert_equal( + \ readfile('wiki-tmp/ref-interactive-2.wiki'), + \ readfile('wiki-tmp/index.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ '[[index#Intro#Section 1]]', + \ readfile('wiki-tmp/links.wiki')[7]) + +call wiki#test#finished() diff --git a/test/test-refile/test-link-boundaries.vim b/test/test-refile/test-link-boundaries.vim new file mode 100644 index 00000000..7779ea92 --- /dev/null +++ b/test/test-refile/test-link-boundaries.vim @@ -0,0 +1,32 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/boundaries.wiki +normal! 7G +silent call wiki#page#refile(#{ + \ target_anchor: '#Intro', + \ target_relation: 'inside' + \}) + +let s:page = readfile('wiki-tmp/boundaries.wiki') +let s:other = readfile('wiki-tmp/boundaries-links.wiki') + +" Links to the moved section and to its subsections are updated. Note that the +" anchor ends in a non-word character, and that both links on the line must be +" updated exactly once. +call assert_equal( + \ '[[#Intro#A.B (v2)]] and [[#Intro#A.B (v2)#Sub]]', s:page[2]) +call assert_equal( + \ '[[boundaries#Intro#A.B (v2)]] and [[boundaries#Intro#A.B (v2)#Sub]]', + \ s:other[2]) + +" The anchor is not a regular expression, so "#A.B …" must not match "#AxB …" +call assert_equal('[[#AxB (v2)]]', s:page[3]) +call assert_equal('[[boundaries#AxB (v2)]]', s:other[3]) + +" An anchor ends at a subanchor or at the end of the link, not at a word +" boundary, so "#A.B (v2)" must not match within "#A.B (v2) extra" +call assert_equal('[[#A.B (v2) extra]]', s:page[4]) +call assert_equal('[[boundaries#A.B (v2) extra]]', s:other[4]) + +call wiki#test#finished() diff --git a/test/test-refile/test-relation-after-1.vim b/test/test-refile/test-relation-after-1.vim new file mode 100644 index 00000000..bde150fb --- /dev/null +++ b/test/test-refile/test-relation-after-1.vim @@ -0,0 +1,24 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/source-1.wiki +normal! 10G +silent call wiki#page#refile(#{ + \ target_anchor: '#Tasks', + \ target_relation: 'after' + \}) + +" Check that the section and its subsection were moved and promoted +call assert_equal( + \ readfile('wiki-tmp/ref-relation-after-1.wiki'), + \ readfile('wiki-tmp/source-1.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ 'Link to: [[#Bar]]', + \ readfile('wiki-tmp/source-1.wiki')[1]) +call assert_equal( + \ '[[source-1#Bar#Subheading]]', + \ readfile('wiki-tmp/links.wiki')[10]) + +call wiki#test#finished() diff --git a/test/test-refile/test-by-anchor-before-1.vim b/test/test-refile/test-relation-before-1.vim similarity index 51% rename from test/test-refile/test-by-anchor-before-1.vim rename to test/test-refile/test-relation-before-1.vim index b805b541..6b609c14 100644 --- a/test/test-refile/test-by-anchor-before-1.vim +++ b/test/test-refile/test-relation-before-1.vim @@ -3,9 +3,12 @@ runtime plugin/wiki.vim silent edit wiki-tmp/index.wiki normal! 13G -silent call wiki#page#refile(#{target_anchor_before: '#Intro'}) +silent call wiki#page#refile(#{ + \ target_anchor: '#Intro', + \ target_relation: 'before' + \}) call assert_equal( - \ readfile('wiki-tmp/ref-by-anchor-before-1.wiki'), + \ readfile('wiki-tmp/ref-relation-before-1.wiki'), \ readfile('wiki-tmp/index.wiki')) call wiki#test#finished() diff --git a/test/test-refile/test-by-anchor-before-2.vim b/test/test-refile/test-relation-before-2.vim similarity index 66% rename from test/test-refile/test-by-anchor-before-2.vim rename to test/test-refile/test-relation-before-2.vim index c0a83b02..704842d0 100644 --- a/test/test-refile/test-by-anchor-before-2.vim +++ b/test/test-refile/test-relation-before-2.vim @@ -5,10 +5,11 @@ silent edit wiki-tmp/source-2.wiki normal! 8G silent call wiki#page#refile(#{ \ target_page: 'target-2', - \ target_anchor_before: '#First#Foo' + \ target_anchor: '#First#Foo', + \ target_relation: 'before' \}) call assert_equal( - \ readfile('wiki-tmp/ref-by-anchor-before-2.wiki'), + \ readfile('wiki-tmp/ref-relation-before-2.wiki'), \ readfile('wiki-tmp/target-2.wiki')) call wiki#test#finished() diff --git a/test/test-refile/test-relation-inside-1.vim b/test/test-refile/test-relation-inside-1.vim new file mode 100644 index 00000000..288852ef --- /dev/null +++ b/test/test-refile/test-relation-inside-1.vim @@ -0,0 +1,24 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/source-1.wiki +normal! 10G +silent call wiki#page#refile(#{ + \ target_anchor: '#Tasks#Baz', + \ target_relation: 'inside' + \}) + +" Check that the section and its subsection were moved and demoted +call assert_equal( + \ readfile('wiki-tmp/ref-relation-inside-1.wiki'), + \ readfile('wiki-tmp/source-1.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ 'Link to: [[#Tasks#Baz#Bar]]', + \ readfile('wiki-tmp/source-1.wiki')[1]) +call assert_equal( + \ '[[source-1#Tasks#Baz#Bar#Subheading]]', + \ readfile('wiki-tmp/links.wiki')[10]) + +call wiki#test#finished() diff --git a/test/test-refile/test-relation-inside-2.vim b/test/test-refile/test-relation-inside-2.vim new file mode 100644 index 00000000..a1cc2871 --- /dev/null +++ b/test/test-refile/test-relation-inside-2.vim @@ -0,0 +1,18 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/nested.wiki +normal! 9G +silent call wiki#page#refile(#{ + \ target_anchor: '#Target', + \ target_relation: 'inside' + \}) + +" The target section has both its own text and a subsection. The refiled +" section must end up below the text that belongs to the target section, but +" above the subsections of the target section. +call assert_equal( + \ readfile('wiki-tmp/ref-relation-inside-2.wiki'), + \ readfile('wiki-tmp/nested.wiki')) + +call wiki#test#finished() diff --git a/test/test-refile/test-to-page.vim b/test/test-refile/test-to-page.vim new file mode 100644 index 00000000..623a8d66 --- /dev/null +++ b/test/test-refile/test-to-page.vim @@ -0,0 +1,30 @@ +source ../init.vim +runtime plugin/wiki.vim + +silent edit wiki-tmp/index.wiki +normal! 13G +silent call wiki#page#refile(#{target_page: 'target-1'}) + +" Check that content was properly moved +call assert_equal( + \ readfile('wiki-tmp/ref-to-page-source.wiki'), + \ readfile('wiki-tmp/index.wiki')) +call assert_equal( + \ readfile('wiki-tmp/ref-to-page-target.wiki'), + \ readfile('wiki-tmp/target-1.wiki')) + +" Check that all links to the previous location are updated +call assert_equal( + \ '[[target-1#Section 1]]', + \ readfile('wiki-tmp/index.wiki')[6]) +call assert_equal( + \ '[[target-1#Section 1#Foo bar Baz]]', + \ readfile('wiki-tmp/index.wiki')[7]) +call assert_equal( + \ '[[target-1#Section 1]]', + \ readfile('wiki-tmp/links.wiki')[7]) +call assert_equal( + \ '[[target-1#Section 1#Foo bar Baz]]', + \ readfile('wiki-tmp/links.wiki')[8]) + +call wiki#test#finished() diff --git a/test/test-refile/wiki/boundaries-links.wiki b/test/test-refile/wiki/boundaries-links.wiki new file mode 100644 index 00000000..ccfcf77d --- /dev/null +++ b/test/test-refile/wiki/boundaries-links.wiki @@ -0,0 +1,5 @@ +# Links + +[[boundaries#A.B (v2)]] and [[boundaries#A.B (v2)#Sub]] +[[boundaries#AxB (v2)]] +[[boundaries#A.B (v2) extra]] diff --git a/test/test-refile/wiki/boundaries.wiki b/test/test-refile/wiki/boundaries.wiki new file mode 100644 index 00000000..a2bf6cf1 --- /dev/null +++ b/test/test-refile/wiki/boundaries.wiki @@ -0,0 +1,20 @@ +# Intro + +[[#A.B (v2)]] and [[#A.B (v2)#Sub]] +[[#AxB (v2)]] +[[#A.B (v2) extra]] + +# A.B (v2) + +## Sub + +Content A. + +# AxB (v2) + +Content B. + +# A.B (v2) extra + +Content C. + diff --git a/test/test-refile/wiki/deep.wiki b/test/test-refile/wiki/deep.wiki new file mode 100644 index 00000000..397770da --- /dev/null +++ b/test/test-refile/wiki/deep.wiki @@ -0,0 +1,16 @@ +# A + +## B + +### C + +#### D + +##### E + +###### F + +# Target + +## Deep target + diff --git a/test/test-refile/wiki/nested.wiki b/test/test-refile/wiki/nested.wiki new file mode 100644 index 00000000..43f03aad --- /dev/null +++ b/test/test-refile/wiki/nested.wiki @@ -0,0 +1,16 @@ +# Target + +Intro text for target. + +## Existing + +Existing text. + +# Source + +Source text. + +## Sub + +Sub text. + diff --git a/test/test-refile/wiki/ref-by-lnum-2-source.wiki b/test/test-refile/wiki/ref-by-lnum-2-source.wiki index 65e2d1a8..2b05bcc1 100644 --- a/test/test-refile/wiki/ref-by-lnum-2-source.wiki +++ b/test/test-refile/wiki/ref-by-lnum-2-source.wiki @@ -4,8 +4,10 @@ Intro text here. This is a wiki. -[[target-1#Section 1]] -[[target-1#Section 1#Foo bar Baz]] +[[#Section 1]] +[[target-2#Second#Foo bar Baz]] This-is-a-wiki. +# Section 1 + diff --git a/test/test-refile/wiki/ref-by-lnum-2-target.wiki b/test/test-refile/wiki/ref-by-lnum-2-target.wiki index 931e84eb..b99dedbb 100644 --- a/test/test-refile/wiki/ref-by-lnum-2-target.wiki +++ b/test/test-refile/wiki/ref-by-lnum-2-target.wiki @@ -1,15 +1,13 @@ -# Section 1 +# First -## Foo bar Baz - -# Section 1 +## Foo -## Subsection A +## Bar -## Subsection B +# Second -# Section 2 +## Baz -## Subsection A +## Foo bar Baz -## Subsection B +## Foobar diff --git a/test/test-refile/wiki/ref-code-block.wiki b/test/test-refile/wiki/ref-code-block.wiki new file mode 100644 index 00000000..d48990f9 --- /dev/null +++ b/test/test-refile/wiki/ref-code-block.wiki @@ -0,0 +1,17 @@ +# Notes + +# Other + +## Sub + +Text. + +### Snippet + +```sh +# not a header +echo foo +``` + +#### Detail + diff --git a/test/test-refile/wiki/ref-command-source.wiki b/test/test-refile/wiki/ref-command-source.wiki new file mode 100644 index 00000000..36ec043a --- /dev/null +++ b/test/test-refile/wiki/ref-command-source.wiki @@ -0,0 +1,11 @@ +Intro text here. + +# Intro + +This is a wiki. + +[[target-2#First#Section 1]] +[[target-2#First#Section 1#Foo bar Baz]] + +This-is-a-wiki. + diff --git a/test/test-refile/wiki/ref-by-lnum-3-target.wiki b/test/test-refile/wiki/ref-command-target.wiki similarity index 64% rename from test/test-refile/wiki/ref-by-lnum-3-target.wiki rename to test/test-refile/wiki/ref-command-target.wiki index b99dedbb..9fa98a86 100644 --- a/test/test-refile/wiki/ref-by-lnum-3-target.wiki +++ b/test/test-refile/wiki/ref-command-target.wiki @@ -2,12 +2,14 @@ ## Foo +## Section 1 + +### Foo bar Baz + ## Bar # Second ## Baz -## Foo bar Baz - ## Foobar diff --git a/test/test-refile/wiki/ref-interactive-2.wiki b/test/test-refile/wiki/ref-interactive-2.wiki new file mode 100644 index 00000000..5fa72627 --- /dev/null +++ b/test/test-refile/wiki/ref-interactive-2.wiki @@ -0,0 +1,15 @@ +Intro text here. + +# Intro + +This is a wiki. + +[[#Intro#Section 1]] +[[#Intro#Section 1#Foo bar Baz]] + +This-is-a-wiki. + +## Section 1 + +### Foo bar Baz + diff --git a/test/test-refile/wiki/ref-interactive-source.wiki b/test/test-refile/wiki/ref-interactive-source.wiki new file mode 100644 index 00000000..7176f2ea --- /dev/null +++ b/test/test-refile/wiki/ref-interactive-source.wiki @@ -0,0 +1,11 @@ +Intro text here. + +# Intro + +This is a wiki. + +[[target-2#Second#Section 1]] +[[target-2#Second#Section 1#Foo bar Baz]] + +This-is-a-wiki. + diff --git a/test/test-refile/wiki/ref-interactive-target.wiki b/test/test-refile/wiki/ref-interactive-target.wiki new file mode 100644 index 00000000..f989f180 --- /dev/null +++ b/test/test-refile/wiki/ref-interactive-target.wiki @@ -0,0 +1,15 @@ +# First + +## Foo + +## Bar + +# Second + +## Section 1 + +### Foo bar Baz + +## Baz + +## Foobar diff --git a/test/test-refile/wiki/ref-relation-after-1.wiki b/test/test-refile/wiki/ref-relation-after-1.wiki new file mode 100644 index 00000000..45e16c91 --- /dev/null +++ b/test/test-refile/wiki/ref-relation-after-1.wiki @@ -0,0 +1,26 @@ +# Inbox +Link to: [[#Bar]] +## Foo + +Random text here. + +# Tasks + +## Baz + +Even more random text here. + +# Bar + +More random text here. + +## Subheading + +More here. + +# Someday + +## Qux + +More text. + diff --git a/test/test-refile/wiki/ref-by-anchor-before-1.wiki b/test/test-refile/wiki/ref-relation-before-1.wiki similarity index 100% rename from test/test-refile/wiki/ref-by-anchor-before-1.wiki rename to test/test-refile/wiki/ref-relation-before-1.wiki diff --git a/test/test-refile/wiki/ref-by-anchor-before-2.wiki b/test/test-refile/wiki/ref-relation-before-2.wiki similarity index 100% rename from test/test-refile/wiki/ref-by-anchor-before-2.wiki rename to test/test-refile/wiki/ref-relation-before-2.wiki diff --git a/test/test-refile/wiki/ref-relation-inside-1.wiki b/test/test-refile/wiki/ref-relation-inside-1.wiki new file mode 100644 index 00000000..6a17db5f --- /dev/null +++ b/test/test-refile/wiki/ref-relation-inside-1.wiki @@ -0,0 +1,26 @@ +# Inbox +Link to: [[#Tasks#Baz#Bar]] +## Foo + +Random text here. + +# Tasks + +## Baz + +Even more random text here. + +### Bar + +More random text here. + +#### Subheading + +More here. + +# Someday + +## Qux + +More text. + diff --git a/test/test-refile/wiki/ref-relation-inside-2.wiki b/test/test-refile/wiki/ref-relation-inside-2.wiki new file mode 100644 index 00000000..30c05b63 --- /dev/null +++ b/test/test-refile/wiki/ref-relation-inside-2.wiki @@ -0,0 +1,16 @@ +# Target + +Intro text for target. + +## Source + +Source text. + +### Sub + +Sub text. + +## Existing + +Existing text. + diff --git a/test/test-refile/wiki/ref-by-lnum-3-source.wiki b/test/test-refile/wiki/ref-to-page-source.wiki similarity index 50% rename from test/test-refile/wiki/ref-by-lnum-3-source.wiki rename to test/test-refile/wiki/ref-to-page-source.wiki index 2b05bcc1..65e2d1a8 100644 --- a/test/test-refile/wiki/ref-by-lnum-3-source.wiki +++ b/test/test-refile/wiki/ref-to-page-source.wiki @@ -4,10 +4,8 @@ Intro text here. This is a wiki. -[[#Section 1]] -[[target-2#Second#Foo bar Baz]] +[[target-1#Section 1]] +[[target-1#Section 1#Foo bar Baz]] This-is-a-wiki. -# Section 1 - diff --git a/test/test-refile/wiki/ref-to-page-target.wiki b/test/test-refile/wiki/ref-to-page-target.wiki new file mode 100644 index 00000000..ee40a968 --- /dev/null +++ b/test/test-refile/wiki/ref-to-page-target.wiki @@ -0,0 +1,16 @@ +# Section 1 + +## Subsection A + +## Subsection B + +# Section 2 + +## Subsection A + +## Subsection B + +# Section 1 + +## Foo bar Baz + diff --git a/test/test-refile/wiki/source-3.wiki b/test/test-refile/wiki/source-3.wiki new file mode 100644 index 00000000..a2e60efd --- /dev/null +++ b/test/test-refile/wiki/source-3.wiki @@ -0,0 +1,17 @@ +# Notes + +## Snippet + +```sh +# not a header +echo foo +``` + +### Detail + +# Other + +## Sub + +Text. +