1 "============================================================================
3 "Description: vim plugin for on the fly syntax checking
4 "Maintainer: Martin Grenfell <martin.grenfell at gmail dot com>
6 "Last Change: 16 Feb, 2012
7 "License: This program is free software. It comes without any warranty,
8 " to the extent permitted by applicable law. You can redistribute
9 " it and/or modify it under the terms of the Do What The Fuck You
10 " Want To Public License, Version 2, as published by Sam Hocevar.
11 " See http://sam.zoy.org/wtfpl/COPYING for more details.
13 "============================================================================
15 if exists("g:loaded_syntastic_plugin")
18 let g:loaded_syntastic_plugin = 1
20 let s:running_windows = has("win16") || has("win32") || has("win64")
23 let s:uname = system('uname')
26 if !exists("g:syntastic_enable_signs")
27 let g:syntastic_enable_signs = 1
30 let g:syntastic_enable_signs = 0
33 if !exists("g:syntastic_enable_balloons")
34 let g:syntastic_enable_balloons = 1
36 if !has('balloon_eval')
37 let g:syntastic_enable_balloons = 0
40 if !exists("g:syntastic_enable_highlighting")
41 let g:syntastic_enable_highlighting = 1
44 if !exists("g:syntastic_echo_current_error")
45 let g:syntastic_echo_current_error = 1
48 if !exists("g:syntastic_auto_loc_list")
49 let g:syntastic_auto_loc_list = 2
52 if !exists("g:syntastic_auto_jump")
53 let syntastic_auto_jump=0
56 if !exists("g:syntastic_quiet_warnings")
57 let g:syntastic_quiet_warnings = 0
60 if !exists("g:syntastic_stl_format")
61 let g:syntastic_stl_format = '[Syntax: line:%F (%t)]'
64 if !exists("g:syntastic_mode_map")
65 let g:syntastic_mode_map = {}
68 if !has_key(g:syntastic_mode_map, "mode")
69 let g:syntastic_mode_map['mode'] = 'active'
72 if !has_key(g:syntastic_mode_map, "active_filetypes")
73 let g:syntastic_mode_map['active_filetypes'] = []
76 if !has_key(g:syntastic_mode_map, "passive_filetypes")
77 let g:syntastic_mode_map['passive_filetypes'] = []
80 if !exists("g:syntastic_check_on_open")
81 let g:syntastic_check_on_open = 0
84 if !exists("g:syntastic_loc_list_height")
85 let g:syntastic_loc_list_height = 10
88 command! SyntasticToggleMode call s:ToggleMode()
89 command! SyntasticCheck call s:UpdateErrors(0) <bar> redraw!
90 command! Errors call s:ShowLocList()
92 highlight link SyntasticError SpellBad
93 highlight link SyntasticWarning SpellCap
96 if g:syntastic_echo_current_error
97 autocmd cursormoved * call s:EchoCurrentError()
100 autocmd BufReadPost * if g:syntastic_check_on_open | call s:UpdateErrors(1) | endif
101 autocmd BufWritePost * call s:UpdateErrors(1)
103 autocmd BufWinEnter * if empty(&bt) | call s:AutoToggleLocList() | endif
104 autocmd BufWinLeave * if empty(&bt) | lclose | endif
108 "refresh and redraw all the error info for this buf when saving or reading
109 function! s:UpdateErrors(auto_invoked)
114 if !a:auto_invoked || s:ModeMapAllowsAutoChecking()
118 if s:BufHasErrorsOrWarningsToDisplay()
119 call setloclist(0, s:LocList())
122 if g:syntastic_enable_balloons
123 call s:RefreshBalloons()
126 if g:syntastic_enable_signs
127 call s:RefreshSigns()
130 if g:syntastic_auto_jump && s:BufHasErrorsOrWarningsToDisplay()
134 call s:AutoToggleLocList()
137 "automatically open/close the location list window depending on the users
138 "config and buffer error state
139 function! s:AutoToggleLocList()
140 if s:BufHasErrorsOrWarningsToDisplay()
141 if g:syntastic_auto_loc_list == 1
145 if g:syntastic_auto_loc_list > 0
147 "TODO: this will close the loc list window if one was opened by
148 "something other than syntastic
154 "lazy init the loc list for the current buffer
155 function! s:LocList()
156 if !exists("b:syntastic_loclist")
157 let b:syntastic_loclist = []
159 return b:syntastic_loclist
162 "clear the loc list for the buffer
163 function! s:ClearLocList()
164 let b:syntastic_loclist = []
167 "detect and cache all syntax errors in this buffer
169 "depends on a function called SyntaxCheckers_{&ft}_GetLocList() existing
171 function! s:CacheErrors()
172 call s:ClearLocList()
174 if filereadable(expand("%"))
176 "sub - for _ in filetypes otherwise we cant name syntax checker
177 "functions legally for filetypes like "gentoo-metadata"
178 let fts = substitute(&ft, '-', '_', 'g')
179 for ft in split(fts, '\.')
181 let errors = SyntaxCheckers_{ft}_GetLocList()
182 "make errors have type "E" by default
183 call SyntasticAddToErrors(errors, {'type': 'E'})
184 call extend(s:LocList(), errors)
190 "toggle the g:syntastic_mode_map['mode']
191 function! s:ToggleMode()
192 if g:syntastic_mode_map['mode'] == "active"
193 let g:syntastic_mode_map['mode'] = "passive"
195 let g:syntastic_mode_map['mode'] = "active"
198 call s:ClearLocList()
199 call s:UpdateErrors(1)
201 echo "Syntastic: " . g:syntastic_mode_map['mode'] . " mode enabled"
204 "check the current filetypes against g:syntastic_mode_map to determine whether
205 "active mode syntax checking should be done
206 function! s:ModeMapAllowsAutoChecking()
207 let fts = split(&ft, '\.')
209 if g:syntastic_mode_map['mode'] == 'passive'
210 "check at least one filetype is active
211 let actives = g:syntastic_mode_map["active_filetypes"]
212 return !empty(filter(fts, 'index(actives, v:val) != -1'))
214 "check no filetypes are passive
215 let passives = g:syntastic_mode_map["passive_filetypes"]
216 return empty(filter(fts, 'index(passives, v:val) != -1'))
220 "return true if there are cached errors/warnings for this buf
221 function! s:BufHasErrorsOrWarnings()
222 return !empty(s:LocList())
225 "return true if there are cached errors for this buf
226 function! s:BufHasErrors()
227 return len(s:ErrorsForType('E')) > 0
230 function! s:BufHasErrorsOrWarningsToDisplay()
231 return s:BufHasErrors() || (!g:syntastic_quiet_warnings && s:BufHasErrorsOrWarnings())
234 function! s:ErrorsForType(type)
235 return s:FilterLocList({'type': a:type})
239 return s:ErrorsForType("E")
242 function! s:Warnings()
243 return s:ErrorsForType("W")
246 "Filter a loc list (defaults to s:LocList()) by a:filters
248 " s:FilterLocList({'bufnr': 10, 'type': 'e'})
250 "would return all errors in s:LocList() for buffer 10.
252 "Note that all comparisons are done with ==?
253 function! s:FilterLocList(filters, ...)
254 let llist = a:0 ? a:1 : s:LocList()
256 let rv = deepcopy(llist)
258 for key in keys(a:filters)
259 let rhs = a:filters[key]
260 if type(rhs) == 1 "string
261 let rhs = '"' . rhs . '"'
264 call filter(rv, "v:val['".key."'] ==? " . rhs)
270 if g:syntastic_enable_signs
271 "define the signs used to display syntax and style errors/warns
272 sign define SyntasticError text=>> texthl=error
273 sign define SyntasticWarning text=>> texthl=todo
274 sign define SyntasticStyleError text=S> texthl=error
275 sign define SyntasticStyleWarning text=S> texthl=todo
278 "start counting sign ids at 5000, start here to hopefully avoid conflicting
279 "with any other code that places signs (not sure if this precaution is
281 let s:first_sign_id = 5000
282 let s:next_sign_id = s:first_sign_id
284 "place signs by all syntax errs in the buffer
285 function! s:SignErrors()
286 if s:BufHasErrorsOrWarningsToDisplay()
288 let errors = s:FilterLocList({'bufnr': bufnr('')})
290 let sign_severity = 'Error'
291 let sign_subtype = ''
292 if has_key(i,'subtype')
293 let sign_subtype = i['subtype']
296 let sign_severity = 'Warning'
298 let sign_type = 'Syntastic' . sign_subtype . sign_severity
300 if !s:WarningMasksError(i, errors)
301 exec "sign place ". s:next_sign_id ." line=". i['lnum'] ." name=". sign_type ." file=". expand("%:p")
302 call add(s:BufSignIds(), s:next_sign_id)
303 let s:next_sign_id += 1
309 "return true if the given error item is a warning that, if signed, would
310 "potentially mask an error if displayed at the same time
311 function! s:WarningMasksError(error, llist)
312 if a:error['type'] !=? 'w'
316 return len(s:FilterLocList({ 'type': "E", 'lnum': a:error['lnum'] }, a:llist)) > 0
319 "remove the signs with the given ids from this buffer
320 function! s:RemoveSigns(ids)
322 exec "sign unplace " . i
323 call remove(s:BufSignIds(), index(s:BufSignIds(), i))
327 "get all the ids of the SyntaxError signs in the buffer
328 function! s:BufSignIds()
329 if !exists("b:syntastic_sign_ids")
330 let b:syntastic_sign_ids = []
332 return b:syntastic_sign_ids
335 "update the error signs
336 function! s:RefreshSigns()
337 let old_signs = copy(s:BufSignIds())
339 call s:RemoveSigns(old_signs)
340 let s:first_sign_id = s:next_sign_id
343 "display the cached errors for this buf in the location list
344 function! s:ShowLocList()
345 if !empty(s:LocList())
347 exec "lopen " . g:syntastic_loc_list_height
354 "remove all error highlights from the window
355 function! s:ClearErrorHighlights()
356 for match in getmatches()
357 if stridx(match['group'], 'Syntastic') == 0
358 call matchdelete(match['id'])
363 "check if a syntax checker exists for the given filetype - and attempt to
365 function! s:Checkable(ft)
366 if !exists("g:loaded_" . a:ft . "_syntax_checker")
367 exec "runtime syntax_checkers/" . a:ft . ".vim"
370 return exists("*SyntaxCheckers_". a:ft ."_GetLocList")
373 "set up error ballons for the current set of errors
374 function! s:RefreshBalloons()
375 let b:syntastic_balloons = {}
376 if s:BufHasErrorsOrWarningsToDisplay()
378 let b:syntastic_balloons[i['lnum']] = i['text']
380 set beval bexpr=SyntasticErrorBalloonExpr()
384 "print as much of a:msg as possible without "Press Enter" prompt appearing
385 function! s:WideMsg(msg)
386 let old_ruler = &ruler
387 let old_showcmd = &showcmd
389 let msg = strpart(a:msg, 0, winwidth(0)-1)
391 "This is here because it is possible for some error messages to begin with
392 "\n which will cause a "press enter" prompt. I have noticed this in the
393 "javascript:jshint checker and have been unable to figure out why it
395 let msg = substitute(msg, "\n", "", "g")
397 set noruler noshowcmd
403 let &showcmd=old_showcmd
406 "echo out the first error we find for the current line in the cmd window
407 function! s:EchoCurrentError()
408 "If we have an error or warning at the current line, show it
409 let errors = s:FilterLocList({'lnum': line("."), "type": 'e'})
410 let warnings = s:FilterLocList({'lnum': line("."), "type": 'w'})
412 let b:syntastic_echoing_error = len(errors) || len(warnings)
414 return s:WideMsg(errors[0]['text'])
417 return s:WideMsg(warnings[0]['text'])
420 "Otherwise, clear the status line
421 if b:syntastic_echoing_error
423 let b:syntastic_echoing_error = 0
427 "load the chosen checker for the current filetype - useful for filetypes like
428 "javascript that have more than one syntax checker
429 function! s:LoadChecker(checker)
430 exec "runtime syntax_checkers/" . &ft . "/" . a:checker . ".vim"
433 "return a string representing the state of buffer according to
434 "g:syntastic_stl_format
436 "return '' if no errors are cached for the buffer
437 function! SyntasticStatuslineFlag()
438 if s:BufHasErrorsOrWarningsToDisplay()
439 let errors = s:Errors()
440 let warnings = s:Warnings()
442 let output = g:syntastic_stl_format
444 "hide stuff wrapped in %E(...) unless there are errors
445 let output = substitute(output, '\C%E{\([^}]*\)}', len(errors) ? '\1' : '' , 'g')
447 "hide stuff wrapped in %W(...) unless there are warnings
448 let output = substitute(output, '\C%W{\([^}]*\)}', len(warnings) ? '\1' : '' , 'g')
450 "hide stuff wrapped in %B(...) unless there are both errors and warnings
451 let output = substitute(output, '\C%B{\([^}]*\)}', (len(warnings) && len(errors)) ? '\1' : '' , 'g')
453 "sub in the total errors/warnings/both
454 let output = substitute(output, '\C%w', len(warnings), 'g')
455 let output = substitute(output, '\C%e', len(errors), 'g')
456 let output = substitute(output, '\C%t', len(s:LocList()), 'g')
458 "first error/warning line num
459 let output = substitute(output, '\C%F', s:LocList()[0]['lnum'], 'g')
461 "first error line num
462 let output = substitute(output, '\C%fe', len(errors) ? errors[0]['lnum'] : '', 'g')
464 "first warning line num
465 let output = substitute(output, '\C%fw', len(warnings) ? warnings[0]['lnum'] : '', 'g')
473 "A wrapper for the :lmake command. Sets up the make environment according to
474 "the options given, runs make, resets the environment, returns the location
477 "a:options can contain the following keys:
481 "The corresponding options are set for the duration of the function call. They
482 "are set with :let, so dont escape spaces.
484 "a:options may also contain:
485 " 'defaults' - a dict containing default values for the returned errors
486 " 'subtype' - all errors will be assigned the given subtype
487 function! SyntasticMake(options)
488 let old_loclist = getloclist(0)
489 let old_makeprg = &makeprg
490 let old_shellpipe = &shellpipe
491 let old_shell = &shell
492 let old_errorformat = &errorformat
494 if !s:running_windows && (s:uname !~ "FreeBSD")
495 "this is a hack to stop the screen needing to be ':redraw'n when
496 "when :lmake is run. Otherwise the screen flickers annoyingly
498 let &shell = '/bin/bash'
501 if has_key(a:options, 'makeprg')
502 let &makeprg = a:options['makeprg']
505 if has_key(a:options, 'errorformat')
506 let &errorformat = a:options['errorformat']
510 let errors = getloclist(0)
512 call setloclist(0, old_loclist)
513 let &makeprg = old_makeprg
514 let &errorformat = old_errorformat
515 let &shellpipe=old_shellpipe
518 if !s:running_windows && s:uname =~ "FreeBSD"
522 if has_key(a:options, 'defaults')
523 call SyntasticAddToErrors(errors, a:options['defaults'])
526 " Add subtype info if present.
527 if has_key(a:options, 'subtype')
528 call SyntasticAddToErrors(errors, {'subtype': a:options['subtype']})
534 "get the error balloon for the current mouse position
535 function! SyntasticErrorBalloonExpr()
536 if !exists('b:syntastic_balloons')
539 return get(b:syntastic_balloons, v:beval_lnum, '')
542 "highlight the list of errors (a:errors) using matchadd()
544 "a:termfunc is provided to highlight errors that do not have a 'col' key (and
545 "hence cant be done automatically). This function must take one arg (an error
546 "item) and return a regex to match that item in the buffer.
548 "an optional boolean third argument can be provided to force a:termfunc to be
549 "used regardless of whether a 'col' key is present for the error
550 function! SyntasticHighlightErrors(errors, termfunc, ...)
551 if !g:syntastic_enable_highlighting
555 call s:ClearErrorHighlights()
557 let force_callback = a:0 && a:1
559 let group = item['type'] == 'E' ? 'SyntasticError' : 'SyntasticWarning'
560 if item['col'] && !force_callback
561 let lastcol = col([item['lnum'], '$'])
562 let lcol = min([lastcol, item['col']])
563 call matchadd(group, '\%'.item['lnum'].'l\%'.lcol.'c')
565 let term = a:termfunc(item)
567 call matchadd(group, '\%' . item['lnum'] . 'l' . term)
573 "take a list of errors and add default values to them from a:options
574 function! SyntasticAddToErrors(errors, options)
575 for i in range(0, len(a:errors)-1)
576 for key in keys(a:options)
577 if !has_key(a:errors[i], key) || empty(a:errors[i][key])
578 let a:errors[i][key] = a:options[key]
585 "take a list of syntax checkers for the current filetype and load the right
586 "one based on the global settings and checker executable availabity
588 "a:checkers should be a list of syntax checker names. These names are assumed
589 "to be the names of the vim syntax checker files that should be sourced, as
590 "well as the names of the actual syntax checker executables. The checkers
591 "should be listed in order of default preference.
593 "if a option called 'g:syntastic_[filetype]_checker' exists then attempt to
594 "load the checker that it points to
595 function! SyntasticLoadChecker(checkers)
596 let opt_name = "g:syntastic_" . &ft . "_checker"
599 let opt_val = {opt_name}
600 if index(a:checkers, opt_val) != -1 && executable(opt_val)
601 call s:LoadChecker(opt_val)
603 echoerr &ft . " syntax not supported or not installed."
606 for checker in a:checkers
607 if executable(checker)
608 return s:LoadChecker(checker)
614 " vim: set et sts=4 sw=4: