vimwiki

Personal wiki for vim
git clone https://github.com/vimwiki/vimwiki.git
Log | Files | Refs | README | LICENSE

commit 931b40ade1e69aa9d15c4f6e4318ec7eaf6cd2cd
parent eb02e0be9aaa98cc6974b1d7a0ca0586f484fd0e
Author: EinfachToll <istjanichtzufassen@googlemail.com>
Date:   Thu,  4 Dec 2014 21:18:58 +0100

New command :VimwikiCheckLinks

while we are at it, refactor the code to search through files

Diffstat:
Mautoload/vimwiki/base.vim | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mautoload/vimwiki/u.vim | 5+++++
Mdoc/vimwiki.txt | 7+++++++
Mftplugin/vimwiki.vim | 47+++++++++++------------------------------------
Asyntax/omnipresent_syntax.vim | 30++++++++++++++++++++++++++++++
Msyntax/vimwiki.vim | 5+++--
6 files changed, 245 insertions(+), 103 deletions(-)

diff --git a/autoload/vimwiki/base.vim b/autoload/vimwiki/base.vim @@ -551,81 +551,68 @@ function! vimwiki#base#backlinks() "{{{ \ escape(VimwikiGet('path').'**/*'.VimwikiGet('ext'), ' ') endfunction "}}} -" vimwiki#base#get_links -function! vimwiki#base#get_links(pat) "{{{ return string-list for files - " in the current wiki matching the pattern "pat" - " search all wiki files (or directories) in wiki 'path' and its subdirs. - - let time1 = reltime() " start the clock - - " XXX: - " if maxhi = 1 and <leader>w<leader>w before loading any vimwiki file - " cached 'subdir' is not set up - try - let subdir = VimwikiGet('subdir') - " FIXED: was previously converting './' to '../' - let invsubdir = VimwikiGet('invsubdir') - catch - let subdir = '' - let invsubdir = '' - endtry - +" 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) + let wiki_nr = a:wiki_nr + if wiki_nr >= 0 + let root_directory = VimwikiGet('path', wiki_nr) + else + let root_directory = VimwikiGet('path').VimwikiGet('diary_rel_path') + let wiki_nr = g:vimwiki_current_idx + endif + if a:directories_only + let ext = '/' + else + let ext = VimwikiGet('ext', wiki_nr) + endif " if current wiki is temporary -- was added by an arbitrary wiki file then do " not search wiki files in subdirectories. Or it would hang the system if " wiki file was created in $HOME or C:/ dirs. - if VimwikiGet('temp') - let search_dirs = '' + if VimwikiGet('temp', wiki_nr) + let pattern = '*'.ext else - let search_dirs = '**/' + let pattern = '**/*'.ext endif - " let globlinks = "\n".glob(VimwikiGet('path').search_dirs.a:pat,1)."\n" - - "save pwd, do lcd %:h, restore old pwd; getcwd() - " change to the directory of the current file - let orig_pwd = getcwd() - - " calling from other than vimwiki file - let path_base = vimwiki#u#path_norm(vimwiki#u#chomp_slash(VimwikiGet('path'))) - let path_file = vimwiki#u#path_norm(vimwiki#u#chomp_slash(expand('%:p:h'))) + return split(globpath(root_directory, pattern), '\n') +endfunction - if vimwiki#u#path_common_pfx(path_file, path_base) != path_base - exe 'lcd! '.path_base +" Returns: a list containing the links to all wiki files for the given wiki +" If the given wiki number is negative, the diary of the current wiki is used +function! vimwiki#base#get_wikilinks(wiki_nr) + let files = s: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 + let cwd = VimwikiGet('path').VimwikiGet('diary_rel_path') else - lcd! %:p:h + let cwd = VimwikiGet('path', a:wiki_nr) endif - - " all path are relative to the current file's location - let globlinks = "\n".glob(invsubdir.search_dirs.a:pat,1)."\n" - " remove extensions - let globlinks = substitute(globlinks,'\'.VimwikiGet('ext').'\ze\n', '', 'g') - " standardize path separators on Windows - let globlinks = substitute(globlinks,'\\', '/', 'g') - - " shortening those paths ../../dir1/dir2/ that can be shortened - " first for the current directory, then for parent etc. - let sp_rx = '\n\zs' . invsubdir . subdir . '\ze' - for i in range(len(invsubdir)/3) "XXX multibyte? - let globlinks = substitute(globlinks, sp_rx, '', 'g') - let sp_rx = substitute(sp_rx,'\\zs../','../\\zs','') - let sp_rx = substitute(sp_rx,'[^/]\+/\\ze','\\ze','') + let result = [] + for wikifile in files + let wikifile = fnamemodify(wikifile, ':r') " strip extension + let wikifile = vimwiki#path#relpath(cwd, wikifile) + call add(result, wikifile) endfor - " for directories: add ./ (instead of now empty) and invsubdir (if distinct) - if a:pat == '*/' - let globlinks = substitute(globlinks, "\n\n", "\n./\n",'') - if invsubdir != '' - let globlinks .= invsubdir."\n" - else - let globlinks .= "./\n" - endif - endif - - " restore the original working directory - exe 'lcd! '.orig_pwd + return result +endfunction - let time2 = vimwiki#u#time(time1) - call VimwikiLog_extend('timing',['base:afterglob('.len(split(globlinks, '\n')).')',time2]) - return globlinks -endfunction "}}} +" Returns: a list containing +function! vimwiki#base#get_wiki_directories(wiki_nr) + let dirs = s:find_files(a:wiki_nr, 1) + if a:wiki_nr == g:vimwiki_current_idx + let cwd = vimwiki#path#wikify_path(expand('%:p:h')) + else + let cwd = VimwikiGet('path', a:wiki_nr) + endif + let result = ['./'] + for wikidir in dirs + let wikidir = vimwiki#path#relpath(cwd, wikidir).'/' + call add(result, wikidir) + endfor + return result +endfunction function! vimwiki#base#get_anchors(filename, syntax) "{{{ if !filereadable(a:filename) @@ -707,6 +694,143 @@ function! s:jump_to_anchor(anchor) "{{{ endfor endfunction "}}} +function! s:link_target(file_from, wiki_nr, link_text) + let [idx, scheme, path, subdir, lnk, ext, url, anchor] = + \ vimwiki#base#resolve_scheme(a:link_text, 0) + let root_dir = fnamemodify(a:file_from, ':p:h').'/' + if lnk =~ '/$' " link to a directory + return [] + elseif url == '' && anchor != '' " only anchor + return [fnamemodify(a:file_from, ':p'), anchor] + elseif scheme == 'file' + return [url, ''] + elseif scheme == 'local' + return [vimwiki#path#normalize(root_dir.lnk), ''] + elseif idx >= len(g:vimwiki_list) + return ['', ''] " a malformed link + elseif scheme !~ '^wiki\d\+\|diary' + return [] + endif + if a:link_text !~ '^wiki\d\+:' + let idx = a:wiki_nr + let ext = VimwikiGet('ext', a:wiki_nr) + endif + if idx != a:wiki_nr + let root_dir = VimwikiGet('path', idx) + let ext = VimwikiGet('ext', idx) + endif + let target_file = root_dir . subdir . lnk . ext + return [vimwiki#path#normalize(target_file), anchor] +endfunction + +function! s:find_links(wikifile, idx) + if !filereadable(a:wikifile) + return [] + endif + + let syntax = VimwikiGet('syntax', a:idx) + let rxheader = g:vimwiki_{syntax}_wikilink + let links = [] + let lnum = 0 + + for line in readfile(a:wikifile) + let lnum += 1 + + let link_count = 1 + while 1 + let col = match(line, rxheader, 0, link_count)+1 + let link_text = matchstr(line, rxheader, 0, link_count) + if link_text == '' + break + endif + let link_count += 1 + let target = s:link_target(a:wikifile, a:idx, link_text) + if !empty(target) + call add(target, lnum) + call add(target, col) + call add(links, target) + endif + endwhile + endfor + + return links +endfunction + +function! vimwiki#base#check_links() + let anchors_of_files = {} + let links_of_files = {} + let errors = [] + for idx in range(len(g:vimwiki_list)) + let syntax = VimwikiGet('syntax', idx) + let wikifiles = s:find_files(idx, 0) + for wikifile in wikifiles + let links_of_files[wikifile] = s:find_links(wikifile, idx) + let anchors_of_files[wikifile] = vimwiki#base#get_anchors(wikifile, syntax) + endfor + endfor + + for wikifile in keys(links_of_files) + for [target_file, target_anchor, lnum, col] in links_of_files[wikifile] + if target_file == '' && target_anchor == '' + call add(errors, {'filename':wikifile, 'lnum':lnum, 'col':col, + \ 'text': "numbered scheme refers to a non-existent wiki"}) + elseif has_key(anchors_of_files, target_file) + if target_anchor != '' && index(anchors_of_files[target_file], target_anchor) < 0 + call add(errors, {'filename':wikifile, 'lnum':lnum, 'col':col, + \'text': "there is no such anchor: ".target_anchor}) + endif + else + if filereadable(target_file) " maybe it's a non-wiki file + let anchors_of_files[target_file] = [] + else + call add(errors, {'filename':wikifile, 'lnum':lnum, 'col':col, + \'text': "there is no such file: ".target_file}) + endif + endif + endfor + endfor + + let reachable_wikifiles = {} + for wikifile in keys(links_of_files) + let reachable_wikifiles[wikifile] = 0 + endfor + for idx in range(len(g:vimwiki_list)) + let index_file = VimwikiGet('path', idx) . VimwikiGet('index', idx) . + \ VimwikiGet('ext', idx) + let reachable_wikifiles[index_file] = 1 + endfor + while 1 + let visit_wikifile = '' + for wf in keys(reachable_wikifiles) + if reachable_wikifiles[wf] == 1 + let visit_wikifile = wf + let reachable_wikifiles[wf] = 2 + break + endif + endfor + if visit_wikifile == '' + break + endif + for [target_file, target_anchor, lnum, col] in links_of_files[visit_wikifile] + if has_key(reachable_wikifiles, target_file) && reachable_wikifiles[target_file] == 0 + let reachable_wikifiles[target_file] = 1 + endif + endfor + endwhile + for wf in keys(reachable_wikifiles) + if reachable_wikifiles[wf] == 0 + call add(errors, {'text':wf." is not reachable from the index file"}) + endif + endfor + + if empty(errors) + echom 'vimwiki: all links are OK' + else + call setqflist(errors, 'r') + copen + endif +endfunction + " vimwiki#base#edit_file function! vimwiki#base#edit_file(command, filename, anchor, ...) "{{{ " XXX: Should we allow * in filenames!? diff --git a/autoload/vimwiki/u.vim b/autoload/vimwiki/u.vim @@ -43,6 +43,11 @@ function vimwiki#u#reload_regexes() "{{{ execute 'runtime! syntax/vimwiki_'.VimwikiGet('syntax').'.vim' endfunction "}}} +" Load omnipresent Wiki syntax +function vimwiki#u#reload_omni_regexes() "{{{ + execute 'runtime! syntax/omnipresent_syntax.vim' +endfunction "}}} + " Load syntax-specific functionality function vimwiki#u#reload_regexes_custom() "{{{ execute 'runtime! syntax/vimwiki_'.VimwikiGet('syntax').'_custom.vim' diff --git a/doc/vimwiki.txt b/doc/vimwiki.txt @@ -697,6 +697,12 @@ il A single list item. Create or update the Table of Contents for the current wiki file. See |vimwiki-toc| +*:VimwikiCheckLinks* + Search through all wiki files and check if the targets of all wiki links + and links to external files actually exist. It checks also if all wiki + files are reachable from the index file. Errors are shown in the quickfix + window. + ============================================================================== 5. Wiki syntax *vimwiki-syntax* @@ -2603,6 +2609,7 @@ Vim plugins: http://www.vim.org/scripts/script.php?script_id=2226 * improved automatic adjustment of checkboxes * text objects for list items, see |vimwiki-text-objects| * g:vimwiki_auto_checkbox is now useless and removed + * Add the command |VimwikiCheckLinks| to check for broken links * Issue 415: Disable folding if g:vimwiki_folding is set to '' * Fix slowdown in Vim 7.4 * Issue #12: Separate diaries from different wikis diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim @@ -9,6 +9,7 @@ endif let b:did_ftplugin = 1 " Don't load another plugin for this buffer call vimwiki#u#reload_regexes() +call vimwiki#u#reload_omni_regexes() " UNDO list {{{ " Reset the following options to undo this plugin. @@ -31,23 +32,9 @@ execute 'setlocal suffixesadd='.VimwikiGet('ext') setlocal isfname-=[,] " gf}}} -" omnicomplete function for wiki files and anchors {{{ - -let g:vimwiki_default_header_search = '^\s*\(=\{1,6}\)\([^=].*[^=]\)\1\s*$' -let g:vimwiki_default_header_match = '^\s*\(=\{1,6}\)=\@!\s*__Header__\s*\1=\@!\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_media_header_search = '^\s*\(=\{1,6}\)\([^=].*[^=]\)\1\s*$' -let g:vimwiki_media_header_match = '^\s*\(=\{1,6}\)=\@!\s*__Header__\s*\1=\@!\s*$' -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_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_media_bold_search = "'''\\zs[^']\\+\\ze'''" -let g:vimwiki_media_bold_match = '''''''__Text__''''''' -" ^- looks strange, but is equivalent to "'''__Text__'''" but since we later -" want to call escape() on this string, we must keep it in single quotes +" MISC }}} +" COMPLETION {{{ function! Complete_wikifiles(findstart, base) if a:findstart == 1 let column = col('.')-1 @@ -72,35 +59,23 @@ function! Complete_wikifiles(findstart, base) if wikinumber >= len(g:vimwiki_list) return [] endif - let directory = VimwikiGet('path', wikinumber) - let ext = VimwikiGet('ext', wikinumber) let prefix = matchstr(a:base, '^wiki\d:\zs.*') let scheme = matchstr(a:base, '^wiki\d:\ze') elseif a:base =~# '^diary:' - let wikinumber = g:vimwiki_current_idx - let directory = VimwikiGet('path').'/'.VimwikiGet('diary_rel_path') - let ext = VimwikiGet('ext') + let wikinumber = -1 let prefix = matchstr(a:base, '^diary:\zs.*') let scheme = matchstr(a:base, '^diary:\ze') else " current wiki let wikinumber = g:vimwiki_current_idx - let directory = VimwikiGet('path') - let ext = VimwikiGet('ext') let prefix = a:base let scheme = '' endif + let links = vimwiki#base#get_wikilinks(wikinumber) let result = [] - if wikinumber == g:vimwiki_current_idx - let cwd = vimwiki#u#wikify_path(expand('%:p:h')) - else - let cwd = vimwiki#u#wikify_path(directory) - endif - for wikifile in split(globpath(directory, '**/*'.ext), '\n') - let wikifile = vimwiki#u#wikify_path(fnamemodify(wikifile, ':r')) - let relative_filename = vimwiki#u#relpath(cwd, wikifile) - if relative_filename =~ '^'.vimwiki#u#escape(prefix) - call add(result, scheme . relative_filename) + for wikifile in links + if wikifile =~ '^'.vimwiki#u#escape(prefix) + call add(result, scheme . wikifile) endif endfor return result @@ -127,10 +102,9 @@ function! Complete_wikifiles(findstart, base) endif endif endfunction -setlocal omnifunc=Complete_wikifiles -" omnicomplete }}} -" MISC }}} +setlocal omnifunc=Complete_wikifiles +" COMPLETION }}} " LIST STUFF {{{ " settings necessary for the automatic formatting of lists @@ -286,6 +260,7 @@ exe 'command! -buffer -nargs=* VWS lvimgrep <args> '. command! -buffer -nargs=+ -complete=custom,vimwiki#base#complete_links_escaped \ VimwikiGoto call vimwiki#base#goto(<f-args>) +command! -buffer VimwikiCheckLinks call vimwiki#base#check_links() " list commands command! -buffer -nargs=+ VimwikiReturn call <SID>CR(<f-args>) diff --git a/syntax/omnipresent_syntax.vim b/syntax/omnipresent_syntax.vim @@ -0,0 +1,30 @@ +" vim:tabstop=2:shiftwidth=2:expandtab:foldmethod=marker:textwidth=79 +" Vimwiki syntax file +" Syntax definitions which are always available +" Author: Daniel Schemala <istjanichtzufassen@gmail.com> +" Home: http://code.google.com/p/vimwiki/ + + +" Define Regexes of anchors for every syntax. +" This has to be separated from vimwiki_default.vim, vimwiki_markdown.vim, etc. +" because the latter are only loaded and available if the current wiki has the +" corresponding syntax +let g:vimwiki_default_header_search = '^\s*\(=\{1,6}\)\([^=].*[^=]\)\1\s*$' +let g:vimwiki_default_header_match = '^\s*\(=\{1,6}\)=\@!\s*__Header__\s*\1=\@!\s*$' +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_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 hier fehlen noch welche + +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*$' +let g:vimwiki_media_bold_search = "'''\\zs[^']\\+\\ze'''" +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 diff --git a/syntax/vimwiki.vim b/syntax/vimwiki.vim @@ -13,8 +13,9 @@ endif "TODO do nothing if ...? (?) let g:starttime = reltime() " start the clock if VimwikiGet('maxhi') - let b:existing_wikifiles = vimwiki#base#get_links('*'.VimwikiGet('ext')) - let b:existing_wikidirs = vimwiki#base#get_links('*/') + let b:existing_wikifiles = vimwiki#base#get_wikilinks(g:vimwiki_current_idx) + let b:existing_wikidirs = + \ vimwiki#base#get_wiki_directories(g:vimwiki_current_idx) endif let s:timescans = vimwiki#u#time(g:starttime) "XXX "let b:xxx = 1