diff --git a/autoload/wiki/buffer.vim b/autoload/wiki/buffer.vim index 3a8c5f6..df7699d 100644 --- a/autoload/wiki/buffer.vim +++ b/autoload/wiki/buffer.vim @@ -62,6 +62,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() @@ -113,6 +114,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 @@ -175,6 +177,7 @@ function! s:init_buffer_mappings() abort " {{{1 \ '(wiki-link-incoming-toggle)': 'wli', \ '(wiki-link-incoming-hover)': 'wlI', \ '(wiki-page-delete)': 'wd', + \ '(wiki-page-refile)' : 'wq', \ '(wiki-page-rename)': 'wr', \ '(wiki-page-rename-section)': '', \ '(wiki-toc-generate)': 'wt', diff --git a/autoload/wiki/page.vim b/autoload/wiki/page.vim index cd1239f..e4e8c4f 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) @@ -287,14 +320,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 0000000..f3100b1 --- /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 diff --git a/test/test-refile/Makefile b/test/test-refile/Makefile new file mode 100644 index 0000000..07a4101 --- /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 0000000..ae32e29 --- /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 0000000..b805b54 --- /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 0000000..c0a83b0 --- /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 0000000..9ff6fbc --- /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 0000000..e15abbe --- /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 0000000..14b7b93 --- /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 0000000..fdaa3fd --- /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 0000000..5abbaeb --- /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 0000000..a9ee119 --- /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 0000000..7c567d6 --- /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 0000000..65e2d1a --- /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 0000000..931e84e --- /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 0000000..2b05bcc --- /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 0000000..b99dedb --- /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 0000000..78ad988 --- /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 0000000..b193024 --- /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 0000000..2ef92b8 --- /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 0000000..2ef92b8 --- /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 0000000..3f69201 --- /dev/null +++ b/test/test-refile/wiki/target-2.wiki @@ -0,0 +1,11 @@ +# First + +## Foo + +## Bar + +# Second + +## Baz + +## Foobar