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'), ' ')
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
return result
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
" restore the original working directory
exe 'lcd! '.orig_pwd
let time2 = vimwiki#u#time(time1)
call VimwikiLog_extend('timing',['base:afterglob('.len(split(globlinks, '\n')).')',time2])
return globlinks
endfunction "}}}
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!?

View File

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

View File

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

View File

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

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 ...? (?)
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