New command :VimwikiCheckLinks

while we are at it, refactor the code to search through files
This commit is contained in:
EinfachToll 2014-12-04 21:18:58 +01:00
parent eb02e0be9a
commit 931b40ade1
6 changed files with 245 additions and 103 deletions

View File

@ -551,81 +551,68 @@ function! vimwiki#base#backlinks() "{{{
\ escape(VimwikiGet('path').'**/*'.VimwikiGet('ext'), ' ') \ escape(VimwikiGet('path').'**/*'.VimwikiGet('ext'), ' ')
endfunction "}}} endfunction "}}}
" vimwiki#base#get_links " Returns: a list containing all files of the given wiki as absolute file path.
function! vimwiki#base#get_links(pat) "{{{ return string-list for files " If the given wiki number is negative, the diary of the current wiki is used
" in the current wiki matching the pattern "pat" " If the second argument is not zero, only directories are found
" search all wiki files (or directories) in wiki 'path' and its subdirs. function! s:find_files(wiki_nr, directories_only)
let wiki_nr = a:wiki_nr
let time1 = reltime() " start the clock if wiki_nr >= 0
let root_directory = VimwikiGet('path', wiki_nr)
" XXX: else
" if maxhi = 1 and <leader>w<leader>w before loading any vimwiki file let root_directory = VimwikiGet('path').VimwikiGet('diary_rel_path')
" cached 'subdir' is not set up let wiki_nr = g:vimwiki_current_idx
try endif
let subdir = VimwikiGet('subdir') if a:directories_only
" FIXED: was previously converting './' to '../' let ext = '/'
let invsubdir = VimwikiGet('invsubdir') else
catch let ext = VimwikiGet('ext', wiki_nr)
let subdir = '' endif
let invsubdir = ''
endtry
" if current wiki is temporary -- was added by an arbitrary wiki file then do " 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 " not search wiki files in subdirectories. Or it would hang the system if
" wiki file was created in $HOME or C:/ dirs. " wiki file was created in $HOME or C:/ dirs.
if VimwikiGet('temp') if VimwikiGet('temp', wiki_nr)
let search_dirs = '' let pattern = '*'.ext
else else
let search_dirs = '**/' let pattern = '**/*'.ext
endif endif
" let globlinks = "\n".glob(VimwikiGet('path').search_dirs.a:pat,1)."\n" return split(globpath(root_directory, pattern), '\n')
endfunction
"save pwd, do lcd %:h, restore old pwd; getcwd() " Returns: a list containing the links to all wiki files for the given wiki
" change to the directory of the current file " If the given wiki number is negative, the diary of the current wiki is used
let orig_pwd = getcwd() function! vimwiki#base#get_wikilinks(wiki_nr)
let files = s:find_files(a:wiki_nr, 0)
" calling from other than vimwiki file if a:wiki_nr == g:vimwiki_current_idx
let path_base = vimwiki#u#path_norm(vimwiki#u#chomp_slash(VimwikiGet('path'))) let cwd = vimwiki#path#wikify_path(expand('%:p:h'))
let path_file = vimwiki#u#path_norm(vimwiki#u#chomp_slash(expand('%:p:h'))) elseif a:wiki_nr < 0
let cwd = VimwikiGet('path').VimwikiGet('diary_rel_path')
if vimwiki#u#path_common_pfx(path_file, path_base) != path_base
exe 'lcd! '.path_base
else else
lcd! %:p:h let cwd = VimwikiGet('path', a:wiki_nr)
endif endif
let result = []
" all path are relative to the current file's location for wikifile in files
let globlinks = "\n".glob(invsubdir.search_dirs.a:pat,1)."\n" let wikifile = fnamemodify(wikifile, ':r') " strip extension
" remove extensions let wikifile = vimwiki#path#relpath(cwd, wikifile)
let globlinks = substitute(globlinks,'\'.VimwikiGet('ext').'\ze\n', '', 'g') call add(result, wikifile)
" 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','')
endfor endfor
" for directories: add ./ (instead of now empty) and invsubdir (if distinct) return result
if a:pat == '*/' endfunction
let globlinks = substitute(globlinks, "\n\n", "\n./\n",'')
if invsubdir != '' " Returns: a list containing
let globlinks .= invsubdir."\n" 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 else
let globlinks .= "./\n" let cwd = VimwikiGet('path', a:wiki_nr)
endif endif
endif let result = ['./']
for wikidir in dirs
" restore the original working directory let wikidir = vimwiki#path#relpath(cwd, wikidir).'/'
exe 'lcd! '.orig_pwd call add(result, wikidir)
endfor
let time2 = vimwiki#u#time(time1) return result
call VimwikiLog_extend('timing',['base:afterglob('.len(split(globlinks, '\n')).')',time2]) endfunction
return globlinks
endfunction "}}}
function! vimwiki#base#get_anchors(filename, syntax) "{{{ function! vimwiki#base#get_anchors(filename, syntax) "{{{
if !filereadable(a:filename) if !filereadable(a:filename)
@ -707,6 +694,143 @@ function! s:jump_to_anchor(anchor) "{{{
endfor endfor
endfunction "}}} 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 " vimwiki#base#edit_file
function! vimwiki#base#edit_file(command, filename, anchor, ...) "{{{ function! vimwiki#base#edit_file(command, filename, anchor, ...) "{{{
" XXX: Should we allow * in filenames!? " XXX: Should we allow * in filenames!?

View File

@ -43,6 +43,11 @@ function vimwiki#u#reload_regexes() "{{{
execute 'runtime! syntax/vimwiki_'.VimwikiGet('syntax').'.vim' execute 'runtime! syntax/vimwiki_'.VimwikiGet('syntax').'.vim'
endfunction "}}} endfunction "}}}
" Load omnipresent Wiki syntax
function vimwiki#u#reload_omni_regexes() "{{{
execute 'runtime! syntax/omnipresent_syntax.vim'
endfunction "}}}
" Load syntax-specific functionality " Load syntax-specific functionality
function vimwiki#u#reload_regexes_custom() "{{{ function vimwiki#u#reload_regexes_custom() "{{{
execute 'runtime! syntax/vimwiki_'.VimwikiGet('syntax').'_custom.vim' execute 'runtime! syntax/vimwiki_'.VimwikiGet('syntax').'_custom.vim'

View File

@ -697,6 +697,12 @@ il A single list item.
Create or update the Table of Contents for the current wiki file. Create or update the Table of Contents for the current wiki file.
See |vimwiki-toc| 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* 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 * improved automatic adjustment of checkboxes
* text objects for list items, see |vimwiki-text-objects| * text objects for list items, see |vimwiki-text-objects|
* g:vimwiki_auto_checkbox is now useless and removed * 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 '' * Issue 415: Disable folding if g:vimwiki_folding is set to ''
* Fix slowdown in Vim 7.4 * Fix slowdown in Vim 7.4
* Issue #12: Separate diaries from different wikis * Issue #12: Separate diaries from different wikis

View File

@ -9,6 +9,7 @@ endif
let b:did_ftplugin = 1 " Don't load another plugin for this buffer let b:did_ftplugin = 1 " Don't load another plugin for this buffer
call vimwiki#u#reload_regexes() call vimwiki#u#reload_regexes()
call vimwiki#u#reload_omni_regexes()
" UNDO list {{{ " UNDO list {{{
" Reset the following options to undo this plugin. " Reset the following options to undo this plugin.
@ -31,23 +32,9 @@ execute 'setlocal suffixesadd='.VimwikiGet('ext')
setlocal isfname-=[,] setlocal isfname-=[,]
" gf}}} " gf}}}
" omnicomplete function for wiki files and anchors {{{ " MISC }}}
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
" COMPLETION {{{
function! Complete_wikifiles(findstart, base) function! Complete_wikifiles(findstart, base)
if a:findstart == 1 if a:findstart == 1
let column = col('.')-1 let column = col('.')-1
@ -72,35 +59,23 @@ function! Complete_wikifiles(findstart, base)
if wikinumber >= len(g:vimwiki_list) if wikinumber >= len(g:vimwiki_list)
return [] return []
endif endif
let directory = VimwikiGet('path', wikinumber)
let ext = VimwikiGet('ext', wikinumber)
let prefix = matchstr(a:base, '^wiki\d:\zs.*') let prefix = matchstr(a:base, '^wiki\d:\zs.*')
let scheme = matchstr(a:base, '^wiki\d:\ze') let scheme = matchstr(a:base, '^wiki\d:\ze')
elseif a:base =~# '^diary:' elseif a:base =~# '^diary:'
let wikinumber = g:vimwiki_current_idx let wikinumber = -1
let directory = VimwikiGet('path').'/'.VimwikiGet('diary_rel_path')
let ext = VimwikiGet('ext')
let prefix = matchstr(a:base, '^diary:\zs.*') let prefix = matchstr(a:base, '^diary:\zs.*')
let scheme = matchstr(a:base, '^diary:\ze') let scheme = matchstr(a:base, '^diary:\ze')
else " current wiki else " current wiki
let wikinumber = g:vimwiki_current_idx let wikinumber = g:vimwiki_current_idx
let directory = VimwikiGet('path')
let ext = VimwikiGet('ext')
let prefix = a:base let prefix = a:base
let scheme = '' let scheme = ''
endif endif
let links = vimwiki#base#get_wikilinks(wikinumber)
let result = [] let result = []
if wikinumber == g:vimwiki_current_idx for wikifile in links
let cwd = vimwiki#u#wikify_path(expand('%:p:h')) if wikifile =~ '^'.vimwiki#u#escape(prefix)
else call add(result, scheme . wikifile)
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)
endif endif
endfor endfor
return result return result
@ -127,10 +102,9 @@ function! Complete_wikifiles(findstart, base)
endif endif
endif endif
endfunction endfunction
setlocal omnifunc=Complete_wikifiles
" omnicomplete }}}
" MISC }}} setlocal omnifunc=Complete_wikifiles
" COMPLETION }}}
" LIST STUFF {{{ " LIST STUFF {{{
" settings necessary for the automatic formatting of lists " 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 command! -buffer -nargs=+ -complete=custom,vimwiki#base#complete_links_escaped
\ VimwikiGoto call vimwiki#base#goto(<f-args>) \ VimwikiGoto call vimwiki#base#goto(<f-args>)
command! -buffer VimwikiCheckLinks call vimwiki#base#check_links()
" list commands " list commands
command! -buffer -nargs=+ VimwikiReturn call <SID>CR(<f-args>) command! -buffer -nargs=+ VimwikiReturn call <SID>CR(<f-args>)

View File

@ -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

View File

@ -13,8 +13,9 @@ endif
"TODO do nothing if ...? (?) "TODO do nothing if ...? (?)
let g:starttime = reltime() " start the clock let g:starttime = reltime() " start the clock
if VimwikiGet('maxhi') if VimwikiGet('maxhi')
let b:existing_wikifiles = vimwiki#base#get_links('*'.VimwikiGet('ext')) let b:existing_wikifiles = vimwiki#base#get_wikilinks(g:vimwiki_current_idx)
let b:existing_wikidirs = vimwiki#base#get_links('*/') let b:existing_wikidirs =
\ vimwiki#base#get_wiki_directories(g:vimwiki_current_idx)
endif endif
let s:timescans = vimwiki#u#time(g:starttime) "XXX let s:timescans = vimwiki#u#time(g:starttime) "XXX
"let b:xxx = 1 "let b:xxx = 1