diff --git a/autoload/vimwiki/base.vim b/autoload/vimwiki/base.vim
index a350193..609a77f 100644
--- a/autoload/vimwiki/base.vim
+++ b/autoload/vimwiki/base.vim
@@ -465,10 +465,9 @@ endfunction " }}}
" vimwiki#base#generate_links
function! vimwiki#base#generate_links() "{{{
+ let lines = []
+
let links = vimwiki#base#get_wikilinks(g:vimwiki_current_idx, 0)
-
- call append(line('$'), substitute(g:vimwiki_rxH1_Template, '__Header__', 'Generated Links', ''))
-
call sort(links)
let bullet = repeat(' ', vimwiki#lst#get_list_margin()).
@@ -476,10 +475,15 @@ function! vimwiki#base#generate_links() "{{{
for link in links
let abs_filepath = vimwiki#path#abs_path_of_link(link)
if !s:is_diary_file(abs_filepath)
- call append(line('$'), bullet.
+ call add(lines, bullet.
\ substitute(g:vimwiki_WikiLinkTemplate1, '__LinkUrl__', '\='."'".link."'", ''))
endif
endfor
+
+ let links_rx = '\m^\s*'.vimwiki#u#escape(vimwiki#lst#default_symbol()).' '
+
+ call vimwiki#base#update_listing_in_buffer(lines, 'Generated Links', links_rx,
+ \ line('$')+1, 1)
endfunction " }}}
" vimwiki#base#goto
@@ -498,7 +502,7 @@ function! vimwiki#base#backlinks() "{{{
let locations = []
for idx in range(len(g:vimwiki_list))
let syntax = VimwikiGet('syntax', idx)
- let wikifiles = s:find_files(idx, 0)
+ let wikifiles = vimwiki#base#find_files(idx, 0)
for source_file in wikifiles
let links = s:get_links(source_file, idx)
for [target_file, _, lnum, col] in links
@@ -522,7 +526,7 @@ endfunction "}}}
" Returns: a list containing all files of the given wiki as absolute file path.
" If the given wiki number is negative, the diary of the current wiki is used
" If the second argument is not zero, only directories are found
-function! s:find_files(wiki_nr, directories_only)
+function! vimwiki#base#find_files(wiki_nr, directories_only)
let wiki_nr = a:wiki_nr
if wiki_nr >= 0
let root_directory = VimwikiGet('path', wiki_nr)
@@ -551,7 +555,7 @@ endfunction
" If the given wiki number is negative, the diary of the current wiki is used.
" If also_absolute_links is nonzero, also return links of the form /file
function! vimwiki#base#get_wikilinks(wiki_nr, also_absolute_links)
- let files = s:find_files(a:wiki_nr, 0)
+ let files = vimwiki#base#find_files(a:wiki_nr, 0)
if a:wiki_nr == g:vimwiki_current_idx
let cwd = vimwiki#path#wikify_path(expand('%:p:h'))
elseif a:wiki_nr < 0
@@ -582,7 +586,7 @@ endfunction
" Returns: a list containing the links to all directories from the current file
function! vimwiki#base#get_wiki_directories(wiki_nr)
- let dirs = s:find_files(a:wiki_nr, 1)
+ let dirs = vimwiki#base#find_files(a:wiki_nr, 1)
if a:wiki_nr == g:vimwiki_current_idx
let cwd = vimwiki#path#wikify_path(expand('%:p:h'))
let root_dir = VimwikiGet('path')
@@ -608,6 +612,7 @@ function! vimwiki#base#get_anchors(filename, syntax) "{{{
let rxheader = g:vimwiki_{a:syntax}_header_search
let rxbold = g:vimwiki_{a:syntax}_bold_search
+ let rxtag = g:vimwiki_{a:syntax}_tag_search
let anchor_level = ['', '', '', '', '', '', '']
let anchors = []
@@ -652,6 +657,22 @@ function! vimwiki#base#get_anchors(filename, syntax) "{{{
let bold_count += 1
endwhile
+ " collect tags text (there can be several in one line)
+ let tag_count = 1
+ while 1
+ let tag_group_text = matchstr(line, rxtag, 0, tag_count)
+ if tag_group_text == ''
+ break
+ endif
+ for tag_text in split(tag_group_text, ':')
+ call add(anchors, tag_text)
+ if current_complete_anchor != ''
+ call add(anchors, current_complete_anchor.'#'.tag_text)
+ endif
+ endfor
+ let tag_count += 1
+ endwhile
+
endfor
return anchors
@@ -672,8 +693,12 @@ function! s:jump_to_anchor(anchor) "{{{
\ '__Header__', "\\='".segment."'", '')
let anchor_bold = substitute(g:vimwiki_{VimwikiGet('syntax')}_bold_match,
\ '__Text__', "\\='".segment."'", '')
+ let anchor_tag = substitute(g:vimwiki_{VimwikiGet('syntax')}_tag_match,
+ \ '__Tag__', "\\='".segment."'", '')
- if !search(anchor_header, 'Wc') && !search(anchor_bold, 'Wc')
+ if !search(anchor_tag, 'Wc')
+ \ && !search(anchor_header, 'Wc')
+ \ && !search(anchor_bold, 'Wc')
call setpos('.', oldpos)
break
endif
@@ -723,7 +748,7 @@ function! vimwiki#base#check_links() "{{{
let errors = []
for idx in range(len(g:vimwiki_list))
let syntax = VimwikiGet('syntax', idx)
- let wikifiles = s:find_files(idx, 0)
+ let wikifiles = vimwiki#base#find_files(idx, 0)
for wikifile in wikifiles
let links_of_files[wikifile] = s:get_links(wikifile, idx)
let anchors_of_files[wikifile] = vimwiki#base#get_anchors(wikifile, syntax)
@@ -1066,6 +1091,74 @@ function! vimwiki#base#nested_syntax(filetype, start, end, textSnipHl) abort "{{
endif
endfunction "}}}
+" creates or updates auto-generated listings in a wiki file, like TOC, diary
+" links, tags list etc.
+" - the listing consists of a level 1 header and a list of strings as content
+" - 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,
+ \ content_regex, default_lnum, create) "{{{
+ " apparently, Vim behaves strange when files change while in diff mode
+ if &diff || &readonly
+ return
+ endif
+
+ " check if the listing is already there
+ let already_there = 0
+
+ let header_rx = '\m^\s*'.
+ \ substitute(g:vimwiki_rxH1_Template, '__Header__', a:start_header, '')
+ \ .'\s*$'
+
+ let start_lnum = 1
+ while start_lnum <= line('$')
+ if getline(start_lnum) =~# header_rx
+ let already_there = 1
+ break
+ endif
+ let start_lnum += 1
+ endwhile
+
+ if !already_there && !a:create
+ return
+ endif
+
+ let old_cursor_pos = getpos('.')
+
+ if already_there
+ " delete the old listing
+ let whitespaces_in_first_line = matchstr(getline(start_lnum), '\m^\s*')
+ let end_lnum = start_lnum + 1
+ while end_lnum <= line('$') && getline(end_lnum) =~# a:content_regex
+ let end_lnum += 1
+ endwhile
+ silent exe start_lnum.','.string(end_lnum - 1).'delete _'
+ else
+ let start_lnum = a:default_lnum
+ let whitespaces_in_first_line = ''
+ endif
+
+ " write new listing
+ let new_header = whitespaces_in_first_line
+ \ . substitute(g:vimwiki_rxH1_Template,
+ \ '__Header__', '\='."'".a:start_header."'", '')
+ call append(start_lnum - 1, new_header)
+ let start_lnum += 1
+ for string in a:strings
+ call append(start_lnum - 1, string)
+ let start_lnum += 1
+ endfor
+ " append an empty line if there is not one
+ if start_lnum <= line('$') && getline(start_lnum) !~# '\m^\s*$'
+ call append(start_lnum - 1, '')
+ endif
+
+ call setpos('.', old_cursor_pos)
+endfunction "}}}
+
" WIKI link following functions {{{
" vimwiki#base#find_next_link
function! vimwiki#base#find_next_link() "{{{
@@ -1143,6 +1236,9 @@ function! vimwiki#base#go_back_link() "{{{
let prev_word = b:vimwiki_prev_link
execute ":e ".substitute(prev_word[0], '\s', '\\\0', 'g')
call setpos('.', prev_word[1])
+ else
+ " maybe we came here by jumping to a tag -> pop from the tag stack
+ silent! pop!
endif
endfunction " }}}
@@ -1606,44 +1702,6 @@ endfunction " }}}
" a:create == 1: creates or updates TOC in current file
" a:create == 0: update if TOC exists
function! vimwiki#base#table_of_contents(create)
- " apparently, Vim behaves strange when files change while in diff mode
- if &diff
- return
- endif
-
- " look for existing TOC
- let toc_header = '^\s*'.substitute(g:vimwiki_rxH1_Template, '__Header__',
- \ '\='."'".g:vimwiki_toc_header."'", '').'\s*$'
- let toc_line = 0
- let lnum = 1
- while lnum <= &modelines + 2 && lnum <= line('$')
- if getline(lnum) =~# toc_header
- let toc_line = lnum
- break
- endif
- let lnum += 1
- endwhile
-
- if !a:create && toc_line <= 0
- return
- endif
-
- let old_cursor_pos = getpos('.')
- let bullet = vimwiki#lst#default_symbol().' '
- let rx_bullet = vimwiki#u#escape(bullet)
- let whitespaces = matchstr(getline(toc_line), '^\s*')
-
- " delete old TOC
- if toc_line > 0
- let endoftoc = toc_line+1
- while endoftoc <= line('$') && getline(endoftoc) =~# '^\s*'.rx_bullet.g:vimwiki_rxWikiLink.'\s*$'
- let endoftoc += 1
- endwhile
- silent exe toc_line.','.string(endoftoc-1).'delete _'
- else
- let toc_line = 1
- endif
-
" collect new headers
let headers = []
let headers_levels = [['', 0], ['', 0], ['', 0], ['', 0], ['', 0], ['', 0]]
@@ -1654,6 +1712,9 @@ function! vimwiki#base#table_of_contents(create)
endif
let h_level = vimwiki#u#count_first_sym(line_content)
let h_text = vimwiki#u#trim(matchstr(line_content, g:vimwiki_rxHeader))
+ if h_text ==# g:vimwiki_toc_header " 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
@@ -1676,25 +1737,23 @@ function! vimwiki#base#table_of_contents(create)
call add(headers, [h_level, h_complete_id, h_text])
endfor
- " write new TOC
- call append(toc_line-1, whitespaces . substitute(g:vimwiki_rxH1_Template,
- \ '__Header__', '\='."'".g:vimwiki_toc_header."'", ''))
-
+ 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 headers
let esc_link = substitute(link, "'", "''", 'g')
let esc_desc = substitute(desc, "'", "''", 'g')
let link = substitute(g:vimwiki_WikiLinkTemplate2, '__LinkUrl__',
\ '\='."'".'#'.esc_link."'", '')
let link = substitute(link, '__LinkDescription__', '\='."'".esc_desc."'", '')
- call append(toc_line, startindent.repeat(indentstring, lvl-1).bullet.link)
- let toc_line += 1
+ call add(lines, startindent.repeat(indentstring, lvl-1).bullet.link)
endfor
- if getline(toc_line+1) !~# '^\s*$'
- call append(toc_line, '')
- endif
- call setpos('.', old_cursor_pos)
+
+ let links_rx = '\m^\s*'.vimwiki#u#escape(vimwiki#lst#default_symbol()).' '
+
+ call vimwiki#base#update_listing_in_buffer(lines, g:vimwiki_toc_header, links_rx,
+ \ 1, a:create)
endfunction
"}}}
diff --git a/autoload/vimwiki/diary.vim b/autoload/vimwiki/diary.vim
index d131efe..540a766 100644
--- a/autoload/vimwiki/diary.vim
+++ b/autoload/vimwiki/diary.vim
@@ -121,36 +121,31 @@ fun! s:group_links(links) "{{{
return result
endfun "}}}
-fun! s:sort(lst) "{{{
+function! s:sort(lst) "{{{
if VimwikiGet("diary_sort") ==? 'desc'
return reverse(sort(a:lst))
else
return sort(a:lst)
endif
-endfun "}}}
+endfunction "}}}
-fun! s:format_diary(...) "{{{
+function! s:format_diary(...) "{{{
let result = []
- call add(result, substitute(g:vimwiki_rxH1_Template, '__Header__', VimwikiGet('diary_header'), ''))
-
if a:0
let g_files = s:group_links(s:get_diary_links(a:1))
else
let g_files = s:group_links(s:get_diary_links())
endif
- " for year in s:rev(sort(keys(g_files)))
for year in s:sort(keys(g_files))
call add(result, '')
call add(result, substitute(g:vimwiki_rxH2_Template, '__Header__', year , ''))
- " for month in s:rev(sort(keys(g_files[year])))
for month in s:sort(keys(g_files[year]))
call add(result, '')
call add(result, substitute(g:vimwiki_rxH3_Template, '__Header__', s:get_month_name(month), ''))
- " for [fl, cap] in s:rev(sort(items(g_files[year][month])))
for [fl, cap] in s:sort(items(g_files[year][month]))
if empty(cap)
let entry = substitute(g:vimwiki_WikiLinkTemplate1, '__LinkUrl__', fl, '')
@@ -165,46 +160,8 @@ fun! s:format_diary(...) "{{{
endfor
endfor
- call add(result, '')
return result
-endfun "}}}
-
-function! s:delete_diary_section() "{{{
- " remove diary section
- let old_pos = getpos('.')
- let ln_start = -1
- let ln_end = -1
- call cursor(1, 1)
- if search(substitute(g:vimwiki_rxH1_Template, '__Header__', VimwikiGet('diary_header'), ''), 'Wc')
- let ln_start = line('.')
- if search(g:vimwiki_rxH1, 'W')
- let ln_end = line('.') - 1
- else
- let ln_end = line('$')
- endif
- endif
-
- if ln_start < 0 || ln_end < 0
- call setpos('.', old_pos)
- return
- endif
-
- if !&readonly
- exe ln_start.",".ln_end."delete _"
- endif
-
- call setpos('.', old_pos)
-endfunction "}}}
-
-function! s:insert_diary_section() "{{{
- if !&readonly
- let ln = line('.')
- call append(ln, s:format_diary())
- if ln == 1 && getline(ln) == ''
- 1,1delete
- endif
- endif
endfunction "}}}
" Diary index stuff }}}
@@ -300,8 +257,9 @@ function! vimwiki#diary#generate_diary_section() "{{{
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)
- call s:delete_diary_section()
- call s:insert_diary_section()
+ let content_rx = '^\%(\s*\* \)\|\%(^\s*$\)\|\%('.g:vimwiki_rxHeader.'\)'
+ call vimwiki#base#update_listing_in_buffer(s:format_diary(),
+ \ VimwikiGet('diary_header'), content_rx, line('$')+1, 1)
else
echom "vimwiki: You can generate diary links only in a diary index page!"
endif
diff --git a/autoload/vimwiki/html.vim b/autoload/vimwiki/html.vim
index a338acd..4c4a769 100644
--- a/autoload/vimwiki/html.vim
+++ b/autoload/vimwiki/html.vim
@@ -289,6 +289,27 @@ function! s:tag_strong(value, header_ids) "{{{
\ .id.'">'.text.''
endfunction "}}}
+function! s:tag_tags(value, header_ids) "{{{
+ let complete_id = ''
+ for level in range(6)
+ if a:header_ids[level][0] != ''
+ let complete_id .= a:header_ids[level][0].'-'
+ endif
+ endfor
+ if a:header_ids[5][0] == ''
+ let complete_id = complete_id[:-2]
+ endif
+ let complete_id = s:safe_html_anchor(complete_id)
+
+ let result = []
+ for tag in split(a:value, ':')
+ let id = s:safe_html_anchor(tag)
+ call add(result, ''.tag.'')
+ endfor
+ return join(result)
+endfunction "}}}
+
function! s:tag_todo(value) "{{{
return ''.a:value.''
endfunction "}}}
@@ -554,6 +575,7 @@ function! s:process_tags_typefaces(line, header_ids) "{{{
let line = s:make_tag(line, g:vimwiki_rxSubScript, 's:tag_sub')
let line = s:make_tag(line, g:vimwiki_rxCode, 's:tag_code')
let line = s:make_tag(line, g:vimwiki_rxEqIn, 's:tag_eqin')
+ let line = s:make_tag(line, g:vimwiki_rxTags, 's:tag_tags', a:header_ids)
return line
endfunction " }}}
diff --git a/autoload/vimwiki/style.css b/autoload/vimwiki/style.css
index 752867b..b9d1c02 100644
--- a/autoload/vimwiki/style.css
+++ b/autoload/vimwiki/style.css
@@ -24,6 +24,7 @@ del {text-decoration: line-through; color: #777777;}
.justright {text-align: right;}
.justcenter {text-align: center;}
.center {margin-left: auto; margin-right: auto;}
+.tag {background-color: #eeeeee; font-family: monospace; padding: 2px;}
/* classes for items of todo lists */
.done0 {
diff --git a/autoload/vimwiki/tags.vim b/autoload/vimwiki/tags.vim
new file mode 100644
index 0000000..4bd13a9
--- /dev/null
+++ b/autoload/vimwiki/tags.vim
@@ -0,0 +1,307 @@
+" vim:tabstop=2:shiftwidth=2:expandtab:foldmethod=marker:textwidth=79
+" Vimwiki autoload plugin file
+
+
+let s:TAGS_METADATA_FILE_NAME = '.tags'
+
+" Tags metadata in-memory format:
+" metadata := { 'pagename': [entries, ...] }
+" entry := { 'tagname':..., 'lineno':..., 'link':... }
+
+" Tags metadata in-file format:
+"
+" Is based on CTags format (see |tags-file-format|).
+"
+" {tagaddress} is set to lineno. We'll let vim search by exact line number; we
+" can afford that, we assume metadata file is always updated before use.
+"
+" Pagename and link are not saved in standard ctags fields, so we'll add
+" an optional field, "vimwiki:". In this field, we encode tab-separated values
+" of missing parameters -- "pagename" and "link".
+
+" vimwiki#tags#update_tags
+" Update tags metadata.
+" a:full_rebuild == 1: re-scan entire wiki
+" a:full_rebuild == 0: only re-scan current page
+" a:all_files == '': only if the file is newer than .tags
+function! vimwiki#tags#update_tags(full_rebuild, all_files) "{{{
+ let all_files = a:all_files != ''
+ if !a:full_rebuild
+ " Updating for one page (current)
+ let page_name = VimwikiGet('subdir') . expand('%:t:r')
+ " Collect tags in current file
+ let tags = s:scan_tags(getline(1, '$'), page_name)
+ " Load metadata file
+ let metadata = s:load_tags_metadata()
+ " Drop old tags
+ let metadata = s:remove_page_from_tags(metadata, page_name)
+ " Merge in the new ones
+ let metadata = s:merge_tags(metadata, page_name, tags)
+ " Save
+ call s:write_tags_metadata(metadata)
+ else " full rebuild
+ let files = vimwiki#base#find_files(g:vimwiki_current_idx, 0)
+ let tags_file_last_modification =
+ \ getftime(vimwiki#tags#metadata_file_path())
+ let metadata = s:load_tags_metadata()
+ for file in files
+ if all_files || getftime(file) >= tags_file_last_modification
+ let page_name = fnamemodify(file, ':t:r')
+ let tags = s:scan_tags(readfile(file), page_name)
+ let metadata = s:remove_page_from_tags(metadata, page_name)
+ let metadata = s:merge_tags(metadata, page_name, tags)
+ endif
+ endfor
+ call s:write_tags_metadata(metadata)
+ endif
+endfunction " }}}
+
+" s:scan_tags
+" Scans the list of text lines (argument) and produces tags metadata as a
+" list of tag entries.
+function! s:scan_tags(lines, page_name) "{{{
+
+ let entries = []
+ let page_name = a:page_name
+
+ " Code wireframe to scan for headers -- borrowed from
+ " vimwiki#base#get_anchors(), with minor modifications.
+
+ let rxheader = g:vimwiki_{VimwikiGet('syntax')}_header_search
+ let rxtag = g:vimwiki_{VimwikiGet('syntax')}_tag_search
+
+ let anchor_level = ['', '', '', '', '', '', '']
+ let current_complete_anchor = ''
+
+ let PROXIMITY_LINES_NR = 5
+ let header_line_nr = - (2 * PROXIMITY_LINES_NR)
+
+ for line_nr in range(1, len(a:lines))
+ let line = a:lines[line_nr - 1]
+
+ " process headers
+ let h_match = matchlist(line, rxheader)
+ if !empty(h_match) " got a header
+ let header_line_nr = line_nr
+ let header = vimwiki#u#trim(h_match[2])
+ let level = len(h_match[1])
+ let anchor_level[level-1] = header
+ for l in range(level, 6)
+ let anchor_level[l] = ''
+ endfor
+ if level == 1
+ let current_complete_anchor = header
+ else
+ let current_complete_anchor = ''
+ for l in range(level-1)
+ if anchor_level[l] != ''
+ let current_complete_anchor .= anchor_level[l].'#'
+ endif
+ endfor
+ let current_complete_anchor .= header
+ endif
+ continue " tags are not allowed in headers
+ endif
+
+ " TODO ignore verbatim blocks
+
+ " Scan line for tags. There can be many of them.
+ let str = line
+ while 1
+ let tag_group = matchstr(str, rxtag)
+ if tag_group == ''
+ break
+ endif
+ let tagend = matchend(str, rxtag)
+ let str = str[(tagend):]
+ for tag in split(tag_group, ':')
+ " Create metadata entry
+ let entry = {}
+ let entry.tagname = tag
+ let entry.lineno = line_nr
+ if line_nr <= PROXIMITY_LINES_NR && header_line_nr < 0
+ " Tag appeared at the top of the file
+ let entry.link = page_name
+ elseif line_nr <= (header_line_nr + PROXIMITY_LINES_NR)
+ let entry.link = page_name . '#' . current_complete_anchor
+ else
+ let entry.link = page_name . '#' . tag
+ endif
+ call add(entries, entry)
+ endfor
+ endwhile
+
+ endfor " loop over lines
+ return entries
+endfunction " }}}
+
+" vimwiki#tags#metadata_file_path
+" Returns tags metadata file path
+function! vimwiki#tags#metadata_file_path() abort "{{{
+ return fnamemodify(VimwikiGet('path') . '/' . s:TAGS_METADATA_FILE_NAME, ':p')
+endfunction " }}}
+
+" s:load_tags_metadata
+" Loads tags metadata from file, returns a dictionary
+function! s:load_tags_metadata() abort "{{{
+ let metadata_path = vimwiki#tags#metadata_file_path()
+ if !filereadable(metadata_path)
+ return {}
+ endif
+ let metadata = {}
+ for line in readfile(metadata_path)
+ if line =~ '^!_TAG_FILE_'
+ continue
+ endif
+ let parts = matchlist(line, '^\(.\{-}\);"\(.*\)$')
+ if parts[0] == '' || parts[1] == '' || parts[2] == ''
+ throw 'VimwikiTags1: Metadata file corrupted'
+ endif
+ let std_fields = split(parts[1], '\t')
+ if len(std_fields) != 3
+ throw 'VimwikiTags2: Metadata file corrupted'
+ endif
+ let vw_part = parts[2]
+ if vw_part[0] != "\t"
+ throw 'VimwikiTags3: Metadata file corrupted'
+ endif
+ let vw_fields = split(vw_part[1:], "\t")
+ if len(vw_fields) != 1 || vw_fields[0] !~ '^vimwiki:'
+ throw 'VimwikiTags4: Metadata file corrupted'
+ endif
+ let vw_data = substitute(vw_fields[0], '^vimwiki:', '', '')
+ let vw_data = substitute(vw_data, '\\n', "\n", 'g')
+ let vw_data = substitute(vw_data, '\\r', "\r", 'g')
+ let vw_data = substitute(vw_data, '\\t', "\t", 'g')
+ let vw_data = substitute(vw_data, '\\\\', "\\", 'g')
+ let vw_fields = split(vw_data, "\t")
+ if len(vw_fields) != 2
+ throw 'VimwikiTags5: Metadata file corrupted'
+ endif
+ let pagename = vw_fields[0]
+ let entry = {}
+ let entry.tagname = std_fields[0]
+ let entry.lineno = std_fields[2]
+ let entry.link = vw_fields[1]
+ if has_key(metadata, pagename)
+ call add(metadata[pagename], entry)
+ else
+ let metadata[pagename] = [entry]
+ endif
+ endfor
+ return metadata
+endfunction " }}}
+
+" s:remove_page_from_tags
+" Removes all entries for given page from metadata in-place. Returns updated
+" metadata (just in case).
+function! s:remove_page_from_tags(metadata, page_name) "{{{
+ if has_key(a:metadata, a:page_name)
+ call remove(a:metadata, a:page_name)
+ return a:metadata
+ else
+ return a:metadata
+ endif
+endfunction " }}}
+
+" s:merge_tags
+" Merges metadata of one file into a:metadata
+function! s:merge_tags(metadata, pagename, file_metadata) "{{{
+ let metadata = a:metadata
+ let metadata[a:pagename] = a:file_metadata
+ return metadata
+endfunction " }}}
+
+" s:write_tags_metadata
+" Saves metadata object into a file. Throws exceptions in case of problems.
+function! s:write_tags_metadata(metadata) "{{{
+ let metadata_path = vimwiki#tags#metadata_file_path()
+ let tags = []
+ for pagename in keys(a:metadata)
+ for entry in a:metadata[pagename]
+ let entry_data = pagename . "\t" . entry.link
+ let entry_data = substitute(entry_data, "\\", '\\\\', 'g')
+ let entry_data = substitute(entry_data, "\t", '\\t', 'g')
+ let entry_data = substitute(entry_data, "\r", '\\r', 'g')
+ let entry_data = substitute(entry_data, "\n", '\\n', 'g')
+ call add(tags,
+ \ entry.tagname . "\t"
+ \ . pagename . VimwikiGet('ext') . "\t"
+ \ . entry.lineno
+ \ . ';"'
+ \ . "\t" . "vimwiki:" . entry_data
+ \)
+ endfor
+ endfor
+ call sort(tags)
+ call insert(tags, "!_TAG_FILE_SORTED\t1\t")
+ call writefile(tags, metadata_path)
+endfunction " }}}
+
+" vimwiki#tags#get_tags
+" Returns list of unique tags found in the .tags file
+function! vimwiki#tags#get_tags() "{{{
+ let metadata = s:load_tags_metadata()
+ let tags = {}
+ for entries in values(metadata)
+ for entry in entries
+ let tags[entry.tagname] = 1
+ endfor
+ endfor
+ return keys(tags)
+endfunction " }}}
+
+" vimwiki#tags#generate_tags
+" 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)
+ let specific_tags = a:000
+
+ 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
+
+ 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
+ call extend(lines, [
+ \ '',
+ \ substitute(g:vimwiki_rxH2_Template, '__Header__', tagname, ''),
+ \ '' ])
+ for taglink in tags_entries[tagname]
+ call add(lines, bullet .
+ \ substitute(g:vimwiki_WikiLinkTemplate1, '__LinkUrl__', taglink, ''))
+ endfor
+ endif
+ endfor
+
+ let links_rx = '\m\%(^\s*$\)\|\%('.g:vimwiki_rxH2.'\)\|\%(^\s*'
+ \ .vimwiki#u#escape(vimwiki#lst#default_symbol()).' '
+ \ .g:vimwiki_rxWikiLink.'$\)'
+
+ call vimwiki#base#update_listing_in_buffer(lines, 'Generated Tags', links_rx,
+ \ line('$')+1, 1)
+endfunction " }}}
+
+" vimwiki#tags#complete_tags
+function! vimwiki#tags#complete_tags(ArgLead, CmdLine, CursorPos) abort " {{{
+ " We can safely ignore args if we use -custom=complete option, Vim engine
+ " will do the job of filtering.
+ let taglist = vimwiki#tags#get_tags()
+ return join(taglist, "\n")
+endfunction " }}}
+
diff --git a/doc/vimwiki.txt b/doc/vimwiki.txt
index d80012f..443ee1c 100644
--- a/doc/vimwiki.txt
+++ b/doc/vimwiki.txt
@@ -38,6 +38,7 @@ CONTENTS *vimwiki-contents*
5.12. Schemes |vimwiki-syntax-schemes|
5.13. Transclusions |vimwiki-syntax-transclude|
5.14. Thumbnails |vimwiki-syntax-thumbnails|
+ 5.15. Tags |vimwiki-syntax-tags|
6. Folding/Outline |vimwiki-folding|
7. Placeholders |vimwiki-placeholders|
8. Lists |vimwiki-lists|
@@ -595,7 +596,7 @@ il A single list item.
*:VimwikiGoto*
Goto link provided by an argument. For example: >
:VimwikiGoto HelloWorld
-< opens opens/creates HelloWorld wiki page.
+< opens opens/creates HelloWorld wiki page.
Supports |cmdline-completion| for link name.
@@ -703,6 +704,21 @@ il A single list item.
files are reachable from the index file. Errors are shown in the quickfix
window.
+*:VimwikiRebuildTags*
+ Rebuilds the tags metadata file for the current wiki file.
+ Necessary for all tags related commands: |vimwiki-syntax-tags|.
+
+ :VimwikiRebuildTags! does the same for all files in the current wiki.
+
+*:VimwikiSearchTags*
+ Searches over the pages in current wiki and finds all locations of a given
+ tag. Supports |cmdline-completion|.
+
+*:VimwikiGenerateTags*
+ Similar to |:VimwikiGenerateLinks|. In the current buffer, appends tags
+ and references to all their instances. Supports |cmdline-completion|. If
+ no arguments (tags) are specified, outputs all tags.
+
==============================================================================
5. Wiki syntax *vimwiki-syntax*
@@ -1227,6 +1243,45 @@ in HTML: >
+ :ind
+which opens up a popup menu with all tags defined in the wiki starting with
+"ind".
+
+Tags are also treated as |vimwiki-anchors| (similar to bold text).
+
+Note that tag search/jump/completion commands need certain metadata saved in
+the wiki folder. This metadata can be manually updated by running
+|:VimwikiRebuildTags|. There is an option |vimwiki-option-auto_tags|, when
+enabled, tags metadata will be auto-updated on each page save.
+
+Once tags metadata has been built, one can use Vim's built-in tag search
+functionality (see |tagsrch.txt|).
+
+Tags-related commands and options:
+ * |:VimwikiRebuildTags|
+ * |:VimwikiGenerateTags|
+ * |:VimwikiSearchTags|
+ * |vimwiki-option-auto_tags|
==============================================================================
@@ -1581,12 +1636,13 @@ See |g:vimwiki_use_calendar| option to turn it off/on.
==============================================================================
12. Anchors *vimwiki-anchors*
-Every header and every bold text is an anchor. To jump to it, use a wikilink
+Every header, tag, and bold text is an anchor. To jump to it, use a wikilink
of the form >
[[file#anchor]]
For example, consider the following file "Todo.wiki": >
= My tasks =
+ :todo-lists:
== Home ==
- [ ] bathe my dog
== Work ==
@@ -1602,6 +1658,9 @@ Then, to jump from your index.wiki directly to your knitting projects, use: >
Or, to jump to an individual project, use this link: >
[[Todo#pig]]
+Or, to jump to a tag, use this link: >
+ [[Todo#todo-lists]]
+
If there are multiple instances of an anchor, you can use the long form which
consists of the complete header hierarchy, separated by '#': >
[[Todo#My tasks#Knitting club#Knitting projects#dog]]
@@ -2048,6 +2107,18 @@ local mappings |vimwiki_glstar|, |vimwiki_gl#| |vimwiki_gl-|, |vimwiki_gl-|,
Note: if you use MediaWiki syntax, you probably would like to set this option
to 0, because every indented line is considered verbatim text.
+
+*vimwiki-option-auto_tags*
+------------------------------------------------------------------------------
+Key Default value Values~
+auto_tags 0 0, 1
+
+Description~
+Set this option to 1 to automatically update the tags metadata when the
+current wiki page is saved: >
+ let g:vimwiki_list = [{'path': '~/my_site/', 'auto_tags': 1}]
+
+
------------------------------------------------------------------------------
12.4 Global Options *vimwiki-global-options*
@@ -2646,6 +2717,7 @@ Vim plugins: http://www.vim.org/scripts/script.php?script_id=2226
???~
+ * Support for tags.
* Support for wiki links absolute to the wiki root
* The "file:" and "local:" schemes semantic changed slightly
* Added the |VimwikiLinkConverter| function
diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim
index 81cec3a..f34300c 100644
--- a/ftplugin/vimwiki.vim
+++ b/ftplugin/vimwiki.vim
@@ -31,26 +31,49 @@ execute 'setlocal suffixesadd='.VimwikiGet('ext')
setlocal isfname-=[,]
" gf}}}
+exe "setlocal tags+=" . vimwiki#tags#metadata_file_path()
+
" MISC }}}
" COMPLETION {{{
function! Complete_wikifiles(findstart, base)
if a:findstart == 1
- let column = col('.')-1
+ let column = col('.')-2
let line = getline('.')[:column]
- let startoflink = match(line, '\[\[\zs[^\\[]*$')
+ let startoflink = match(line, '\[\[\zs[^\\[\]]*$')
if startoflink != -1
+ let s:line_context = '['
return startoflink
endif
if VimwikiGet('syntax') ==? 'markdown'
- let startofinlinelink = match(line, '\[.*\](\zs.*$')
+ let startofinlinelink = match(line, '\[.*\](\zs[^)]*$')
if startofinlinelink != -1
+ let s:line_context = '['
return startofinlinelink
endif
endif
+ let startoftag = match(line, ':\zs[^:[:space:]]*$')
+ if startoftag != -1
+ let s:line_context = ':'
+ return startoftag
+ endif
+ let s:line_context = ''
return -1
else
- if a:base !~# '#'
+ " Completion works for wikilinks/anchors, and for tags. s:line_content
+ " tells us, which string came before a:base. There seems to be no easier
+ " solution, because calling col('.') here returns garbage.
+ if s:line_context == ''
+ return []
+ elseif s:line_context == ':'
+ " Tags completion
+ let tags = vimwiki#tags#get_tags()
+ if a:base != ''
+ call filter(tags,
+ \ "v:val[:" . (len(a:base)-1) . "] == '" . substitute(a:base, "'", "''", '') . "'" )
+ endif
+ return tags
+ elseif a:base !~# '#'
" we look for wiki files
if a:base =~# '^wiki\d:'
@@ -290,6 +313,14 @@ command! -buffer VimwikiTableMoveColumnRight call vimwiki#tbl#move_column_right(
command! -buffer VimwikiDiaryNextDay call vimwiki#diary#goto_next_day()
command! -buffer VimwikiDiaryPrevDay call vimwiki#diary#goto_prev_day()
+" tags commands
+command! -buffer -bang
+ \ VimwikiRebuildTags call vimwiki#tags#update_tags(1, '')
+command! -buffer -nargs=* -complete=custom,vimwiki#tags#complete_tags
+ \ VimwikiSearchTags VimwikiSearch /::/
+command! -buffer -nargs=* -complete=custom,vimwiki#tags#complete_tags
+ \ VimwikiGenerateTags call vimwiki#tags#generate_tags()
+
" COMMANDS }}}
" KEYBINDINGS {{{
@@ -622,6 +653,13 @@ if VimwikiGet('auto_toc')
au BufWritePre call vimwiki#base#table_of_contents(0)
augroup END
endif
+
+if VimwikiGet('auto_tags')
+ " Automatically update tags metadata on page write.
+ augroup vimwiki
+ au BufWritePost call vimwiki#tags#update_tags(0, '')
+ augroup END
+endif
" AUTOCOMMANDS }}}
" PASTE, CAT URL {{{
diff --git a/plugin/vimwiki.vim b/plugin/vimwiki.vim
index 584f026..b969ff8 100644
--- a/plugin/vimwiki.vim
+++ b/plugin/vimwiki.vim
@@ -414,6 +414,8 @@ let s:vimwiki_defaults.diary_link_fmt = '%Y-%m-%d'
let s:vimwiki_defaults.custom_wiki2html = ''
"
let s:vimwiki_defaults.list_margin = -1
+
+let s:vimwiki_defaults.auto_tags = 0
"}}}
" DEFAULT options {{{
diff --git a/syntax/omnipresent_syntax.vim b/syntax/omnipresent_syntax.vim
index 612e63d..aaf3545 100644
--- a/syntax/omnipresent_syntax.vim
+++ b/syntax/omnipresent_syntax.vim
@@ -13,12 +13,16 @@ let g:vimwiki_default_header_match = '^\s*\(=\{1,6}\)=\@!\s*__Header__\s*\1=\@!\
let g:vimwiki_default_bold_search = '\%(^\|\s\|[[:punct:]]\)\@<=\*\zs\%([^*`[:space:]][^*`]*[^*`[:space:]]\|[^*`[:space:]]\)\ze\*\%([[:punct:]]\|\s\|$\)\@='
let g:vimwiki_default_bold_match = '\%(^\|\s\|[[:punct:]]\)\@<=\*__Text__\*\%([[:punct:]]\|\s\|$\)\@='
let g:vimwiki_default_wikilink = '\[\[\zs[^\\\]|]\+\ze\%(|[^\\\]]\+\)\?\]\]'
+let g:vimwiki_default_tag_search = '\(^\|\s\)\zs:\([^:''[:space:]]\+:\)\+\ze\(\s\|$\)'
+let g:vimwiki_default_tag_match = '\(^\|\s\):\([^:''[:space:]]\+:\)*__Tag__:\([^:[:space:]]\+:\)*\(\s\|$\)'
let g:vimwiki_markdown_header_search = '^\s*\(#\{1,6}\)\([^#].*\)$'
let g:vimwiki_markdown_header_match = '^\s*\(#\{1,6}\)#\@!\s*__Header__\s*$'
let g:vimwiki_markdown_bold_search = '\%(^\|\s\|[[:punct:]]\)\@<=\*\zs\%([^*`[:space:]][^*`]*[^*`[:space:]]\|[^*`[:space:]]\)\ze\*\%([[:punct:]]\|\s\|$\)\@='
let g:vimwiki_markdown_bold_match = '\%(^\|\s\|[[:punct:]]\)\@<=\*__Text__\*\%([[:punct:]]\|\s\|$\)\@='
let g:vimwiki_markdown_wikilink = g:vimwiki_default_wikilink "XXX plus markdown-style links
+let g:vimwiki_markdown_tag_search = g:vimwiki_default_tag_search
+let g:vimwiki_markdown_tag_match = g:vimwiki_default_tag_match
let g:vimwiki_media_header_search = '^\s*\(=\{1,6}\)\([^=].*[^=]\)\1\s*$'
let g:vimwiki_media_header_match = '^\s*\(=\{1,6}\)=\@!\s*__Header__\s*\1=\@!\s*$'
@@ -27,3 +31,5 @@ let g:vimwiki_media_bold_match = '''''''__Text__'''''''
" ^- this strange looking thing is equivalent to "'''__Text__'''" but since we later
" want to call escape() on this string, we must keep it in single quotes
let g:vimwiki_media_wikilink = g:vimwiki_default_wikilink
+let g:vimwiki_media_tag_search = g:vimwiki_default_tag_search " XXX rework to mediawiki categories format?
+let g:vimwiki_media_tag_match = g:vimwiki_default_tag_match " XXX rework to mediawiki categories format?
diff --git a/syntax/vimwiki.vim b/syntax/vimwiki.vim
index 4f7642d..ded43f3 100644
--- a/syntax/vimwiki.vim
+++ b/syntax/vimwiki.vim
@@ -489,6 +489,10 @@ if g:vimwiki_valid_html_tags != ''
execute 'syntax match VimwikiComment /'.g:vimwiki_rxComment.'/ contains=@Spell'
endif
+
+" tags
+execute 'syntax match VimwikiTag /'.g:vimwiki_rxTags.'/'
+
" }}}
" header groups highlighting "{{{
@@ -554,6 +558,7 @@ hi def link VimwikiListTodo VimwikiList
hi def link VimwikiCheckBoxDone Comment
hi def link VimwikiEmoticons Character
hi def link VimwikiHR Identifier
+hi def link VimwikiTag Keyword
hi def link VimwikiDelText Constant
hi def link VimwikiDelTextT VimwikiDelText
diff --git a/syntax/vimwiki_default.vim b/syntax/vimwiki_default.vim
index 6023539..6150f99 100644
--- a/syntax/vimwiki_default.vim
+++ b/syntax/vimwiki_default.vim
@@ -91,3 +91,5 @@ let g:vimwiki_rxMathStart = '{{\$'
let g:vimwiki_rxMathEnd = '}}\$'
let g:vimwiki_rxComment = '^\s*%%.*$'
+let g:vimwiki_rxTags = '\%(^\|\s\)\@<=:\%([^:''[:space:]]\+:\)\+\%(\s\|$\)\@='
+" see also g:vimwiki_default_tag_search
diff --git a/syntax/vimwiki_markdown.vim b/syntax/vimwiki_markdown.vim
index 31667e2..c32c7d7 100644
--- a/syntax/vimwiki_markdown.vim
+++ b/syntax/vimwiki_markdown.vim
@@ -88,3 +88,4 @@ let g:vimwiki_rxMathStart = '\$\$'
let g:vimwiki_rxMathEnd = '\$\$'
let g:vimwiki_rxComment = '^\s*%%.*$'
+let g:vimwiki_rxTags = '\%(^\|\s\)\@<=:\%([^:[:space:]]\+:\)\+\%(\s\|$\)\@='
diff --git a/syntax/vimwiki_media.vim b/syntax/vimwiki_media.vim
index 37c24db..9c0498e 100644
--- a/syntax/vimwiki_media.vim
+++ b/syntax/vimwiki_media.vim
@@ -69,3 +69,4 @@ let g:vimwiki_rxMathStart = '{{\$'
let g:vimwiki_rxMathEnd = '}}\$'
let g:vimwiki_rxComment = '^\s*%%.*$'
+let g:vimwiki_rxTags = '\%(^\|\s\)\@<=:\%([^:[:space:]]\+:\)\+\%(\s\|$\)\@='