├── README.md ├── syntax_checkers └── help │ └── vimhelplint.vim ├── doc ├── vimhelplint.jax └── vimhelplint.txt └── ftplugin └── help_lint.vim /README.md: -------------------------------------------------------------------------------- 1 | # vim-vimhelplint 2 | 3 | ## How to use 4 | 5 | 1. Install **vim-vimhelplint** 6 | - Copy `ftplugin/help_lint.vim` to your `~/.vim/ftplugin/help_lint.vim`. 7 | - Or you can use your favorite plugin manager ([Vundle](https://github.com/gmarik/Vundle.vim), [Neobundle](https://github.com/Shougo/neobundle.vim), [vim-plug](https://github.com/junegunn/vim-plug)). 8 | 9 | ```vim 10 | " Vundle 11 | Plugin 'machakann/vim-vimhelplint' 12 | 13 | " Neobundle 14 | NeoBundle 'machakann/vim-vimhelplint' 15 | 16 | " vim-plug 17 | Plug 'machakann/vim-vimhelplint' 18 | ``` 19 | 20 | 1. Open Vim and edit your help file: `:edit path/to/your_help_file.txt` 21 | 22 | 1. Execute the ex command `:VimhelpLint`, and it will report errors to the 23 | quickfix list. You can use e.g. `:copen` to open the list then. 24 | Use `:VimhelpLint!` to open the quickfix window automatically. 25 | 26 | --- 27 | 28 | Or if you are using [Neomake](https://github.com/neomake/neomake), run the shell command in the root directory of Neomake plugin. 29 | 30 | ``` 31 | make build/vimhelplint 32 | ``` 33 | 34 | Then it automatically downloads vimhelplint, you can run it by: 35 | 36 | ``` 37 | :Neomake vimhelplint 38 | ``` 39 | 40 | ## Integration in CI 41 | 42 | You can run the plugin automatically as follows, e.g. on Travis CI: 43 | 44 | ```sh 45 | vim -esN --cmd 'set rtp+=path/to/vim-vimhelplint' -c 'filetype plugin on' \ 46 | -c 'e doc/yourplugin.txt' -c 'verb VimhelpLintEcho' -c q 47 | ``` 48 | -------------------------------------------------------------------------------- /syntax_checkers/help/vimhelplint.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_syntastic_help_vimhelplint_checker') 2 | finish 3 | endif 4 | let g:loaded_syntastic_help_vimhelplint_checker = 1 5 | 6 | if !exists('g:syntastic_help_vimhelplint_sort') 7 | let g:syntastic_help_vimhelplint_sort = 1 8 | endif 9 | 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | function! SyntaxCheckers_help_vimhelplint_IsAvailable() dict 14 | return exists('*VimhelpLintGetQflist') 15 | endfunction 16 | 17 | function! SyntaxCheckers_help_vimhelplint_GetHighlightRegex(item) 18 | let pattern = '' 19 | if a:item.bufnr == bufnr('%') 20 | if a:item.nr == 1 21 | let pattern = printf('\%%%dl', a:item.lnum) 22 | elseif a:item.nr == 2 23 | let str = matchstr(a:item.text, 'A tag "\zs.\+\ze" is duplicate with another in this file\.') 24 | let pattern = printf('\%%%dl\%%>%dc%s', a:item.lnum, a:item.col-1, printf('*%s*', str)) 25 | elseif a:item.nr == 3 26 | let str = matchstr(a:item.text, 'A tag "\zs.\+\ze" is duplicate with another in the file') 27 | let pattern = printf('\%%%dl\%%>%dc%s', a:item.lnum, a:item.col-1, printf('*%s*', str)) 28 | elseif a:item.nr == 4 29 | let str = matchstr(a:item.text, 'A link "\zs.\+\ze" does not have a corresponding tag\.') 30 | let pattern = printf('\%%%dl\%%>%dc%s', a:item.lnum, a:item.col-1, printf('|%s|', str)) 31 | elseif a:item.nr == 5 32 | let str = matchstr(a:item.text, 'A link "\zs.\+\ze" does not have a corresponding tag\.') 33 | let pattern = printf('\%%%dl\%%>%dc%s', a:item.lnum, a:item.col-1, printf('|%s|', str)) 34 | elseif a:item.nr == 6 35 | let str = matchstr(a:item.text, 'There is a tag "\zs.\+\ze". Is it consistent with a link') 36 | let pattern = printf('\%%%dl\%%>%dc%s', a:item.lnum, a:item.col-1, printf('|%s|', str)) 37 | endif 38 | endif 39 | 40 | return pattern 41 | endfunction 42 | 43 | function! SyntaxCheckers_help_vimhelplint_GetLocList() dict 44 | return VimhelpLintGetQflist() 45 | endfunction 46 | 47 | call g:SyntasticRegistry.CreateAndRegisterChecker({ 48 | \ 'filetype': 'help', 49 | \ 'name': 'vimhelplint', 50 | \ 'exec': '' }) 51 | 52 | let &cpo = s:save_cpo 53 | unlet s:save_cpo 54 | 55 | " vim: set sw=4 sts=4 et fdm=marker: 56 | -------------------------------------------------------------------------------- /doc/vimhelplint.jax: -------------------------------------------------------------------------------- 1 | *vimhelplint.jax* Vim ヘルプファイル用の静的構文チェック 2 | Last change:06-Dec-2019. 3 | 4 | 書いた人 : machakann 5 | ライセンス : NYSL ライセンス 6 | 日本語版 7 | 英語版 (非公式) 8 | 9 | 必要要件: Vim 7.4 かそれ以降の Vim エディタであること 10 | 11 | ============================================================================== 12 | INDEX *vimhelplint-index* 13 | 14 | USAGE |vimhelplint-usage| 15 | COMMANDS |vimhelplint-commands| 16 | FUNCTIONS |vimhelplint-functions| 17 | COOPERATION WITH OTHER PLUGINS |vimhelplint-cooperation| 18 | 19 | ============================================================================== 20 | USAGE *vimhelplint-usage* 21 | 22 | *vimhelplint* は静的構文チェックを行うためのVimヘルプファイル用ファイルタイプ 23 | プラグインです。 24 | 25 | このプラグインはコマンド |:VimhelpLint| を定義します。このコマンドは編集中のフ 26 | ァイル、およびそのファイルと同じフォルダに存在するヘルプファイルをチェックしま 27 | す。 28 | > 29 | :VimhelpLint 30 | < 31 | 結果は |quickfix| へ送られます。結果を確認のために |:copen|, |:cnext| などのコ 32 | マンドをご使用ください。 |:VimhelpLint| コマンドに!修飾子をつけると 33 | |quickfix-window| を自動的に開きます。 34 | > 35 | :VimhelpLint! 36 | < 37 | 38 | もし編集中のファイルがあまり大きくなければ自動的にチェックを走らせるのも便利か 39 | もしれません。 40 | > 41 | autocmd BufWritePost */doc/*.txt :silent VimhelpLint! 42 | < 43 | ただし、残念ながら |:VimhelpLint| コマンドはしばしば時間がかかってしまうことが 44 | あることを留意しておいてください。 45 | 46 | ============================================================================== 47 | ERROR LIST *vimhelplint-error-list* 48 | 49 | このリストは将来更新される可能性があります。 50 | 51 | 番号 種類 説明~ 52 | ------------------------------------------------------------------------------ 53 | 1 警告 一行が長すぎます。推奨される一行の長さは78桁です。 54 | |help-writing| ただし、79桁目の改行文字は許されます。 55 | 56 | 2 エラー 同じファイルの中でタグが重複しています。 57 | 58 | 3 エラー 別のファイルのタグと重複しています。 59 | 60 | 4 エラー 有効でないリンクです。対応するタグが見つかりません。 61 | 62 | 5 警告 おそらくリンクとしては有効に働きますが、正確な記述では 63 | ありません。スコーププレフィックス (g:, t:, w:, b:) の 64 | 書き忘れと思われます。 65 | 66 | 6 警告 おそらくリンクとしては有効に働きますが、正確な記述では 67 | ありません。 68 | 69 | 8 警告 Vim のオプションにリンク表記を使うべきではありません。 70 | ------------------------------------------------------------------------------ 71 | 72 | 以下は既に廃止されたエラーおよび警告のリストです。 73 | 74 | No. Type Note~ 75 | ------------------------------------------------------------------------------ 76 | 7 Error 『当該オプション名はタグジャンプできません。クオーテー 77 | ションの前にスペースを挿入してください。』 78 | この問題は patch-7.4.1568 によって既に解決しました。 79 | ------------------------------------------------------------------------------ 80 | 81 | ============================================================================== 82 | COMMANDS *vimhelplint-commands* 83 | 84 | :VimhelpLint *:VimhelpLint* 85 | 構文チェックを実行し、エラーを |quickfix| リストへ送ります。 86 | ! 修飾子 |:command-bang| 付きで実行された場合は、エラーがあれば 87 | |quickfix-window| を開き、なければこれを閉じます。 88 | 89 | :VimhelpLintEcho *:VimhelpLintEcho* 90 | 構文チェックを実行し、結果を表示します。このメッセージは次の 91 | 'errorformat' パターンによって解釈できる形式で出力されます。 92 | > 93 | set efm=%f:%l:%c:%trror:%n:%m,%f:%l:%c:%tarning:%n:%m 94 | < 95 | ============================================================================== 96 | FUNCTIONS *vimhelplint-functions* 97 | 98 | VimhelpLintGetQflist() *VimhelpLintGetQflist()* 99 | 編集中のヘルプファイルのエラーをリストにして返します。返り値は 100 | |setqflist()| の引数とすることのできる形式です。 101 | 102 | ============================================================================== 103 | COOPERATION WITH OTHER PLUGINS *vimhelplint-cooperation* 104 | 105 | |vimhelplint| は下記のプラグインと連動します。 106 | 107 | vim-hier~ 108 | vim-hier (https://github.com/jceb/vim-hier) は |quickfix| のエラー箇所をハイラ 109 | イトするためのプラグインです。エラー箇所を見易くしてくれます。 vim-hier が利用 110 | 可能な場合 |vimhelplint| は自動的にこれと連動します。特別な設定は必要ありませ 111 | ん。 112 | 113 | 114 | 以下のプラグインは構文チェックを自動的に行うためのプラグインです。これらは便利 115 | ですが |:VimhelpLint| はしばしば時間がかかってしまう点を忘れないでください。 116 | 117 | vim-watchdogs~ 118 | |watchdogs.vim| (https://github.com/osyo-manga/vim-watchdogs) は非同期に構文 119 | チェックを行うためのプラグインです。使うためにはまず |watchdogs.vim| と必要な 120 | もの (上のURLを確認してください) をインストールし、次の設定を vimrc に追加して 121 | ください。 122 | > 123 | let g:quickrun_config['help/watchdogs_checker'] = { 124 | \ 'type': 'watchdogs_checker/vimhelplint', 125 | \ } 126 | < 127 | 128 | syntastic~ 129 | |syntastic| (https://github.com/scrooloose/syntastic) は自動的に構文チェックを 130 | 行うためのプラグインです。 |syntastic| をインストールし、次の設定を vimrc に追 131 | 加してください。 132 | > 133 | let g:syntastic_help_checkers = ['vimhelplint'] 134 | < 135 | 136 | Neomake~ 137 | Neomake (https://github.com/neomake/neomake) は Neovim/Vim の両方に対応した構 138 | 文チェックを非同期に行うためのフレームワークです。インストールの後、 Neomake 139 | のルートディレクトリで以下を実行すると vimhelplint を自動でダウンロードしま 140 | す。 141 | > 142 | make build/vimhelplint 143 | < 144 | あとは次のコマンドで構文チェックを実行できます。詳しくは |Neomake.txt| を参照 145 | してください。 146 | > 147 | :Neomake vimhelplint 148 | < 149 | ============================================================================== 150 | vim:tw=78:ts=8:ft=help:norl:noet: 151 | -------------------------------------------------------------------------------- /doc/vimhelplint.txt: -------------------------------------------------------------------------------- 1 | *vimhelplint.txt* A lint tool for vim help files. 2 | Last change:13-Mar-2017. 3 | 4 | Author : machakann 5 | License : NYSL license 6 | Japanese 7 | English (Unofficial) 8 | 9 | Requirement: Vim 7.4 or higher 10 | 11 | ============================================================================== 12 | INDEX *vimhelplint-index* 13 | 14 | USAGE |vimhelplint-usage| 15 | ERROR LIST |vimhelplint-error-list| 16 | COMMANDS |vimhelplint-commands| 17 | FUNCTIONS |vimhelplint-functions| 18 | COOPERATION WITH OTHER PLUGINS |vimhelplint-cooperation| 19 | 20 | ============================================================================== 21 | USAGE *vimhelplint-usage* 22 | 23 | *vimhelplint* is a filetype plugin for vim help files to carry out static 24 | syntax check. 25 | 26 | This plugin defines a command |:VimhelpLint|, the command checks 27 | the help file that user is editing (and the files in the same folder with it) 28 | are checked. 29 | > 30 | :VimhelpLint 31 | < 32 | It emits the result to |quickfix|, thus use |:copen|, |:cnext| and 33 | so on to find errors. If user add bang (!) to the command, it opens 34 | |quickfix-window| automatically. 35 | > 36 | :VimhelpLint! 37 | < 38 | 39 | If the editing file is not so big, it might be convenient to run syntax check 40 | automatically when user |:write| the file. 41 | > 42 | autocmd BufWritePost */doc/*.txt :silent VimhelpLint! 43 | < 44 | However please remember that |:VimhelpLint| command sometimes gets heavy. 45 | 46 | ============================================================================== 47 | ERROR LIST *vimhelplint-error-list* 48 | 49 | The list of error items might be updated in the future. 50 | 51 | No. Type Note~ 52 | ------------------------------------------------------------------------------ 53 | 1 Warning The line is too long. According to the recommended 54 | style, it should be no longer than 78. |help-writing| 55 | The breakings "\n" at 79th column is allowed. 56 | 57 | 2 Error The tag is duplicate with another in the same file. 58 | 59 | 3 Error The tag is duplicate with another in a external help. 60 | 61 | 4 Error An orphan link. No corresponding tag has found. 62 | 63 | 5 Warning The link probably works, but it is not an exact form. 64 | A scope prefix may be dropped. 65 | 66 | 6 Warning The link probably works, but it is not an exact form. 67 | 68 | 8 Warning An Vim's option should not be link notation. 69 | ------------------------------------------------------------------------------ 70 | 71 | The obsolete errors and warnings are listed below. 72 | 73 | No. Type Note~ 74 | ------------------------------------------------------------------------------ 75 | 7 Error "The link of an option name is not jumpable. Need a 76 | space before the former quote." 77 | This problem has been solved by patch-7.4.1568. 78 | ------------------------------------------------------------------------------ 79 | 80 | ============================================================================== 81 | COMMANDS *vimhelplint-commands* 82 | 83 | :VimhelpLint *:VimhelpLint* 84 | Runs syntax check and add errors to |quickfix| list. If user executes 85 | this command with '!' modifier |:command-bang|, it opens 86 | |quickfix-window| with errors or closes the window without errors. 87 | 88 | :VimhelpLintEcho *:VimhelpLintEcho* 89 | Runs syntax check and echo results. The messages are formatted as 90 | resolved by the following 'errorformat' patterns. 91 | > 92 | set efm=%f:%l:%c:%trror:%n:%m,%f:%l:%c:%tarning:%n:%m 93 | < 94 | ============================================================================== 95 | FUNCTIONS *vimhelplint-functions* 96 | 97 | VimhelpLintGetQflist() *VimhelpLintGetQflist()* 98 | Returns a list of errors in the editing help files. The returned list 99 | could be an argument of |setqflist()|. 100 | 101 | ============================================================================== 102 | COOPERATION WITH OTHER PLUGINS *vimhelplint-cooperation* 103 | 104 | |vimhelplint| could cooperate with the listed plugins. 105 | 106 | vim-hier~ 107 | vim-hier (https://github.com/jceb/vim-hier) is a plugin to highlight lines 108 | registered in |quickfix|. This plugin makes easier to find erroneous lines. If 109 | vim-hier is available, |vimhelplint| use it automatically without any setting. 110 | 111 | 112 | The following plugins can run syntax check automatically. Note that running 113 | vimhelplint is slow in general, so you better use one with async support, and 114 | you should be careful about enabling it by default / invoking it too often. 115 | 116 | vim-watchdogs~ 117 | |watchdogs.vim| (https://github.com/osyo-manga/vim-watchdogs) is a plugin for 118 | asynchronous syntax checking. Install the |watchdogs.vim| and the requirements 119 | (see the web page) and add |vimhelplint| as a syntax checker in vimrc. 120 | > 121 | let g:quickrun_config['help/watchdogs_checker'] = { 122 | \ 'type': 'watchdogs_checker/vimhelplint', 123 | \ } 124 | < 125 | 126 | syntastic~ 127 | |Syntastic| (https://github.com/scrooloose/syntastic) is a plugin to do syntax 128 | check automatically. Install it and write a line to vimrc. 129 | > 130 | let g:syntastic_help_checkers = ['vimhelplint'] 131 | < 132 | 133 | Neomake~ 134 | Neomake (https://github.com/neomake/neomake) is a framework for Neovim/Vim to 135 | run lint tools asynchronously. Install it and run the following in the root 136 | directory of the plugin to install vimhelplint: 137 | > 138 | make build/vimhelplint 139 | < 140 | Then you can run vimhelplint asynchronously and more (see |Neomake|): 141 | > 142 | :Neomake vimhelplint 143 | < 144 | ============================================================================== 145 | vim:tw=78:ts=8:ft=help:norl:noet: 146 | -------------------------------------------------------------------------------- /ftplugin/help_lint.vim: -------------------------------------------------------------------------------- 1 | " A lint tool for vim help files. 2 | " Maintainer: Masaaki Nakamura 3 | " Last Change: 06-Dec-2019. 4 | " License: NYSL license 5 | " Japanese 6 | " English (Unofficial) 7 | 8 | " NOTE: Usage when you edit a vim help file, type: 9 | " :VimhelpLint 10 | " Or if you add bang '!', then it opens quickfix window if there were 11 | " error. 12 | " :VimhelpLint! 13 | 14 | " NOTE: Error list 15 | " 1 : Warning : The width of a line should be no longer than 78. 16 | " 2 : Error : Duplicate tags in the same file. 17 | " 3 : Error : Duplicate tags in another file. 18 | " 4 : Error : A hot link is not linked to any tags. 19 | " 5 : Warning : A tag seems to have inconsistency with a link on scope prefix. 20 | " 6 : Warning : A hot link seems mis-typed. 21 | " obsol 7 : Error : The link of an option name is not jumpable. Need a space before the former quote. 22 | " 8 : Warning : An Vim's option should not be link notation. 23 | 24 | if &compatible || exists('b:loaded_ftplugin_help_lint') 25 | finish 26 | endif 27 | let b:loaded_ftplugin_help_lint = 1 28 | 29 | command! -bang -buffer -bar -nargs=0 VimhelpLint call s:vimhelp_lint('') 30 | command! -buffer -bar -nargs=0 VimhelpLintEcho call s:vimhelp_lint_echo() 31 | 32 | if exists("*\vimhelp_lint") 33 | finish 34 | endif 35 | 36 | " patches 37 | if v:version > 704 || (v:version == 704 && has('patch237')) 38 | let s:has_patch_7_3_465 = has('patch-7.3.465') 39 | let s:has_patch_7_4_218 = has('patch-7.4.218') 40 | let s:has_patch_7_4_358 = has('patch-7.4.358') 41 | else 42 | let s:has_patch_7_3_465 = v:version == 703 && has('patch465') 43 | let s:has_patch_7_4_218 = v:version == 704 && has('patch218') 44 | let s:has_patch_7_4_358 = v:version == 704 && has('patch358') 45 | endif 46 | 47 | function! VimhelpLintGetQflist() abort "{{{ 48 | let qflist = [] 49 | 50 | """ gather hyper texts 51 | let view = s:winsaveview() 52 | let separator = has('win32') && !&shellslash ? '\' : '/' 53 | let path_expr = printf('%s%s*.%s', expand('%:h'), separator, expand('%:e')) 54 | let hypertext_in_files = [] 55 | for doc in s:glob(path_expr, 1, 1) 56 | silent let bufnr = bufnr(doc, !bufexists(doc)) 57 | let hypertext_in_files += s:extract_hypertexts(bufnr) 58 | let qflist += map(range(1, line('$')), 's:checker_for_style(v:val, bufnr)') 59 | endfor 60 | call s:winrestview(view) 61 | 62 | """ clasify 63 | let tags_in_files = filter(copy(hypertext_in_files), 'v:val.kind ==# "tag"') 64 | let links_in_files = filter(copy(hypertext_in_files), 'v:val.kind ==# "link"') 65 | let options_in_files = filter(copy(hypertext_in_files), 'v:val.kind ==# "option"') 66 | 67 | """ check 68 | let taglist = map(copy(tags_in_files), 'v:val.name') 69 | let buftype = &l:buftype 70 | try 71 | let &l:buftype = 'help' 72 | 73 | " check tags : A tag should not be duplicate. 74 | let qflist += s:check(tags_in_files, function('s:checker_for_tags'), taglist) 75 | 76 | " check links : A link should have a corresponding tag. 77 | let qflist += s:check(links_in_files, function('s:checker_for_links'), taglist) 78 | 79 | " check options : A option should have a corresponding tag. 80 | let qflist += s:check(options_in_files, function('s:checker_for_links'), taglist) 81 | 82 | " check linked options (|'option'|) : An option should not be link notation. 83 | let qflist += s:check(links_in_files, function('s:checker_for_linked_options'), taglist) 84 | finally 85 | let &l:buftype = buftype 86 | endtry 87 | call s:sort(filter(qflist, 'v:val != {}'), 's:compare_qfitems') 88 | return qflist 89 | endfunction 90 | "}}} 91 | function! s:vimhelp_lint(bang) abort "{{{ 92 | if &filetype !=# 'help' 93 | call s:echo('This is not a help file!', 'ErrorMsg') 94 | return 95 | endif 96 | 97 | let qflist = VimhelpLintGetQflist() 98 | 99 | call setqflist(qflist, 'r') 100 | if qflist != [] 101 | call s:hier('on') 102 | call s:echo(printf('%d errors have been found!', len(qflist)), 'WarningMsg') 103 | else 104 | call s:hier('off') 105 | call s:echo('No errors.') 106 | endif 107 | 108 | if a:bang ==# '!' 109 | cwindow 110 | endif 111 | endfunction 112 | "}}} 113 | function! s:vimhelp_lint_echo() abort "{{{ 114 | if &filetype !=# 'help' 115 | call s:echo('This is not a help file!', 'ErrorMsg') 116 | return 117 | endif 118 | 119 | let qflist = VimhelpLintGetQflist() 120 | 121 | for qfitem in qflist 122 | let bufname = bufname(qfitem.bufnr) 123 | let lnum = qfitem.lnum 124 | let col = qfitem.col 125 | let e_or_w = qfitem.type ==# 'E' ? 'Error' : 'Warning' 126 | let nr = qfitem.nr 127 | let text = qfitem.text 128 | let msg = printf('%s:%d:%d:%s:%d:%s', bufname, lnum, col, e_or_w, nr, text) 129 | echo msg 130 | endfor 131 | endfunction 132 | "}}} 133 | function! s:extract_hypertexts(bufnr) abort "{{{ 134 | execute 'silent buffer ' . a:bufnr 135 | 136 | let hypertext_in_files = [] 137 | let lines = getline(1, line('$')) 138 | let skip = 0 139 | let scraped = [] 140 | for [lnum, line] in map(range(1, line('$')), '[v:val, lines[v:val-1]]') 141 | if skip 142 | if line =~# '^\%([^ [:tab:]]\|<\)' 143 | let skip = 0 144 | endif 145 | endif 146 | 147 | if !skip 148 | let scraped += s:extract_hypertexts_from_a_line(lnum, line) 149 | 150 | if line =~# '\%(^\| \)>[a-z0-9]*$' 151 | let skip = 1 152 | endif 153 | endif 154 | endfor 155 | 156 | let buf_info = {'bufnr': a:bufnr, 'bufname': fnamemodify(bufname('%'), ':t:r')} 157 | let hypertext_in_files += map(scraped, 'extend(v:val, buf_info)') 158 | 159 | return hypertext_in_files 160 | endfunction 161 | "}}} 162 | function! s:extract_hypertexts_from_a_line(lnum, line) abort "{{{ 163 | let list = [] 164 | 165 | " extract tags 166 | let list += s:extract_a_kind_of_hypertexts(a:lnum, a:line, 'tag', '\*\zs[#-)!+-{}~]\+\ze\*\%(\s\|$\)') 167 | 168 | " extract links 169 | let list += s:extract_a_kind_of_hypertexts(a:lnum, a:line, 'link', '\%(^\|[^\\]\)|\zs[#-)!+-{}~]\+\ze|', '\%(|||\|.*====*|\|:|vim:|\)') 170 | 171 | " extract options 172 | let list += s:extract_a_kind_of_hypertexts(a:lnum, a:line, 'option', '\C''\%([a-z]\{2,}\|t_..\)''', '\s*\zs.\{-}\ze\s\=\~$') 173 | 174 | return list 175 | endfunction 176 | "}}} 177 | function! s:extract_a_kind_of_hypertexts(lnum, line, kind, pattern, ...) abort "{{{ 178 | let exceptpat = get(a:000, 0, '') 179 | let list = [] 180 | let l:count = 1 181 | while 1 182 | " NOTE: Use same arguments for match() and matchstr() because its faster. 183 | " A kind of cache might take effect(?) 184 | let start = match(a:line, a:pattern, 0, l:count) 185 | if start > -1 186 | let str = matchstr(a:line, a:pattern, 0, l:count) 187 | if exceptpat ==# '' || !s:is_except_pattern(a:line, exceptpat, start) 188 | let list += [{'kind': a:kind, 'name': str, 'lnum': a:lnum, 'start': start, 'line': a:line}] 189 | endif 190 | else 191 | break 192 | endif 193 | let l:count += 1 194 | endwhile 195 | return list 196 | endfunction 197 | "}}} 198 | function! s:is_except_pattern(line, exceptpat, idx) abort "{{{ 199 | let result = 0 200 | let l:count = 1 201 | while 1 202 | let except_start = match(a:line, a:exceptpat, 0, l:count) 203 | let except_end = matchend(a:line, a:exceptpat, 0, l:count) 204 | if except_start < 0 || except_end < 0 205 | break 206 | elseif a:idx >= except_start && a:idx < except_end 207 | let result = 1 208 | break 209 | endif 210 | let l:count += 1 211 | endwhile 212 | return result 213 | endfunction 214 | "}}} 215 | function! s:escape(string) abort "{{{ 216 | return escape(a:string, '~"\.^$[]*') 217 | endfunction 218 | "}}} 219 | function! s:winsaveview() abort "{{{ 220 | return [bufnr('%'), winsaveview()] 221 | endfunction 222 | "}}} 223 | function! s:winrestview(view) abort "{{{ 224 | let [bufnr, view] = a:view 225 | execute 'silent buffer ' . bufnr 226 | call winrestview(view) 227 | endfunction 228 | "}}} 229 | function! s:hier(switch) abort "{{{ 230 | if a:switch ==# 'on' 231 | if exists(':HierUpdate') 232 | if !exists('g:hier_enabled') 233 | HierStart 234 | else 235 | HierUpdate 236 | endif 237 | endif 238 | elseif a:switch ==# 'off' 239 | if exists('g:hier_enabled') 240 | " FIXME: Which is better? HierStop or HierClear? 241 | HierStop 242 | endif 243 | endif 244 | endfunction 245 | "}}} 246 | function! s:echo(str, ...) abort "{{{ 247 | let hl = get(a:000, 0, 'NONE') 248 | execute 'echohl ' . hl 249 | echo 'vimhelplint: ' . a:str 250 | echohl NONE 251 | endfunction 252 | "}}} 253 | " function! s:glob(expr, ...) abort "{{{ 254 | if s:has_patch_7_3_465 255 | function! s:glob(expr, ...) abort 256 | let nosuf = get(a:000, 0, 0) 257 | let list = get(a:000, 1, 0) 258 | return glob(a:expr, nosuf, list) 259 | endfunction 260 | else 261 | function! s:glob(expr, ...) abort 262 | let nosuf = get(a:000, 0, 0) 263 | let list = get(a:000, 1, 0) 264 | let paths = glob(a:expr, nosuf) 265 | return list ? split(paths, "\") : paths 266 | endfunction 267 | endif 268 | "}}} 269 | 270 | " checkers 271 | function! s:check(targets, checker, taglist) abort "{{{ 272 | " NOTE: The returned should be same as 273 | " filter(map(copy(a:targets), 'a:checker(v:val, a:taglist)'), 'v:val != {}') 274 | " but faster because removing duplicates. 275 | let representatives = s:uniq(s:sort(deepcopy(a:targets), 's:compare_bufnr_and_name'), 's:compare_bufnr_and_name') 276 | let qflist = [] 277 | for rep in representatives 278 | let result = a:checker(rep, a:taglist) 279 | if result != {} 280 | for target in a:targets 281 | if rep.name ==# target.name && rep.bufnr == target.bufnr 282 | let nr = result.nr 283 | let type = result.type 284 | let bufnr = target.bufnr 285 | let lnum = target.lnum 286 | let idx = target.start 287 | let text = result.text 288 | let qflist += [s:qfitem(nr, type, bufnr, lnum, idx, text)] 289 | endif 290 | endfor 291 | endif 292 | endfor 293 | return qflist 294 | endfunction 295 | "}}} 296 | function! s:checker_for_style(lnum, bufnr) abort "{{{ 297 | let qfitem = {} 298 | if strdisplaywidth(getline(a:lnum)) > 78 299 | " [Error 1] 300 | let text = 'The width of a line should be no longer than 78.' 301 | let qfitem = s:qfitem(1, 'W', a:bufnr, a:lnum, 0, text) 302 | endif 303 | return qfitem 304 | endfunction 305 | "}}} 306 | function! s:checker_for_tags(tag, taglist) abort "{{{ 307 | let name = a:tag.name 308 | let bufnr = a:tag.bufnr 309 | let bufname = a:tag.bufname 310 | let lnum = a:tag.lnum 311 | let idx = a:tag.start 312 | 313 | let qfitem = {} 314 | if count(a:taglist, name, 0) > 1 315 | " [Error 2] 316 | let text = printf('A tag "%s" is duplicate with another in this file.', name) 317 | let qfitem = s:qfitem(2, 'E', bufnr, lnum, idx, text) 318 | else 319 | let pattern = printf('\C^%s$', s:escape(name)) 320 | let tags_in_external_file = taglist(pattern) 321 | for ext_tag in tags_in_external_file 322 | if fnamemodify(ext_tag.filename, ':t:r') !=? bufname 323 | " [Error 3] 324 | let text = printf('A tag "%s" is duplicate with another in the file %s.', name, ext_tag.filename) 325 | let qfitem = s:qfitem(3, 'E', bufnr, lnum, idx, text) 326 | break 327 | endif 328 | endfor 329 | endif 330 | return qfitem 331 | endfunction 332 | "}}} 333 | function! s:checker_for_links(link, taglist) abort "{{{ 334 | let bufnr = a:link.bufnr 335 | let bufname = a:link.bufname 336 | let lnum = a:link.lnum 337 | let idx = a:link.start 338 | let line = a:link.line 339 | 340 | let qfitem = {} 341 | if count(a:taglist, a:link.name, 0) < 1 342 | let is_linked = 0 343 | 344 | let pattern = printf('\C^%s$', s:escape(a:link.name)) 345 | let tags_in_external_file = taglist(pattern) 346 | if tags_in_external_file != [] 347 | for ext_tag in tags_in_external_file 348 | if fnamemodify(ext_tag.filename, ':t:r') !=? bufname 349 | let is_linked = 1 350 | break 351 | endif 352 | endfor 353 | else 354 | let is_linked = 0 355 | endif 356 | 357 | if !is_linked 358 | " analogize 359 | " FIXME: It might be unnecessary. 360 | let likely = s:analogize(a:link, a:taglist) 361 | 362 | let err_nr = get(likely, 'error', 0) 363 | if err_nr == 4 364 | " [Error 4] 365 | if has_key(likely, 'name') 366 | let text = printf('A link "%s" does not have any corresponding tag. Isn''t it "%s"?', a:link.name, likely.name) 367 | else 368 | let text = printf('A link "%s" does not have any corresponding tag.', a:link.name) 369 | endif 370 | let qfitem = s:qfitem(4, 'E', bufnr, lnum, idx, text) 371 | elseif err_nr == 5 372 | " [Error 5] 373 | " FIXME: This is probably wrong with the tag although here is in the 374 | " link checker. Should cursor move to tag? 375 | let text = printf('A link "%s" does not have any corresponding tag. Isn''t it "%s"? Or a scope prefix missing at the tag?', likely.name, a:link.name) 376 | let qfitem = s:qfitem(5, 'W', bufnr, lnum, idx, text) 377 | elseif err_nr == 6 378 | " [Error 6] 379 | let text = printf('A link "%s" does not have any corresponding tag. Isn''t it "%s"?', a:link.name, likely.name) 380 | let qfitem = s:qfitem(6, 'W', bufnr, lnum, idx, text) 381 | else 382 | " should not reach here 383 | let qfitem = {} 384 | endif 385 | endif 386 | endif 387 | return qfitem 388 | endfunction 389 | "}}} 390 | function! s:checker_for_linked_options(link, taglist) abort "{{{ 391 | if a:link.name =~# '^''\w\+''$' 392 | " [Error 8] 393 | let text = printf('An option |%s| should not be link notation.', a:link.name) 394 | return s:qfitem(8, 'W', a:link.bufnr, a:link.lnum, a:link.start, text) 395 | endif 396 | return {} 397 | endfunction 398 | "}}} 399 | function! s:qfitem(nr, type, bufnr, lnum, col, text) abort "{{{ 400 | return { 401 | \ 'nr' : a:nr, 402 | \ 'type' : a:type, 403 | \ 'bufnr' : a:bufnr, 404 | \ 'lnum' : a:lnum, 405 | \ 'col' : a:col+1, 406 | \ 'vcol' : 0, 407 | \ 'text' : a:text, 408 | \ 'valid' : 1, 409 | \ } 410 | endfunction 411 | "}}} 412 | function! s:analogize(link, taglist, ...) abort "{{{ 413 | let is_deep = get(a:000, 0, 0) 414 | let escaped = s:escape(a:link.name) 415 | if !is_deep 416 | " assume the link as a name of a valiable without a scope prefix 417 | if a:link.name =~# '^[bgtw]:\h[[:alnum:]_#]\{2,}$' 418 | let pattern = printf('^%s$', escaped[2:]) 419 | let idx = match(a:taglist, pattern) 420 | if idx > -1 421 | return {'name': a:taglist[idx], 'error': 5} 422 | endif 423 | endif 424 | 425 | " simplely analogize 426 | let err_nr = {'error': 6} 427 | if a:link.name =~# '^\h\%(\w\+[_#]\)\+\w\+\%(()\)\?$' 428 | let pattern = printf('^[bgtw]:%s\%(()\)\?$', escaped) 429 | elseif a:link.name =~# '^\h\w*$' 430 | let pattern = printf('^\%(:\?%s\|%s()\|''%s''\|%s.*\|.*%s\)$', escaped, escaped, escaped, escaped, escaped) 431 | else 432 | let pattern = printf('%s', escaped) 433 | endif 434 | else 435 | " deeply analogize 436 | let err_nr = {'error': 4} 437 | if a:link.name =~# '''^[:alpha:]\{2,}$''' 438 | let word = matchstr(a:link.name, '''\zs[:alpha:]\{2,}\ze''') 439 | let pattern = printf('''%s''', s:fuzzy_pattern(word)) 440 | elseif a:link.name =~# '^\h\w*()$' 441 | let word = matchstr(a:link.name, '\h\w*\ze()') 442 | let pattern = printf('%s()', s:fuzzy_pattern(word)) 443 | elseif a:link.name =~# '^:\h\w*$' 444 | let word = matchstr(a:link.name, ':\h\w*\ze') 445 | let pattern = printf(':%s', s:fuzzy_pattern(word)) 446 | elseif a:link.name =~# '^\h\w*$' 447 | let fuzzy_pattern = s:fuzzy_pattern(a:link.name) 448 | let pattern = printf('^\%(:\?%s\|%s()\|''%s''\|%s.*\)$', fuzzy_pattern, fuzzy_pattern, fuzzy_pattern, escaped) 449 | else 450 | let pattern = s:fuzzy_pattern(a:link.name) 451 | endif 452 | endif 453 | 454 | let errormsg = '' 455 | try 456 | let idx = match(a:taglist, pattern) 457 | catch /^Vim\%((\a\+)\)\=:E16/ 458 | " backtrack too complicated pattern 459 | let pattern = printf('\%%(%s.\|%s\)', escaped, escaped[0:-2]) 460 | let idx = match(a:taglist, pattern) 461 | catch 462 | let errormsg = printf('vimhelplint: Unanticipated error. [%s] %s', v:throwpoint, v:exception) 463 | finally 464 | if errormsg ==# '' 465 | if idx > -1 466 | let likely = {'name': a:taglist[idx]} 467 | else 468 | let likely = get(taglist(pattern), 0, {}) 469 | if get(likely, 'name', '') ==# a:link.name 470 | " NOTE: I don't know why but taglist() returns a non-existent tag 471 | " (probably when a same named link exists). 472 | let likely = {} 473 | endif 474 | endif 475 | else 476 | echoerr errormsg 477 | endif 478 | 479 | if !is_deep 480 | return likely == {} ? s:analogize(a:link, a:taglist, 1) : extend(likely, err_nr) 481 | else 482 | return extend(likely, err_nr) 483 | endif 484 | endtry 485 | endfunction 486 | "}}} 487 | function! s:fuzzy_pattern(word) abort "{{{ 488 | " FIXME: Ah... Does anyone have good idea? 489 | 490 | let charlist = map(split(a:word, '\zs'), 's:escape(v:val)') 491 | 492 | " swapped 493 | let items = [] 494 | if len(charlist) > 1 495 | for n in range(len(charlist)-1) 496 | let copied = copy(charlist) 497 | let c = remove(copied, n) 498 | call insert(copied, c, n+1) 499 | let items += [join(copied, '')] 500 | endfor 501 | endif 502 | let swapped = join(items, '\|') 503 | 504 | " misspelled 505 | let items = [] 506 | if len(charlist) > 1 507 | for n in range(len(charlist)) 508 | let copied = copy(charlist) 509 | let copied[n] = '.' 510 | let items += [join(copied, '')] 511 | endfor 512 | endif 513 | let misspelled = join(items, '\|') 514 | 515 | " dropped 516 | let items = [] 517 | for n in range(len(charlist)) 518 | let copied = copy(charlist) 519 | call remove(copied, n) 520 | let items += [join(copied, '')] 521 | endfor 522 | let dropped = join(items, '\|') 523 | 524 | " added 525 | let items = [] 526 | for n in range(len(charlist)) 527 | let copied = copy(charlist) 528 | call insert(copied, '.', n) 529 | let items += [join(copied, '')] 530 | endfor 531 | let copied = copy(charlist) 532 | let items += [join(add(copied, '.'), '')] 533 | let added = join(items, '\|') 534 | 535 | return printf('\%%(%s\)', join([swapped, misspelled, dropped, added], '\|')) 536 | endfunction 537 | "}}} 538 | " function! s:sort(list, func, ...) abort "{{{ 539 | if s:has_patch_7_4_358 540 | function! s:sort(list, func, ...) abort 541 | return sort(a:list, a:func) 542 | endfunction 543 | else 544 | function! s:sort(list, func, ...) abort 545 | " NOTE: len(a:list) is always larger than n or same. 546 | " FIXME: The number of item in a:list would not be large, but if there was 547 | " any efficient argorithm, I would rewrite here. 548 | let len = len(a:list) 549 | let n = min([get(a:000, 0, len), len]) 550 | for i in range(n) 551 | if len - 2 >= i 552 | let min = len - 1 553 | for j in range(len - 2, i, -1) 554 | if call(a:func, [a:list[min], a:list[j]]) >= 1 555 | let min = j 556 | endif 557 | endfor 558 | 559 | if min > i 560 | call insert(a:list, remove(a:list, min), i) 561 | endif 562 | endif 563 | endfor 564 | return a:list 565 | endfunction 566 | endif 567 | "}}} 568 | " function! s:uniq(list, func) abort "{{{ 569 | if s:has_patch_7_4_218 570 | function! s:uniq(list, func) abort 571 | return uniq(a:list, a:func) 572 | endfunction 573 | else 574 | function! s:uniq(list, func) abort 575 | let i = 0 576 | let len = len(a:list) 577 | while i < len-1 578 | if call(a:func, [a:list[i], a:list[i+1]]) == 0 579 | call remove(a:list, i+1) 580 | let len -= 1 581 | else 582 | let i += 1 583 | endif 584 | endwhile 585 | return a:list 586 | endfunction 587 | endif 588 | "}}} 589 | function! s:compare_bufnr_and_name(i1, i2) abort "{{{ 590 | if a:i1.bufnr != a:i2.bufnr 591 | let result = a:i1.bufnr - a:i2.bufnr 592 | else 593 | let result = s:compare_string(a:i1.name, a:i2.name) 594 | endif 595 | return result 596 | endfunction 597 | "}}} 598 | function! s:compare_qfitems(i1, i2) abort "{{{ 599 | if a:i1.bufnr != a:i2.bufnr 600 | let result = s:compare_string(bufname(a:i1.bufnr), bufname(a:i2.bufnr)) 601 | else 602 | let pos1 = [a:i1.lnum, a:i1.col] 603 | let pos2 = [a:i2.lnum, a:i2.col] 604 | if s:is_ahead(pos1, pos2) 605 | let result = 1 606 | elseif s:is_ahead(pos2, pos1) 607 | let result = -1 608 | else 609 | let result = 0 610 | endif 611 | endif 612 | return result 613 | endfunction 614 | "}}} 615 | function! s:compare_string(s1, s2) abort "{{{ 616 | let chars1 = map(split(a:s1, '\zs'), 'char2nr(v:val)') 617 | let chars2 = map(split(a:s2, '\zs'), 'char2nr(v:val)') 618 | let len1 = len(chars1) 619 | let len2 = len(chars2) 620 | let result = len1 - len2 621 | for i in range(min([len1, len2])) 622 | if chars1[i] != chars2[i] 623 | let result = chars1[i] - chars2[i] 624 | break 625 | endif 626 | endfor 627 | return result 628 | endfunction 629 | "}}} 630 | function! s:is_ahead(pos1, pos2) abort "{{{ 631 | return a:pos1[0] > a:pos2[0] || (a:pos1[0] == a:pos2[0] && a:pos1[1] > a:pos2[1]) 632 | endfunction 633 | "}}} 634 | 635 | " for vim-watchdogs "{{{ 636 | if exists(':WatchdogsRun') == 2 637 | function! s:get_plugin_dir() abort "{{{ 638 | return s:plugin_path 639 | endfunction 640 | "}}} 641 | function! s:integrate_watchdog_config() abort "{{{ 642 | if !exists('g:quickrun_config') 643 | let g:quickrun_config = {} 644 | endif 645 | call extend(g:quickrun_config, s:vimhelplint_watchdogs_checker) 646 | endfunction 647 | "}}} 648 | let s:plugin_path = expand(':h:h') 649 | let s:vimhelplint_watchdogs_checker = { 650 | \ 'watchdogs_checker/vimhelplint' : { 651 | \ 'command': 'vim', 652 | \ 'exec' : '%C -X -N -u NONE -i NONE -V1 -e -s -c "set rtp+=' . s:get_plugin_dir() . '" -c "silent filetype plugin on" -c "silent edit %s" -c "VimhelpLintEcho" -c "qall!"', 653 | \ 'errorformat': '%f:%l:%c:%trror:%n:%m,%f:%l:%c:%tarning:%n:%m', 654 | \ }, 655 | \ } 656 | call s:integrate_watchdog_config() 657 | endif 658 | "}}} 659 | 660 | " vim:set foldmethod=marker: 661 | " vim:set commentstring="%s: 662 | " vim:set ts=2 sts=2 sw=2 et: 663 | --------------------------------------------------------------------------------