From adf899f1ba863b1ea2c3327e7cde79ba21354435 Mon Sep 17 00:00:00 2001 From: LCD 47 Date: Sun, 9 Oct 2016 11:08:06 +0300 Subject: [PATCH] Refcator registry code. It is now possible to run checkers for a filetype different than the filetype of the current file. --- README.markdown | 86 ++++++++++++++++++++++------------- doc/syntastic.txt | 19 ++++++-- plugin/syntastic.vim | 49 ++++++++++---------- plugin/syntastic/checker.vim | 10 ++-- plugin/syntastic/loclist.vim | 2 +- plugin/syntastic/modemap.vim | 11 +++-- plugin/syntastic/registry.vim | 76 +++++++++++++++++++++++-------- 7 files changed, 166 insertions(+), 87 deletions(-) diff --git a/README.markdown b/README.markdown index 1cf31e6f..e3e07687 100644 --- a/README.markdown +++ b/README.markdown @@ -26,20 +26,21 @@ 4. [FAQ](#faq) 4.1. [I installed syntastic but it isn't reporting any errors...](#faqinfo) 4.2. [Syntastic supports several checkers for my filetype, how do I tell it which one(s) to use?](#faqcheckers) -4.3. [I have enabled multiple checkers for the current filetype. How can I display all errors from all checkers together?](#faqaggregate) -4.4. [How can I pass additional arguments to a checker?](#faqargs) -4.5. [I run a checker and the location list is not updated...](#faqloclist) -4.5. [I run`:lopen` or `:lwindow` and the error window is empty...](#faqloclist) -4.6. [How can I jump between the different errors without using the location list at the bottom of the window?](#faqlnext) -4.7. [The error window is closed automatically when I `:quit` the current buffer but not when I `:bdelete` it?](#faqbdelete) -4.8. [My favourite checker needs to load a configuration file from the project's root rather than the current directory...](#faqconfig) -4.9. [What is the difference between syntax checkers and style checkers?](#faqstyle) -4.10. [How can I check scripts written for different versions of Python?](#faqpython) -4.11. [How can I check scripts written for different versions of Ruby?](#faqruby) -4.12. [The `perl` checker has stopped working...](#faqperl) -4.13. [What happened to the `rustc` checker?](#faqrust) -4.14. [What happened to the `tsc` checker?](#faqtsc) -4.15. [What happened to the `xcrun` checker?](#faqxcrun) +4.3. [How can run checkers for "foreign" filetypes against the current file?](#faqforeign) +4.4. [I have enabled multiple checkers for the current filetype. How can I display all errors from all checkers together?](#faqaggregate) +4.5. [How can I pass additional arguments to a checker?](#faqargs) +4.6. [I run a checker and the location list is not updated...](#faqloclist) +4.6. [I run`:lopen` or `:lwindow` and the error window is empty...](#faqloclist) +4.7. [How can I jump between the different errors without using the location list at the bottom of the window?](#faqlnext) +4.8. [The error window is closed automatically when I `:quit` the current buffer but not when I `:bdelete` it?](#faqbdelete) +4.9. [My favourite checker needs to load a configuration file from the project's root rather than the current directory...](#faqconfig) +4.10. [What is the difference between syntax checkers and style checkers?](#faqstyle) +4.11. [How can I check scripts written for different versions of Python?](#faqpython) +4.12. [How can I check scripts written for different versions of Ruby?](#faqruby) +4.13. [The `perl` checker has stopped working...](#faqperl) +4.14. [What happened to the `rustc` checker?](#faqrust) +4.15. [What happened to the `tsc` checker?](#faqtsc) +4.16. [What happened to the `xcrun` checker?](#faqxcrun) 5. [Resources](#otherresources) - - - @@ -264,13 +265,36 @@ For example to run `phpcs` and `phpmd`: ``` This works for any checkers available for the current filetype, even if they -aren't listed in `g:syntastic__checkers`. You can't run checkers for -"foreign" filetypes though (e.g. you can't run, say, a Python checker if the -filetype of the current file is `php`). +aren't listed in `g:syntastic__checkers`. + + + +__4.3. Q. How can run checkers for "foreign" filetypes against the current +file?__ + +A. You need to qualify the name of the "foreign" checker with the name +of its filetype. For example to check `tex` files with the checker +`language_check` (which normally acts only on files of type `text`), you can +add `text/language_check` to the list fo checkers for `tex`: +```vim +let g:syntastic_tex_checkers = ['lacheck', 'text/language_check'] +``` + +This also works with `:SyntasticCheck`, e.g. the following command runs +`text/language_check` against the current file regardless of the current +filetype: +```vim +:SyntasticCheck text/language_check +``` + +Of course, the checkers specified this way need to be known to syntastic, and +they need to be shown as available when you run `:SyntasticInfo`. You can't +just make up a combination of a filetype and a program name and expect it to +work as a checker. -__4.3. Q. I have enabled multiple checkers for the current filetype. How can I +__4.4. Q. I have enabled multiple checkers for the current filetype. How can I display all errors from all checkers together?__ A. Set `g:syntastic_aggregate_errors` to 1 in your `vimrc`: @@ -282,7 +306,7 @@ See `:help syntastic-aggregating-errors` for more details. -__4.4. Q. How can I pass additional arguments to a checker?__ +__4.5. Q. How can I pass additional arguments to a checker?__ A. In most cases a command line is constructed using an internal function named `makeprgBuild()`, which provides a number of options that allow you to @@ -306,8 +330,8 @@ list of options should be included in the [manual][checkers] -__4.5. Q. I run a checker and the location list is not updated...__ -__4.5. Q. I run`:lopen` or `:lwindow` and the error window is empty...__ +__4.6. Q. I run a checker and the location list is not updated...__ +__4.6. Q. I run`:lopen` or `:lwindow` and the error window is empty...__ A. By default the location list is changed only when you run the `:Errors` command, in order to minimise conflicts with other plugins. If you want the @@ -319,7 +343,7 @@ let g:syntastic_always_populate_loc_list = 1 -__4.6. Q. How can I jump between the different errors without using the location +__4.7. Q. How can I jump between the different errors without using the location list at the bottom of the window?__ A. Vim provides several built-in commands for this. See `:help :lnext` and @@ -331,7 +355,7 @@ mappings (among other things). -__4.7. Q. The error window is closed automatically when I `:quit` the current buffer +__4.8. Q. The error window is closed automatically when I `:quit` the current buffer but not when I `:bdelete` it?__ A. There is no safe way to handle that situation automatically, but you can @@ -343,7 +367,7 @@ cabbrev bd =(getcmdtype()==#':' && getcmdpos()==1 ? 'lclose\|bdele -__4.8. My favourite checker needs to load a configuration file from the +__4.9. My favourite checker needs to load a configuration file from the project's root rather than the current directory...__ A. You can set up an `autocmd` to search for the configuration file in the @@ -363,7 +387,7 @@ autocmd FileType javascript let b:syntastic_javascript_jscs_args = -__4.9. Q. What is the difference between syntax checkers and style checkers?__ +__4.10. Q. What is the difference between syntax checkers and style checkers?__ A. The errors and warnings they produce are highlighted differently and can be filtered by different rules, but otherwise the distinction is pretty much @@ -393,7 +417,7 @@ See `:help syntastic_quiet_messages` for more information. -__4.10. Q. How can I check scripts written for different versions of Python?__ +__4.11. Q. How can I check scripts written for different versions of Python?__ A. Install a Python version manager such as [virtualenv][virtualenv] or [pyenv][pyenv], activate the environment for the relevant version @@ -409,7 +433,7 @@ scripts. -__4.11. Q. How can I check scripts written for different versions of Ruby?__ +__4.12. Q. How can I check scripts written for different versions of Ruby?__ A. Install a Ruby version manager such as [rvm][rvm] or [rbenv][rbenv], activate the environment for the relevant version of Ruby, and install in it @@ -424,7 +448,7 @@ scripts. -__4.12. Q. The `perl` checker has stopped working...__ +__4.13. Q. The `perl` checker has stopped working...__ A. The `perl` checker runs `perl -c` against your file, which in turn __executes__ any `BEGIN`, `UNITCHECK`, and `CHECK` blocks, and any `use` @@ -440,14 +464,14 @@ let g:syntastic_enable_perl_checker = 1 -__4.13. Q. What happened to the `rustc` checker?__ +__4.14. Q. What happened to the `rustc` checker?__ A. It is now part of the [rust.vim][rust] plugin. If you install this plugin the checker should be picked up automatically by syntastic. -__4.14. Q. What happened to the `tsc` checker?__ +__4.15. Q. What happened to the `tsc` checker?__ A. It didn't meet people's expectations and it has been removed. The plugin [tsuquyomi][tsuquyomi] comes packaged with a checker for TypeScript. If you @@ -455,7 +479,7 @@ install this plugin the checker should be picked up automatically by syntastic. -__4.15. Q. What happened to the `xcrun` checker?__ +__4.16. Q. What happened to the `xcrun` checker?__ A. The `xcrun` checker used to have a security problem and it has been removed. A better checker for __Swift__ is part of the [vim-swift][swift] plugin. If you diff --git a/doc/syntastic.txt b/doc/syntastic.txt index 8f3473bc..0e6bc35e 100644 --- a/doc/syntastic.txt +++ b/doc/syntastic.txt @@ -301,12 +301,22 @@ the order specified. The set by |'syntastic_aggregate_errors'| still apply. Example: > :SyntasticCheck flake8 pylint < +You can also run checkers for filetypes different from the current filetype +by qualifying their names with their respective filetypes, like this: +"/". + +Example: > + :SyntasticCheck lacheck text/language_check +< :SyntasticInfo *:SyntasticInfo* The command takes an optional argument, and outputs information about the checkers available for the filetype named by said argument, or for the current filetype if no argument was provided. +Example: > + :SyntasticInfo python +< :SyntasticReset *:SyntasticReset* Resets the list of errors and turns off all error notifiers. @@ -761,6 +771,10 @@ If neither |'g:syntastic__checkers'| nor |'b:syntastic_checkers'| is set, a default list of checker is used. Beware however that this list deliberately kept minimal, for performance reasons. +You can specify checkers for other filetypes anywhere in these lists, by +qualifying their names with their respective filetypes: > + let g:syntastic_tex_checkers = ["lacheck", "text/language_check"] +< Take a look elsewhere in this manual to find out what checkers and filetypes are supported by syntastic: |syntastic-checkers|. @@ -854,9 +868,8 @@ omitting the filename from the command line: > let g:syntastic_sml_smlnj_fname = "" < *syntastic-config-no-makeprgbuild* -For checkers that do not use the "makeprgBuild()" function you will have to -look at the source code of the checker in question. If there are specific -options that can be set they are normally documented in this manual (see +For checkers that do not use the "makeprgBuild()" function any specific +options that can be set should be documented in this manual (see |syntastic-checkers|). ------------------------------------------------------------------------------ diff --git a/plugin/syntastic.vim b/plugin/syntastic.vim index f88c5ab2..1e5c36d7 100644 --- a/plugin/syntastic.vim +++ b/plugin/syntastic.vim @@ -19,7 +19,7 @@ if has('reltime') lockvar! g:_SYNTASTIC_START endif -let g:_SYNTASTIC_VERSION = '3.7.0-233' +let g:_SYNTASTIC_VERSION = '3.7.0-234' lockvar g:_SYNTASTIC_VERSION " Sanity checks {{{1 @@ -178,11 +178,20 @@ let s:_quit_pre = [] " @vimlint(EVL103, 1, a:cmdLine) " @vimlint(EVL103, 1, a:argLead) function! s:CompleteCheckerName(argLead, cmdLine, cursorPos) abort " {{{2 - let checker_names = [] - for ft in s:_resolve_filetypes([]) - call extend(checker_names, s:registry.getNamesOfAvailableCheckers(ft)) - endfor - return join(checker_names, "\n") + let names = [] + + let sep_idx = stridx(a:argLead, '/') + if sep_idx >= 1 + let ft = a:argLead[: sep_idx-1] + call extend(names, map( s:registry.getNamesOfAvailableCheckers(ft), 'ft . "/" . v:val' )) + else + for ft in s:registry.resolveFiletypes(&filetype) + call extend(names, s:registry.getNamesOfAvailableCheckers(ft)) + endfor + call extend(names, map( copy(s:registry.getKnownFiletypes()), 'v:val . "/"' )) + endif + + return join(names, "\n") endfunction " }}}2 " @vimlint(EVL103, 0, a:cursorPos) " @vimlint(EVL103, 0, a:cmdLine) @@ -220,7 +229,7 @@ endfunction " }}}2 function! SyntasticInfo(...) abort " {{{2 call s:modemap.modeInfo(a:000) - call s:registry.echoInfoFor(s:_resolve_filetypes(a:000)) + call s:registry.echoInfoFor(a:000) call s:_explain_skip(a:000) call syntastic#log#debugShowOptions(g:_SYNTASTIC_DEBUG_TRACE, s:_DEBUG_DUMP_OPTIONS) call syntastic#log#debugDump(g:_SYNTASTIC_DEBUG_VARIABLES) @@ -384,14 +393,12 @@ function! s:UpdateErrors(buf, auto_invoked, checker_names) abort " {{{2 return endif - let run_checks = !a:auto_invoked || s:modemap.doAutoChecking() + let run_checks = !a:auto_invoked || s:modemap.doAutoChecking(a:buf) if run_checks call s:CacheErrors(a:buf, a:checker_names) call syntastic#util#setLastTick(a:buf) - else - if a:auto_invoked - return - endif + elseif a:auto_invoked + return endif let loclist = g:SyntasticLoclist.current(a:buf) @@ -453,20 +460,17 @@ function! s:CacheErrors(buf, checker_names) abort " {{{2 call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE, 'getcwd() = ' . string(getcwd())) " }}}3 - let filetypes = s:_resolve_filetypes([]) - let aggregate_errors = syntastic#util#var('aggregate_errors') || len(filetypes) > 1 + let clist = s:registry.getCheckers(getbufvar(a:buf, '&filetype'), a:checker_names) + + let aggregate_errors = + \ syntastic#util#var('aggregate_errors') || len(syntastic#util#unique(map(copy(clist), 'v:val.getFiletype()'))) > 1 let decorate_errors = aggregate_errors && syntastic#util#var('id_checkers') let sort_aggregated_errors = aggregate_errors && syntastic#util#var('sort_aggregated_errors') - let clist = [] - for type in filetypes - call extend(clist, s:registry.getCheckers(type, a:checker_names)) - endfor - let names = [] let unavailable_checkers = 0 for checker in clist - let cname = checker.getFiletype() . '/' . checker.getName() + let cname = checker.getCName() if !checker.isAvailable() call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE, 'CacheErrors: Checker ' . cname . ' is not available') let unavailable_checkers += 1 @@ -687,11 +691,6 @@ endfunction " }}}2 " Utilities {{{1 -function! s:_resolve_filetypes(filetypes) abort " {{{2 - let type = len(a:filetypes) ? a:filetypes[0] : &filetype - return split( get(g:syntastic_filetype_map, type, type), '\m\.' ) -endfunction " }}}2 - function! s:_ignore_file(filename) abort " {{{2 let fname = fnamemodify(a:filename, ':p') for pattern in g:syntastic_ignore_files diff --git a/plugin/syntastic/checker.vim b/plugin/syntastic/checker.vim index c3a12a9d..345d2f03 100644 --- a/plugin/syntastic/checker.vim +++ b/plugin/syntastic/checker.vim @@ -65,6 +65,10 @@ function! g:SyntasticChecker.getName() abort " {{{2 return self._name endfunction " }}}2 +function! g:SyntasticChecker.getCName() abort " {{{2 + return self._filetype . '/' . self._name +endfunction " }}}2 + " Synchronise _exec with user's setting. Force re-validation if needed. " " XXX: This function must be called at least once before calling either @@ -92,7 +96,7 @@ endfunction " }}}2 function! g:SyntasticChecker.getLocListRaw() abort " {{{2 let checker_start = reltime() - let name = self._filetype . '/' . self._name + let name = self.getCName() if has_key(self, '_enable') let status = syntastic#util#var(self._enable, -1) @@ -150,7 +154,7 @@ function! g:SyntasticChecker.getVersion(...) abort " {{{2 call self.setVersion(parsed_ver) else call syntastic#log#ndebug(g:_SYNTASTIC_DEBUG_LOCLIST, 'checker output:', split(version_output, "\n", 1)) - call syntastic#log#error("checker " . self._filetype . "/" . self._name . ": can't parse version string (abnormal termination?)") + call syntastic#log#error("checker " . self.getCName() . ": can't parse version string (abnormal termination?)") endif endif return get(self, '_version', []) @@ -164,7 +168,7 @@ function! g:SyntasticChecker.setVersion(version) abort " {{{2 endfunction " }}}2 function! g:SyntasticChecker.log(msg, ...) abort " {{{2 - let leader = self._filetype . '/' . self._name . ': ' + let leader = self.getCName() . ': ' if a:0 call syntastic#log#debug(g:_SYNTASTIC_DEBUG_CHECKERS, leader . a:msg, a:1) else diff --git a/plugin/syntastic/loclist.vim b/plugin/syntastic/loclist.vim index ad984c95..9f8d06fc 100644 --- a/plugin/syntastic/loclist.vim +++ b/plugin/syntastic/loclist.vim @@ -281,7 +281,7 @@ endfunction " }}}2 " "would return all errors for buffer 10. " -"Note that all comparisons are done with ==? +"Note that all string comparisons are done with ==? function! g:SyntasticLoclist.filter(filters) abort " {{{2 let conditions = values(map(copy(a:filters), 's:_translate(v:key, v:val)')) let filter = len(conditions) == 1 ? diff --git a/plugin/syntastic/modemap.vim b/plugin/syntastic/modemap.vim index 40445b6e..74b09a71 100644 --- a/plugin/syntastic/modemap.vim +++ b/plugin/syntastic/modemap.vim @@ -29,7 +29,8 @@ function! g:SyntasticModeMap.synch() abort " {{{2 endfunction " }}}2 function! g:SyntasticModeMap.allowsAutoChecking(filetype) abort " {{{2 - let fts = split(a:filetype, '\m\.') + let registry = g:SyntasticRegistry.Instance() + let fts = registry.resolveFiletypes(a:filetype) if self.isPassive() return self._isOneFiletypeActive(fts) @@ -38,13 +39,13 @@ function! g:SyntasticModeMap.allowsAutoChecking(filetype) abort " {{{2 endif endfunction " }}}2 -function! g:SyntasticModeMap.doAutoChecking() abort " {{{2 - let local_mode = get(b:, 'syntastic_mode', '') +function! g:SyntasticModeMap.doAutoChecking(buf) abort " {{{2 + let local_mode = getbufvar(a:buf, 'syntastic_mode') if local_mode ==# 'active' || local_mode ==# 'passive' return local_mode ==# 'active' endif - return self.allowsAutoChecking(&filetype) + return self.allowsAutoChecking(getbufvar(a:buf, '&filetype')) endfunction " }}}2 function! g:SyntasticModeMap.isPassive() abort " {{{2 @@ -96,7 +97,7 @@ function! g:SyntasticModeMap.modeInfo(filetypes) abort " {{{2 echomsg 'Local mode: ' . b:syntastic_mode endif - echomsg 'The current file will ' . (self.doAutoChecking() ? '' : 'not ') . 'be checked automatically' + echomsg 'The current file will ' . (self.doAutoChecking(bufnr('')) ? '' : 'not ') . 'be checked automatically' endif endfunction " }}}2 diff --git a/plugin/syntastic/registry.vim b/plugin/syntastic/registry.vim index a24e283a..4d8f74e8 100644 --- a/plugin/syntastic/registry.vim +++ b/plugin/syntastic/registry.vim @@ -188,24 +188,39 @@ endfunction " }}}2 " not checked for availability (that is, the corresponding IsAvailable() are " not run). function! g:SyntasticRegistry.getCheckers(ftalias, hints_list) abort " {{{2 - let ft = s:_normalise_filetype(a:ftalias) - call self._loadCheckersFor(ft, 0) - - let checkers_map = self._checkerMap[ft] - if empty(checkers_map) - return [] - endif - - call self._checkDeprecation(ft) + let ftlist = self.resolveFiletypes(a:ftalias) let names = - \ !empty(a:hints_list) ? syntastic#util#unique(a:hints_list) : - \ exists('b:syntastic_checkers') ? b:syntastic_checkers : - \ exists('g:syntastic_' . ft . '_checkers') ? g:syntastic_{ft}_checkers : - \ get(s:_DEFAULT_CHECKERS, ft, 0) + \ !empty(a:hints_list) ? a:hints_list : + \ exists('b:syntastic_checkers') ? b:syntastic_checkers : [] - return type(names) == type([]) ? - \ self._filterCheckersByName(checkers_map, names) : [checkers_map[keys(checkers_map)[0]]] + let cnames = [] + if !empty(names) + for name in names + if name !~# '/' + for ft in ftlist + call add(cnames, ft . '/' . name) + endfor + else + call add(cnames, name) + endif + endfor + else + for ft in ftlist + call self._sanityCheck(ft) + let defs = + \ exists('g:syntastic_' . ft . '_checkers') ? g:syntastic_{ft}_checkers : + \ get(s:_DEFAULT_CHECKERS, ft, []) + call extend(cnames, map(copy(defs), 'ft . "/" . v:val' )) + endfor + endif + let cnames = syntastic#util#unique(cnames) + + for ft in syntastic#util#unique(map( copy(cnames), 'v:val[: stridx(v:val, "/")-1]' )) + call self._loadCheckersFor(ft, 0) + endfor + + return self._filterCheckersByName(cnames) endfunction " }}}2 " Same as getCheckers(), but keep only the available checkers. This runs the @@ -242,8 +257,12 @@ function! g:SyntasticRegistry.getNamesOfAvailableCheckers(ftalias) abort " {{{2 return keys(filter( copy(self._checkerMap[ft]), 'v:val.isAvailable()' )) endfunction " }}}2 +function! g:SyntasticRegistry.resolveFiletypes(ftalias) abort " {{{2 + return map(split( get(g:syntastic_filetype_map, a:ftalias, a:ftalias), '\m\.' ), 's:_normalise_filetype(v:val)') +endfunction " }}}2 + function! g:SyntasticRegistry.echoInfoFor(ftalias_list) abort " {{{2 - let ft_list = syntastic#util#unique(map( copy(a:ftalias_list), 's:_normalise_filetype(v:val)' )) + let ft_list = syntastic#util#unique(self.resolveFiletypes(empty(a:ftalias_list) ? &filetype : a:ftalias_list[0])) if len(ft_list) != 1 let available = [] let active = [] @@ -321,8 +340,20 @@ function! g:SyntasticRegistry._registerChecker(checker) abort " {{{2 let self._checkerMap[ft][name] = a:checker endfunction " }}}2 -function! g:SyntasticRegistry._filterCheckersByName(checkers_map, list) abort " {{{2 - return filter( map(copy(a:list), 'get(a:checkers_map, v:val, {})'), '!empty(v:val)' ) +function! g:SyntasticRegistry._findChecker(cname) abort " {{{2 + let sep_idx = stridx(a:cname, '/') + if sep_idx > 0 + let ft = a:cname[: sep_idx-1] + let name = a:cname[sep_idx+1 :] + else + let ft = &filetype + let name = a:cname + endif + return get(self._checkerMap[ft], name, {}) +endfunction "}}}2 + +function! g:SyntasticRegistry._filterCheckersByName(cnames) abort " {{{2 + return filter( map(copy(a:cnames), 'self._findChecker(v:val)'), '!empty(v:val)' ) endfunction " }}}2 function! g:SyntasticRegistry._loadCheckersFor(filetype, force) abort " {{{2 @@ -338,7 +369,14 @@ function! g:SyntasticRegistry._loadCheckersFor(filetype, force) abort " {{{2 endfunction " }}}2 " Check for obsolete variable g:syntastic__checker -function! g:SyntasticRegistry._checkDeprecation(filetype) abort " {{{2 +function! g:SyntasticRegistry._sanityCheck(filetype) abort " {{{2 + if exists('g:syntastic_' . a:filetype . '_checkers') && + \ type(g:syntastic_{a:filetype}_checkers) != type([]) + + unlet! g:syntastic_{a:filetype}_checkers + call syntastic#log#error('variable g:syntastic_' . a:filetype . '_checkers has to be a list of strings') + endif + if exists('g:syntastic_' . a:filetype . '_checker') && \ !exists('g:syntastic_' . a:filetype . '_checkers') && \ type(g:syntastic_{a:filetype}_checker) == type('')