]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/plugin/syntastic.vim
0eb657a2b8510038f1c7388d2a366f1bbe495e63
[rbdr/dotfiles] / vim / plugin / syntastic.vim
1 "============================================================================
2 "File: syntastic.vim
3 "Description: vim plugin for on the fly syntax checking
4 "Maintainer: Martin Grenfell <martin.grenfell at gmail dot com>
5 "Version: 2.3.0
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.
12 "
13 "============================================================================
14
15 if exists("g:loaded_syntastic_plugin")
16 finish
17 endif
18 let g:loaded_syntastic_plugin = 1
19
20 let s:running_windows = has("win16") || has("win32") || has("win64")
21
22 if !s:running_windows
23 let s:uname = system('uname')
24 endif
25
26 if !exists("g:syntastic_enable_signs")
27 let g:syntastic_enable_signs = 1
28 endif
29 if !has('signs')
30 let g:syntastic_enable_signs = 0
31 endif
32
33 if !exists("g:syntastic_enable_balloons")
34 let g:syntastic_enable_balloons = 1
35 endif
36 if !has('balloon_eval')
37 let g:syntastic_enable_balloons = 0
38 endif
39
40 if !exists("g:syntastic_enable_highlighting")
41 let g:syntastic_enable_highlighting = 1
42 endif
43
44 if !exists("g:syntastic_echo_current_error")
45 let g:syntastic_echo_current_error = 1
46 endif
47
48 if !exists("g:syntastic_auto_loc_list")
49 let g:syntastic_auto_loc_list = 2
50 endif
51
52 if !exists("g:syntastic_auto_jump")
53 let syntastic_auto_jump=0
54 endif
55
56 if !exists("g:syntastic_quiet_warnings")
57 let g:syntastic_quiet_warnings = 0
58 endif
59
60 if !exists("g:syntastic_stl_format")
61 let g:syntastic_stl_format = '[Syntax: line:%F (%t)]'
62 endif
63
64 if !exists("g:syntastic_mode_map")
65 let g:syntastic_mode_map = {}
66 endif
67
68 if !has_key(g:syntastic_mode_map, "mode")
69 let g:syntastic_mode_map['mode'] = 'active'
70 endif
71
72 if !has_key(g:syntastic_mode_map, "active_filetypes")
73 let g:syntastic_mode_map['active_filetypes'] = []
74 endif
75
76 if !has_key(g:syntastic_mode_map, "passive_filetypes")
77 let g:syntastic_mode_map['passive_filetypes'] = []
78 endif
79
80 if !exists("g:syntastic_check_on_open")
81 let g:syntastic_check_on_open = 0
82 endif
83
84 if !exists("g:syntastic_loc_list_height")
85 let g:syntastic_loc_list_height = 10
86 endif
87
88 command! SyntasticToggleMode call s:ToggleMode()
89 command! SyntasticCheck call s:UpdateErrors(0) <bar> redraw!
90 command! Errors call s:ShowLocList()
91
92 highlight link SyntasticError SpellBad
93 highlight link SyntasticWarning SpellCap
94
95 augroup syntastic
96 if g:syntastic_echo_current_error
97 autocmd cursormoved * call s:EchoCurrentError()
98 endif
99
100 autocmd BufReadPost * if g:syntastic_check_on_open | call s:UpdateErrors(1) | endif
101 autocmd BufWritePost * call s:UpdateErrors(1)
102
103 autocmd BufWinEnter * if empty(&bt) | call s:AutoToggleLocList() | endif
104 autocmd BufWinLeave * if empty(&bt) | lclose | endif
105 augroup END
106
107
108 "refresh and redraw all the error info for this buf when saving or reading
109 function! s:UpdateErrors(auto_invoked)
110 if !empty(&buftype)
111 return
112 endif
113
114 if !a:auto_invoked || s:ModeMapAllowsAutoChecking()
115 call s:CacheErrors()
116 end
117
118 if s:BufHasErrorsOrWarningsToDisplay()
119 call setloclist(0, s:LocList())
120 endif
121
122 if g:syntastic_enable_balloons
123 call s:RefreshBalloons()
124 endif
125
126 if g:syntastic_enable_signs
127 call s:RefreshSigns()
128 endif
129
130 if g:syntastic_auto_jump && s:BufHasErrorsOrWarningsToDisplay()
131 silent! ll
132 endif
133
134 call s:AutoToggleLocList()
135 endfunction
136
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
142 call s:ShowLocList()
143 endif
144 else
145 if g:syntastic_auto_loc_list > 0
146
147 "TODO: this will close the loc list window if one was opened by
148 "something other than syntastic
149 lclose
150 endif
151 endif
152 endfunction
153
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 = []
158 endif
159 return b:syntastic_loclist
160 endfunction
161
162 "clear the loc list for the buffer
163 function! s:ClearLocList()
164 let b:syntastic_loclist = []
165 endfunction
166
167 "detect and cache all syntax errors in this buffer
168 "
169 "depends on a function called SyntaxCheckers_{&ft}_GetLocList() existing
170 "elsewhere
171 function! s:CacheErrors()
172 call s:ClearLocList()
173
174 if filereadable(expand("%"))
175
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, '\.')
180 if s:Checkable(ft)
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)
185 endif
186 endfor
187 endif
188 endfunction
189
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"
194 else
195 let g:syntastic_mode_map['mode'] = "active"
196 endif
197
198 call s:ClearLocList()
199 call s:UpdateErrors(1)
200
201 echo "Syntastic: " . g:syntastic_mode_map['mode'] . " mode enabled"
202 endfunction
203
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, '\.')
208
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'))
213 else
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'))
217 endif
218 endfunction
219
220 "return true if there are cached errors/warnings for this buf
221 function! s:BufHasErrorsOrWarnings()
222 return !empty(s:LocList())
223 endfunction
224
225 "return true if there are cached errors for this buf
226 function! s:BufHasErrors()
227 return len(s:ErrorsForType('E')) > 0
228 endfunction
229
230 function! s:BufHasErrorsOrWarningsToDisplay()
231 return s:BufHasErrors() || (!g:syntastic_quiet_warnings && s:BufHasErrorsOrWarnings())
232 endfunction
233
234 function! s:ErrorsForType(type)
235 return s:FilterLocList({'type': a:type})
236 endfunction
237
238 function! s:Errors()
239 return s:ErrorsForType("E")
240 endfunction
241
242 function! s:Warnings()
243 return s:ErrorsForType("W")
244 endfunction
245
246 "Filter a loc list (defaults to s:LocList()) by a:filters
247 "e.g.
248 " s:FilterLocList({'bufnr': 10, 'type': 'e'})
249 "
250 "would return all errors in s:LocList() for buffer 10.
251 "
252 "Note that all comparisons are done with ==?
253 function! s:FilterLocList(filters, ...)
254 let llist = a:0 ? a:1 : s:LocList()
255
256 let rv = deepcopy(llist)
257 for error in llist
258 for key in keys(a:filters)
259 let rhs = a:filters[key]
260 if type(rhs) == 1 "string
261 let rhs = '"' . rhs . '"'
262 endif
263
264 call filter(rv, "v:val['".key."'] ==? " . rhs)
265 endfor
266 endfor
267 return rv
268 endfunction
269
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
276 endif
277
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
280 "actually needed)
281 let s:first_sign_id = 5000
282 let s:next_sign_id = s:first_sign_id
283
284 "place signs by all syntax errs in the buffer
285 function! s:SignErrors()
286 if s:BufHasErrorsOrWarningsToDisplay()
287
288 let errors = s:FilterLocList({'bufnr': bufnr('')})
289 for i in errors
290 let sign_severity = 'Error'
291 let sign_subtype = ''
292 if has_key(i,'subtype')
293 let sign_subtype = i['subtype']
294 endif
295 if i['type'] ==? 'w'
296 let sign_severity = 'Warning'
297 endif
298 let sign_type = 'Syntastic' . sign_subtype . sign_severity
299
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
304 endif
305 endfor
306 endif
307 endfunction
308
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'
313 return 0
314 endif
315
316 return len(s:FilterLocList({ 'type': "E", 'lnum': a:error['lnum'] }, a:llist)) > 0
317 endfunction
318
319 "remove the signs with the given ids from this buffer
320 function! s:RemoveSigns(ids)
321 for i in a:ids
322 exec "sign unplace " . i
323 call remove(s:BufSignIds(), index(s:BufSignIds(), i))
324 endfor
325 endfunction
326
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 = []
331 endif
332 return b:syntastic_sign_ids
333 endfunction
334
335 "update the error signs
336 function! s:RefreshSigns()
337 let old_signs = copy(s:BufSignIds())
338 call s:SignErrors()
339 call s:RemoveSigns(old_signs)
340 let s:first_sign_id = s:next_sign_id
341 endfunction
342
343 "display the cached errors for this buf in the location list
344 function! s:ShowLocList()
345 if !empty(s:LocList())
346 let num = winnr()
347 exec "lopen " . g:syntastic_loc_list_height
348 if num != winnr()
349 wincmd p
350 endif
351 endif
352 endfunction
353
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'])
359 endif
360 endfor
361 endfunction
362
363 "check if a syntax checker exists for the given filetype - and attempt to
364 "load one
365 function! s:Checkable(ft)
366 if !exists("g:loaded_" . a:ft . "_syntax_checker")
367 exec "runtime syntax_checkers/" . a:ft . ".vim"
368 endif
369
370 return exists("*SyntaxCheckers_". a:ft ."_GetLocList")
371 endfunction
372
373 "set up error ballons for the current set of errors
374 function! s:RefreshBalloons()
375 let b:syntastic_balloons = {}
376 if s:BufHasErrorsOrWarningsToDisplay()
377 for i in s:LocList()
378 let b:syntastic_balloons[i['lnum']] = i['text']
379 endfor
380 set beval bexpr=SyntasticErrorBalloonExpr()
381 endif
382 endfunction
383
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
388
389 let msg = strpart(a:msg, 0, winwidth(0)-1)
390
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
394 "happens
395 let msg = substitute(msg, "\n", "", "g")
396
397 set noruler noshowcmd
398 redraw
399
400 echo msg
401
402 let &ruler=old_ruler
403 let &showcmd=old_showcmd
404 endfunction
405
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'})
411
412 let b:syntastic_echoing_error = len(errors) || len(warnings)
413 if len(errors)
414 return s:WideMsg(errors[0]['text'])
415 endif
416 if len(warnings)
417 return s:WideMsg(warnings[0]['text'])
418 endif
419
420 "Otherwise, clear the status line
421 if b:syntastic_echoing_error
422 echo
423 let b:syntastic_echoing_error = 0
424 endif
425 endfunction
426
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"
431 endfunction
432
433 "return a string representing the state of buffer according to
434 "g:syntastic_stl_format
435 "
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()
441
442 let output = g:syntastic_stl_format
443
444 "hide stuff wrapped in %E(...) unless there are errors
445 let output = substitute(output, '\C%E{\([^}]*\)}', len(errors) ? '\1' : '' , 'g')
446
447 "hide stuff wrapped in %W(...) unless there are warnings
448 let output = substitute(output, '\C%W{\([^}]*\)}', len(warnings) ? '\1' : '' , 'g')
449
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')
452
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')
457
458 "first error/warning line num
459 let output = substitute(output, '\C%F', s:LocList()[0]['lnum'], 'g')
460
461 "first error line num
462 let output = substitute(output, '\C%fe', len(errors) ? errors[0]['lnum'] : '', 'g')
463
464 "first warning line num
465 let output = substitute(output, '\C%fw', len(warnings) ? warnings[0]['lnum'] : '', 'g')
466
467 return output
468 else
469 return ''
470 endif
471 endfunction
472
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
475 "list
476 "
477 "a:options can contain the following keys:
478 " 'makeprg'
479 " 'errorformat'
480 "
481 "The corresponding options are set for the duration of the function call. They
482 "are set with :let, so dont escape spaces.
483 "
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
493
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
497 let &shellpipe='&>'
498 let &shell = '/bin/bash'
499 endif
500
501 if has_key(a:options, 'makeprg')
502 let &makeprg = a:options['makeprg']
503 endif
504
505 if has_key(a:options, 'errorformat')
506 let &errorformat = a:options['errorformat']
507 endif
508
509 silent lmake!
510 let errors = getloclist(0)
511
512 call setloclist(0, old_loclist)
513 let &makeprg = old_makeprg
514 let &errorformat = old_errorformat
515 let &shellpipe=old_shellpipe
516 let &shell=old_shell
517
518 if !s:running_windows && s:uname =~ "FreeBSD"
519 redraw!
520 endif
521
522 if has_key(a:options, 'defaults')
523 call SyntasticAddToErrors(errors, a:options['defaults'])
524 endif
525
526 " Add subtype info if present.
527 if has_key(a:options, 'subtype')
528 call SyntasticAddToErrors(errors, {'subtype': a:options['subtype']})
529 endif
530
531 return errors
532 endfunction
533
534 "get the error balloon for the current mouse position
535 function! SyntasticErrorBalloonExpr()
536 if !exists('b:syntastic_balloons')
537 return ''
538 endif
539 return get(b:syntastic_balloons, v:beval_lnum, '')
540 endfunction
541
542 "highlight the list of errors (a:errors) using matchadd()
543 "
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.
547 "
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
552 return
553 endif
554
555 call s:ClearErrorHighlights()
556
557 let force_callback = a:0 && a:1
558 for item in a:errors
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')
564 else
565 let term = a:termfunc(item)
566 if len(term) > 0
567 call matchadd(group, '\%' . item['lnum'] . 'l' . term)
568 endif
569 endif
570 endfor
571 endfunction
572
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]
579 endif
580 endfor
581 endfor
582 return a:errors
583 endfunction
584
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
587 "
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.
592 "
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"
597
598 if exists(opt_name)
599 let opt_val = {opt_name}
600 if index(a:checkers, opt_val) != -1 && executable(opt_val)
601 call s:LoadChecker(opt_val)
602 else
603 echoerr &ft . " syntax not supported or not installed."
604 endif
605 else
606 for checker in a:checkers
607 if executable(checker)
608 return s:LoadChecker(checker)
609 endif
610 endfor
611 endif
612 endfunction
613
614 " vim: set et sts=4 sw=4: