From fc158ba744a7a9657d91631efb6591c4f3c08807 Mon Sep 17 00:00:00 2001 From: Steven Stallion Date: Thu, 21 Mar 2019 18:11:03 -0500 Subject: [PATCH] Support automatic generation of links and tags --- autoload/vimwiki/base.vim | 151 ++++++++++++++++++++----------------- autoload/vimwiki/diary.vim | 141 +++++++++++++++++----------------- autoload/vimwiki/tags.vim | 113 ++++++++++++++------------- autoload/vimwiki/vars.vim | 2 + doc/vimwiki.txt | 22 ++++++ ftplugin/vimwiki.vim | 19 ++++- 6 files changed, 257 insertions(+), 191 deletions(-) diff --git a/autoload/vimwiki/base.vim b/autoload/vimwiki/base.vim index a76962a..152dd50 100644 --- a/autoload/vimwiki/base.vim +++ b/autoload/vimwiki/base.vim @@ -346,42 +346,47 @@ function! vimwiki#base#get_globlinks_escaped() abort endfunction -function! vimwiki#base#generate_links() - let lines = [] +function! vimwiki#base#generate_links(create) - let links = vimwiki#base#get_wikilinks(vimwiki#vars#get_bufferlocal('wiki_nr'), 0) - call sort(links) + function! Generator() closure + let lines = [] - let bullet = repeat(' ', vimwiki#lst#get_list_margin()) . vimwiki#lst#default_symbol().' ' - for link in links - let link_infos = vimwiki#base#resolve_link(link) - if !s:is_diary_file(link_infos.filename) - if vimwiki#vars#get_wikilocal('syntax') == 'markdown' - let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink1Template') - else - let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate1') + let links = vimwiki#base#get_wikilinks(vimwiki#vars#get_bufferlocal('wiki_nr'), 0) + call sort(links) + + let bullet = repeat(' ', vimwiki#lst#get_list_margin()) . vimwiki#lst#default_symbol().' ' + for link in links + let link_infos = vimwiki#base#resolve_link(link) + if !s:is_diary_file(link_infos.filename) + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink1Template') + else + let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate1') + endif + + let link_caption = vimwiki#base#read_caption(link_infos.filename) + if link_caption == '' " default to link if caption not found + let link_caption = link + endif + + let entry = s:safesubstitute(link_tpl, '__LinkUrl__', link, '') + let entry = s:safesubstitute(entry, '__LinkDescription__', link_caption, '') + call add(lines, bullet. entry) endif + endfor - let link_caption = vimwiki#base#read_caption(link_infos.filename) - if link_caption == '' " default to link if caption not found - let link_caption = link - endif - - let entry = s:safesubstitute(link_tpl, '__LinkUrl__', link, '') - let entry = s:safesubstitute(entry, '__LinkDescription__', link_caption, '') - call add(lines, bullet. entry) - endif - endfor + return lines + endfunction let links_rx = '\%(^\s*$\)\|\%('.vimwiki#vars#get_syntaxlocal('rxListBullet').'\)' call vimwiki#base#update_listing_in_buffer( - \ lines, + \ funcref('Generator'), \ vimwiki#vars#get_global('links_header'), \ links_rx, \ line('$')+1, \ vimwiki#vars#get_global('links_header_level'), - \ 1) + \ a:create) endfunction @@ -1043,13 +1048,13 @@ endfunction " creates or updates auto-generated listings in a wiki file, like TOC, diary " links, tags list etc. -" - the listing consists of a header and a list of strings as content +" - the listing consists of a header and a list of strings provided by a funcref " - a:content_regex is used to determine how long a potentially existing list is " - a:default_lnum is the line number where the new listing should be placed if " it's not already present " - if a:create is true, it will be created if it doesn't exist, otherwise it " will only be updated if it already exists -function! vimwiki#base#update_listing_in_buffer(strings, start_header, +function! vimwiki#base#update_listing_in_buffer(Generator, start_header, \ content_regex, default_lnum, header_level, create) " Vim behaves strangely when files change while in diff mode if &diff || &readonly @@ -1129,7 +1134,7 @@ function! vimwiki#base#update_listing_in_buffer(strings, start_header, let lines_diff += 1 endfor endif - for string in a:strings + for string in a:Generator() keepjumps call append(start_lnum - 1, string) let start_lnum += 1 let lines_diff += 1 @@ -1757,6 +1762,11 @@ function! s:collect_headers() if line_content !~# vimwiki#vars#get_syntaxlocal('rxHeader') continue endif + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + if stridx(line_content, vimwiki#vars#get_syntaxlocal('rxH')) > 0 + continue " markdown headers must start in the first column + endif + endif let header_level = vimwiki#u#count_first_sym(line_content) let header_text = \ vimwiki#u#trim(matchstr(line_content, vimwiki#vars#get_syntaxlocal('rxHeader'))) @@ -1879,55 +1889,60 @@ function! vimwiki#base#table_of_contents(create) endif endif - let numbering = vimwiki#vars#get_global('html_header_numbering') - let headers_levels = [['', 0], ['', 0], ['', 0], ['', 0], ['', 0], ['', 0]] - let complete_header_infos = [] - for header in headers - let h_text = header[2] - let h_level = header[1] - if h_text ==# toc_header_text " don't include the TOC's header itself - continue - endif - let headers_levels[h_level-1] = [h_text, headers_levels[h_level-1][1]+1] - for idx in range(h_level, 5) | let headers_levels[idx] = ['', 0] | endfor - - let h_complete_id = '' - for l in range(h_level-1) - if headers_levels[l][0] != '' - let h_complete_id .= headers_levels[l][0].'#' + function! Generator() closure + let numbering = vimwiki#vars#get_global('html_header_numbering') + let headers_levels = [['', 0], ['', 0], ['', 0], ['', 0], ['', 0], ['', 0]] + let complete_header_infos = [] + for header in headers + let h_text = header[2] + let h_level = header[1] + " don't include the TOC's header itself + if h_text ==# toc_header_text + continue endif + let headers_levels[h_level-1] = [h_text, headers_levels[h_level-1][1]+1] + for idx in range(h_level, 5) | let headers_levels[idx] = ['', 0] | endfor + + let h_complete_id = '' + for l in range(h_level-1) + if headers_levels[l][0] != '' + let h_complete_id .= headers_levels[l][0].'#' + endif + endfor + let h_complete_id .= headers_levels[h_level-1][0] + + if numbering > 0 && numbering <= h_level + let h_number = join(map(copy(headers_levels[numbering-1 : h_level-1]), 'v:val[1]'), '.') + let h_number .= vimwiki#vars#get_global('html_header_numbering_sym') + let h_text = h_number.' '.h_text + endif + + call add(complete_header_infos, [h_level, h_complete_id, h_text]) endfor - let h_complete_id .= headers_levels[h_level-1][0] - if numbering > 0 && numbering <= h_level - let h_number = join(map(copy(headers_levels[numbering-1 : h_level-1]), 'v:val[1]'), '.') - let h_number .= vimwiki#vars#get_global('html_header_numbering_sym') - let h_text = h_number.' '.h_text - endif + let lines = [] + let startindent = repeat(' ', vimwiki#lst#get_list_margin()) + let indentstring = repeat(' ', vimwiki#u#sw()) + let bullet = vimwiki#lst#default_symbol().' ' + for [lvl, link, desc] in complete_header_infos + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink2Template') + else + let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate2') + endif + let link = s:safesubstitute(link_tpl, '__LinkUrl__', + \ '#'.link, '') + let link = s:safesubstitute(link, '__LinkDescription__', desc, '') + call add(lines, startindent.repeat(indentstring, lvl-1).bullet.link) + endfor - call add(complete_header_infos, [h_level, h_complete_id, h_text]) - endfor - - let lines = [] - let startindent = repeat(' ', vimwiki#lst#get_list_margin()) - let indentstring = repeat(' ', vimwiki#u#sw()) - let bullet = vimwiki#lst#default_symbol().' ' - for [lvl, link, desc] in complete_header_infos - if vimwiki#vars#get_wikilocal('syntax') == 'markdown' - let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink2Template') - else - let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate2') - endif - let link = s:safesubstitute(link_tpl, '__LinkUrl__', - \ '#'.link, '') - let link = s:safesubstitute(link, '__LinkDescription__', desc, '') - call add(lines, startindent.repeat(indentstring, lvl-1).bullet.link) - endfor + return lines + endfunction let links_rx = '\%(^\s*$\)\|\%('.vimwiki#vars#get_syntaxlocal('rxListBullet').'\)' call vimwiki#base#update_listing_in_buffer( - \ lines, + \ funcref('Generator'), \ toc_header_text, \ links_rx, \ 1, diff --git a/autoload/vimwiki/diary.vim b/autoload/vimwiki/diary.vim index c3a576f..bfbbb82 100644 --- a/autoload/vimwiki/diary.vim +++ b/autoload/vimwiki/diary.vim @@ -202,72 +202,6 @@ function! s:sort(lst) endfunction -function! s:format_diary() - let result = [] - - let links_with_captions = s:read_captions(s:get_diary_files()) - let g_files = s:group_links(links_with_captions) - let g_keys = s:sort(keys(g_files)) - - for year in g_keys - if len(result) > 0 - call add(result, '') - endif - - call add(result, - \ substitute(vimwiki#vars#get_syntaxlocal('rxH2_Template'), '__Header__', year , '')) - - for month in s:sort(keys(g_files[year])) - call add(result, '') - call add(result, substitute(vimwiki#vars#get_syntaxlocal('rxH3_Template'), - \ '__Header__', s:get_month_name(month), '')) - - if vimwiki#vars#get_wikilocal('syntax') == 'markdown' - for _ in range(vimwiki#vars#get_global('markdown_header_style')) - call add(result, '') - endfor - endif - - for [fl, captions] in s:sort(items(g_files[year][month])) - let topcap = captions['top'] - let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate2') - - if vimwiki#vars#get_wikilocal('syntax') == 'markdown' - let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink1Template') - - if empty(topcap) " When using markdown syntax, we should ensure we always have a link description. - let topcap = fl - endif - endif - - if empty(topcap) - let top_link_tpl = vimwiki#vars#get_global('WikiLinkTemplate1') - else - let top_link_tpl = link_tpl - endif - - let bullet = vimwiki#lst#default_symbol().' ' - let entry = substitute(top_link_tpl, '__LinkUrl__', fl, '') - let entry = substitute(entry, '__LinkDescription__', topcap, '') - call add(result, repeat(' ', vimwiki#lst#get_list_margin()).bullet.entry) - - for [depth, subcap] in captions['rest'] - if empty(subcap) - continue - endif - let entry = substitute(link_tpl, '__LinkUrl__', fl.'#'.subcap, '') - let entry = substitute(entry, '__LinkDescription__', subcap, '') - call add(result, repeat(' ', vimwiki#lst#get_list_margin() * (2 + depth)).'- '.entry) - endfor - endfor - - endfor - endfor - - return result -endfunction - - " The given wiki number a:wnum is 1 for the first wiki, 2 for the second and so on. This is in " contrast to most other places, where counting starts with 0. When a:wnum is 0, the current wiki " is used. @@ -376,13 +310,84 @@ endfunction function! vimwiki#diary#generate_diary_section() + + function! Generator() closure + let lines = [] + + let links_with_captions = s:read_captions(s:get_diary_files()) + let g_files = s:group_links(links_with_captions) + let g_keys = s:sort(keys(g_files)) + + for year in g_keys + if len(lines) > 0 + call add(lines, '') + endif + + call add(lines, substitute(vimwiki#vars#get_syntaxlocal('rxH2_Template'), '__Header__', year , '')) + + for month in s:sort(keys(g_files[year])) + call add(lines, '') + call add(lines, substitute(vimwiki#vars#get_syntaxlocal('rxH3_Template'), + \ '__Header__', s:get_month_name(month), '')) + + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + for _ in range(vimwiki#vars#get_global('markdown_header_style')) + call add(lines, '') + endfor + endif + + for [fl, captions] in s:sort(items(g_files[year][month])) + let topcap = captions['top'] + let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate2') + + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink1Template') + + if empty(topcap) " When using markdown syntax, we should ensure we always have a link description. + let topcap = fl + endif + endif + + if empty(topcap) + let top_link_tpl = vimwiki#vars#get_global('WikiLinkTemplate1') + else + let top_link_tpl = link_tpl + endif + + let bullet = vimwiki#lst#default_symbol().' ' + let entry = substitute(top_link_tpl, '__LinkUrl__', fl, '') + let entry = substitute(entry, '__LinkDescription__', topcap, '') + call add(lines, repeat(' ', vimwiki#lst#get_list_margin()).bullet.entry) + + for [depth, subcap] in captions['rest'] + if empty(subcap) + continue + endif + let entry = substitute(link_tpl, '__LinkUrl__', fl.'#'.subcap, '') + let entry = substitute(entry, '__LinkDescription__', subcap, '') + call add(lines, repeat(' ', vimwiki#lst#get_list_margin() * (2 + depth)).'- '.entry) + endfor + endfor + + endfor + endfor + + return lines + endfunction + let current_file = vimwiki#path#path_norm(expand("%:p")) let diary_file = vimwiki#path#path_norm(s:diary_index()) if vimwiki#path#is_equal(current_file, diary_file) let content_rx = '^\%('.vimwiki#vars#get_syntaxlocal('rxHeader').'\)\|'. \ '\%(^\s*$\)\|\%('.vimwiki#vars#get_syntaxlocal('rxListBullet').'\)' - call vimwiki#base#update_listing_in_buffer(s:format_diary(), - \ vimwiki#vars#get_wikilocal('diary_header'), content_rx, 1, 1, 1) + + call vimwiki#base#update_listing_in_buffer( + \ funcref('Generator'), + \ vimwiki#vars#get_wikilocal('diary_header'), + \ content_rx, + \ 1, + \ 1, + \ 1) else echomsg 'Vimwiki Error: You can generate diary links only in a diary index page!' endif diff --git a/autoload/vimwiki/tags.vim b/autoload/vimwiki/tags.vim index 9841a06..e5a7411 100644 --- a/autoload/vimwiki/tags.vim +++ b/autoload/vimwiki/tags.vim @@ -61,6 +61,13 @@ function! vimwiki#tags#update_tags(full_rebuild, all_files) endfunction +function! s:safesubstitute(text, search, replace, mode) + " Substitute regexp but do not interpret replace + let escaped = escape(a:replace, '\&') + return substitute(a:text, a:search, escaped, a:mode) +endfunction + + " Scans the list of text lines (argument) and produces tags metadata as a list of tag entries. function! s:scan_tags(lines, page_name) @@ -292,74 +299,76 @@ endfunction " Similar to vimwiki#base#generate_links. In the current buffer, appends " tags and references to all their instances. If no arguments (tags) are " specified, outputs all tags. -function! vimwiki#tags#generate_tags(...) abort - let need_all_tags = (a:0 == 0) +function! vimwiki#tags#generate_tags(create, ...) abort let specific_tags = a:000 - let header_level = vimwiki#vars#get_global('tags_header_level') - let metadata = s:load_tags_metadata() - " make a dictionary { tag_name: [tag_links, ...] } - let tags_entries = {} - for entries in values(metadata) - for entry in entries - if has_key(tags_entries, entry.tagname) - call add(tags_entries[entry.tagname], entry.link) - else - let tags_entries[entry.tagname] = [entry.link] - endif - endfor - endfor + function! Generator() closure + let need_all_tags = empty(specific_tags) + let metadata = s:load_tags_metadata() - let tag_match = printf('rxH%d', header_level + 1) - let tag_tpl = printf('rxH%d_Template', header_level + 1) - - let lines = [] - let bullet = repeat(' ', vimwiki#lst#get_list_margin()).vimwiki#lst#default_symbol().' ' - for tagname in sort(keys(tags_entries)) - if need_all_tags || index(specific_tags, tagname) != -1 - if len(lines) > 0 - call add(lines, '') - endif - - call add(lines, - \ substitute(vimwiki#vars#get_syntaxlocal(tag_tpl), '__Header__', tagname, '')) - - if vimwiki#vars#get_wikilocal('syntax') == 'markdown' - for _ in range(vimwiki#vars#get_global('markdown_header_style')) - call add(lines, '') - endfor - endif - - for taglink in sort(tags_entries[tagname]) - if vimwiki#vars#get_wikilocal('syntax') == 'markdown' - let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink3Template') - let link_infos = vimwiki#base#resolve_link(taglink) - let link_caption = vimwiki#base#read_caption(link_infos.filename) - let link_text = split(taglink, '#', 1)[0] - - let entry = substitute(link_tpl, '__LinkUrl__', link_text, '') - let entry = substitute(entry, '__LinkAnchor__', link_infos.anchor, '') - let entry = substitute(entry, '__LinkDescription__', link_caption, '') - call add(lines, bullet.entry) + " make a dictionary { tag_name: [tag_links, ...] } + let tags_entries = {} + for entries in values(metadata) + for entry in entries + if has_key(tags_entries, entry.tagname) + call add(tags_entries[entry.tagname], entry.link) else - let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate1') - call add(lines, bullet . substitute(link_tpl, '__LinkUrl__', taglink, '')) + let tags_entries[entry.tagname] = [entry.link] endif endfor - endif - endfor + endfor + let lines = [] + let bullet = repeat(' ', vimwiki#lst#get_list_margin()).vimwiki#lst#default_symbol().' ' + for tagname in sort(keys(tags_entries)) + if need_all_tags || index(specific_tags, tagname) != -1 + if len(lines) > 0 + call add(lines, '') + endif + + let tag_tpl = printf('rxH%d_Template', header_level + 1) + call add(lines, s:safesubstitute(vimwiki#vars#get_syntaxlocal(tag_tpl), '__Header__', tagname, '')) + + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + for _ in range(vimwiki#vars#get_global('markdown_header_style')) + call add(lines, '') + endfor + endif + + for taglink in sort(tags_entries[tagname]) + if vimwiki#vars#get_wikilocal('syntax') == 'markdown' + let link_tpl = vimwiki#vars#get_syntaxlocal('Weblink3Template') + let link_infos = vimwiki#base#resolve_link(taglink) + let link_caption = split(link_infos.anchor, '#', 0)[-1] + let link_text = split(taglink, '#', 1)[0] + + let entry = s:safesubstitute(link_tpl, '__LinkUrl__', link_text, '') + let entry = s:safesubstitute(entry, '__LinkAnchor__', link_infos.anchor, '') + let entry = s:safesubstitute(entry, '__LinkDescription__', link_caption, '') + call add(lines, bullet.entry) + else + let link_tpl = vimwiki#vars#get_global('WikiLinkTemplate1') + call add(lines, bullet . substitute(link_tpl, '__LinkUrl__', taglink, '')) + endif + endfor + endif + endfor + + return lines + endfunction + + let tag_match = printf('rxH%d', header_level + 1) let links_rx = '^\%('.vimwiki#vars#get_syntaxlocal(tag_match).'\)\|'. \ '\%(^\s*$\)\|\%('.vimwiki#vars#get_syntaxlocal('rxListBullet').'\)' call vimwiki#base#update_listing_in_buffer( - \ lines, + \ funcref('Generator'), \ vimwiki#vars#get_global('tags_header'), \ links_rx, \ line('$')+1, \ header_level, - \ 1) + \ a:create) endfunction diff --git a/autoload/vimwiki/vars.vim b/autoload/vimwiki/vars.vim index 02c09c9..a3c99a0 100644 --- a/autoload/vimwiki/vars.vim +++ b/autoload/vimwiki/vars.vim @@ -251,6 +251,8 @@ function! s:populate_wikilocal_options() let default_values = { \ 'auto_diary_index': {'type': type(0), 'default': 0, 'min': 0, 'max': 1}, \ 'auto_export': {'type': type(0), 'default': 0, 'min': 0, 'max': 1}, + \ 'auto_generate_links': {'type': type(0), 'default': 0, 'min': 0, 'max': 1}, + \ 'auto_generate_tags': {'type': type(0), 'default': 0, 'min': 0, 'max': 1}, \ 'auto_tags': {'type': type(0), 'default': 0, 'min': 0, 'max': 1}, \ 'auto_toc': {'type': type(0), 'default': 0, 'min': 0, 'max': 1}, \ 'automatic_nested_syntaxes': {'type': type(0), 'default': 1, 'min': 0, 'max': 1}, diff --git a/doc/vimwiki.txt b/doc/vimwiki.txt index e02d4a7..f053ce2 100644 --- a/doc/vimwiki.txt +++ b/doc/vimwiki.txt @@ -2357,6 +2357,28 @@ See |:VimwikiDiaryGenerateLinks|: > let g:vimwiki_list = [{'path': '~/my_site/', 'auto_diary_index': 1}] +*vimwiki-option-auto_generate_links* +------------------------------------------------------------------------------ +Key Default value Values~ +auto_generate_links 0 0, 1 + +Description~ +Set this option to 1 to automatically update generated links when the +current wiki page is saved: > + let g:vimwiki_list = [{'path': '~/my_site/', 'auto_generate_links': 1}] + + +*vimwiki-option-auto_generate_tags* +------------------------------------------------------------------------------ +Key Default value Values~ +auto_generate_tags 0 0, 1 + +Description~ +Set this option to 1 to automatically update generated tags when the +current wiki page is saved: > + let g:vimwiki_list = [{'path': '~/my_site/', 'auto_generate_tags': 1}] + + ------------------------------------------------------------------------------ 12.4 Global Options *vimwiki-global-options* diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim index 7e812a4..6f19681 100644 --- a/ftplugin/vimwiki.vim +++ b/ftplugin/vimwiki.vim @@ -266,7 +266,7 @@ command! -buffer -nargs=? VimwikiNormalizeLink call vimwiki#base#normalize_link( command! -buffer VimwikiTabnewLink call vimwiki#base#follow_link('tab', 0, 1) -command! -buffer VimwikiGenerateLinks call vimwiki#base#generate_links() +command! -buffer VimwikiGenerateLinks call vimwiki#base#generate_links(1) command! -buffer -nargs=0 VimwikiBacklinks call vimwiki#base#backlinks() command! -buffer -nargs=0 VWB call vimwiki#base#backlinks() @@ -319,7 +319,7 @@ command! -buffer -bang VimwikiRebuildTags call vimwiki#tags#update_tags(1, ':/ command! -buffer -nargs=* -complete=custom,vimwiki#tags#complete_tags - \ VimwikiGenerateTags call vimwiki#tags#generate_tags() + \ VimwikiGenerateTags call vimwiki#tags#generate_tags(1, ) command! -buffer VimwikiPasteUrl call vimwiki#html#PasteUrl(expand('%:p')) command! -buffer VimwikiCatUrl call vimwiki#html#CatUrl(expand('%:p')) @@ -696,7 +696,20 @@ endif if vimwiki#vars#get_wikilocal('auto_tags') " Automatically update tags metadata on page write. augroup vimwiki - au BufWritePost call vimwiki#tags#update_tags(0, '') + au BufWritePre call vimwiki#tags#update_tags(0, '') augroup END endif +if vimwiki#vars#get_wikilocal('auto_generate_links') + " Automatically generate links *before* the file is written + augroup vimwiki + au BufWritePre call vimwiki#base#generate_links(0) + augroup END +endif + +if vimwiki#vars#get_wikilocal('auto_generate_tags') + " Automatically generate tags *before* the file is written + augroup vimwiki + au BufWritePre call vimwiki#tags#generate_tags(0) + augroup END +endif