util.vim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. if exists('g:loaded_syntastic_util_autoload') || !exists('g:loaded_syntastic_plugin')
  2. finish
  3. endif
  4. let g:loaded_syntastic_util_autoload = 1
  5. let s:save_cpo = &cpo
  6. set cpo&vim
  7. " Public functions {{{1
  8. function! syntastic#util#isRunningWindows() abort " {{{2
  9. return has('win16') || has('win32') || has('win64')
  10. endfunction " }}}2
  11. function! syntastic#util#DevNull() abort " {{{2
  12. if syntastic#util#isRunningWindows()
  13. return 'NUL'
  14. endif
  15. return '/dev/null'
  16. endfunction " }}}2
  17. " Get directory separator
  18. function! syntastic#util#Slash() abort " {{{2
  19. return (!exists('+shellslash') || &shellslash) ? '/' : '\'
  20. endfunction " }}}2
  21. function! syntastic#util#CygwinPath(path) abort " {{{2
  22. return substitute(syntastic#util#system('cygpath -m ' . syntastic#util#shescape(a:path)), "\n", '', 'g')
  23. endfunction " }}}2
  24. function! syntastic#util#system(command) abort " {{{2
  25. let old_shell = &shell
  26. let old_lc_messages = $LC_MESSAGES
  27. let old_lc_all = $LC_ALL
  28. let &shell = syntastic#util#var('shell')
  29. let $LC_MESSAGES = 'C'
  30. let $LC_ALL = ''
  31. let out = system(a:command)
  32. let $LC_ALL = old_lc_all
  33. let $LC_MESSAGES = old_lc_messages
  34. let &shell = old_shell
  35. return out
  36. endfunction " }}}2
  37. " Create a temporary directory
  38. function! syntastic#util#tmpdir() abort " {{{2
  39. let tempdir = ''
  40. if (has('unix') || has('mac')) && executable('mktemp')
  41. " TODO: option "-t" to mktemp(1) is not portable
  42. let tmp = $TMPDIR !=# '' ? $TMPDIR : $TMP !=# '' ? $TMP : '/tmp'
  43. let out = split(syntastic#util#system('mktemp -q -d ' . tmp . '/vim-syntastic-' . getpid() . '-XXXXXXXX'), "\n")
  44. if v:shell_error == 0 && len(out) == 1
  45. let tempdir = out[0]
  46. endif
  47. endif
  48. if tempdir ==# ''
  49. if has('win32') || has('win64')
  50. let tempdir = $TEMP . syntastic#util#Slash() . 'vim-syntastic-' . getpid()
  51. elseif has('win32unix')
  52. let tempdir = syntastic#util#CygwinPath('/tmp/vim-syntastic-' . getpid())
  53. elseif $TMPDIR !=# ''
  54. let tempdir = $TMPDIR . '/vim-syntastic-' . getpid()
  55. else
  56. let tempdir = '/tmp/vim-syntastic-' . getpid()
  57. endif
  58. try
  59. call mkdir(tempdir, 'p', 0700)
  60. catch /\m^Vim\%((\a\+)\)\=:E739/
  61. call syntastic#log#error(v:exception)
  62. let tempdir = '.'
  63. endtry
  64. endif
  65. return tempdir
  66. endfunction " }}}2
  67. " Recursively remove a directory
  68. function! syntastic#util#rmrf(what) abort " {{{2
  69. " try to make sure we don't delete directories we didn't create
  70. if a:what !~? 'vim-syntastic-'
  71. return
  72. endif
  73. if getftype(a:what) ==# 'dir'
  74. if !exists('s:rmrf')
  75. let s:rmrf =
  76. \ has('unix') || has('mac') ? 'rm -rf' :
  77. \ has('win32') || has('win64') ? 'rmdir /S /Q' :
  78. \ has('win16') || has('win95') || has('dos16') || has('dos32') ? 'deltree /Y' : ''
  79. endif
  80. if s:rmrf !=# ''
  81. silent! call syntastic#util#system(s:rmrf . ' ' . syntastic#util#shescape(a:what))
  82. else
  83. call s:_rmrf(a:what)
  84. endif
  85. else
  86. silent! call delete(a:what)
  87. endif
  88. endfunction " }}}2
  89. "search the first 5 lines of the file for a magic number and return a map
  90. "containing the args and the executable
  91. "
  92. "e.g.
  93. "
  94. "#!/usr/bin/perl -f -bar
  95. "
  96. "returns
  97. "
  98. "{'exe': '/usr/bin/perl', 'args': ['-f', '-bar']}
  99. function! syntastic#util#parseShebang() abort " {{{2
  100. for lnum in range(1, 5)
  101. let line = getline(lnum)
  102. if line =~# '^#!'
  103. let line = substitute(line, '\v^#!\s*(\S+/env(\s+-\S+)*\s+)?', '', '')
  104. let exe = matchstr(line, '\m^\S*\ze')
  105. let args = split(matchstr(line, '\m^\S*\zs.*'))
  106. return { 'exe': exe, 'args': args }
  107. endif
  108. endfor
  109. return { 'exe': '', 'args': [] }
  110. endfunction " }}}2
  111. " Get the value of a variable. Allow local variables to override global ones.
  112. function! syntastic#util#var(name, ...) abort " {{{2
  113. return
  114. \ exists('b:syntastic_' . a:name) ? b:syntastic_{a:name} :
  115. \ exists('g:syntastic_' . a:name) ? g:syntastic_{a:name} :
  116. \ a:0 > 0 ? a:1 : ''
  117. endfunction " }}}2
  118. " Parse a version string. Return an array of version components.
  119. function! syntastic#util#parseVersion(version, ...) abort " {{{2
  120. return map(split(matchstr( a:version, a:0 ? a:1 : '\v^\D*\zs\d+(\.\d+)+\ze' ), '\m\.'), 'str2nr(v:val)')
  121. endfunction " }}}2
  122. " Verify that the 'installed' version is at least the 'required' version.
  123. "
  124. " 'installed' and 'required' must be arrays. If they have different lengths,
  125. " the "missing" elements will be assumed to be 0 for the purposes of checking.
  126. "
  127. " See http://semver.org for info about version numbers.
  128. function! syntastic#util#versionIsAtLeast(installed, required) abort " {{{2
  129. return syntastic#util#compareLexi(a:installed, a:required) >= 0
  130. endfunction " }}}2
  131. " Almost lexicographic comparison of two lists of integers. :) If lists
  132. " have different lengths, the "missing" elements are assumed to be 0.
  133. function! syntastic#util#compareLexi(a, b) abort " {{{2
  134. for idx in range(max([len(a:a), len(a:b)]))
  135. let a_element = str2nr(get(a:a, idx, 0))
  136. let b_element = str2nr(get(a:b, idx, 0))
  137. if a_element != b_element
  138. return a_element > b_element ? 1 : -1
  139. endif
  140. endfor
  141. " still here, thus everything matched
  142. return 0
  143. endfunction " }}}2
  144. " strwidth() was added in Vim 7.3; if it doesn't exist, we use strlen()
  145. " and hope for the best :)
  146. let s:_width = function(exists('*strwidth') ? 'strwidth' : 'strlen')
  147. lockvar s:_width
  148. function! syntastic#util#screenWidth(str, tabstop) abort " {{{2
  149. let chunks = split(a:str, "\t", 1)
  150. let width = s:_width(chunks[-1])
  151. for c in chunks[:-2]
  152. let cwidth = s:_width(c)
  153. let width += cwidth + a:tabstop - cwidth % a:tabstop
  154. endfor
  155. return width
  156. endfunction " }}}2
  157. "print as much of a:msg as possible without "Press Enter" prompt appearing
  158. function! syntastic#util#wideMsg(msg) abort " {{{2
  159. let old_ruler = &ruler
  160. let old_showcmd = &showcmd
  161. "This is here because it is possible for some error messages to
  162. "begin with \n which will cause a "press enter" prompt.
  163. let msg = substitute(a:msg, "\n", '', 'g')
  164. "convert tabs to spaces so that the tabs count towards the window
  165. "width as the proper amount of characters
  166. let chunks = split(msg, "\t", 1)
  167. let msg = join(map(chunks[:-2], 'v:val . repeat(" ", &tabstop - s:_width(v:val) % &tabstop)'), '') . chunks[-1]
  168. let msg = strpart(msg, 0, &columns - 1)
  169. set noruler noshowcmd
  170. call syntastic#util#redraw(0)
  171. echo msg
  172. let &ruler = old_ruler
  173. let &showcmd = old_showcmd
  174. endfunction " }}}2
  175. " Check whether a buffer is loaded, listed, and not hidden
  176. function! syntastic#util#bufIsActive(buffer) abort " {{{2
  177. " convert to number, or hell breaks loose
  178. let buf = str2nr(a:buffer)
  179. if !bufloaded(buf) || !buflisted(buf)
  180. return 0
  181. endif
  182. " get rid of hidden buffers
  183. for tab in range(1, tabpagenr('$'))
  184. if index(tabpagebuflist(tab), buf) >= 0
  185. return 1
  186. endif
  187. endfor
  188. return 0
  189. endfunction " }}}2
  190. " start in directory a:where and walk up the parent folders until it
  191. " finds a file matching a:what; return path to that file
  192. function! syntastic#util#findInParent(what, where) abort " {{{2
  193. let here = fnamemodify(a:where, ':p')
  194. let root = syntastic#util#Slash()
  195. if syntastic#util#isRunningWindows() && here[1] ==# ':'
  196. " The drive letter is an ever-green source of fun. That's because
  197. " we don't care about running syntastic on Amiga these days. ;)
  198. let root = fnamemodify(root, ':p')
  199. let root = here[0] . root[1:]
  200. endif
  201. let old = ''
  202. while here !=# ''
  203. let p = split(globpath(here, a:what, 1), '\n')
  204. if !empty(p)
  205. return fnamemodify(p[0], ':p')
  206. elseif here ==? root || here ==? old
  207. break
  208. endif
  209. let old = here
  210. " we use ':h:h' rather than ':h' since ':p' adds a trailing '/'
  211. " if 'here' is a directory
  212. let here = fnamemodify(here, ':p:h:h')
  213. endwhile
  214. return ''
  215. endfunction " }}}2
  216. " Returns unique elements in a list
  217. function! syntastic#util#unique(list) abort " {{{2
  218. let seen = {}
  219. let uniques = []
  220. for e in a:list
  221. if !has_key(seen, e)
  222. let seen[e] = 1
  223. call add(uniques, e)
  224. endif
  225. endfor
  226. return uniques
  227. endfunction " }}}2
  228. " A less noisy shellescape()
  229. function! syntastic#util#shescape(string) abort " {{{2
  230. return a:string =~# '\m^[A-Za-z0-9_/.-]\+$' ? a:string : shellescape(a:string)
  231. endfunction " }}}2
  232. " A less noisy shellescape(expand())
  233. function! syntastic#util#shexpand(string, ...) abort " {{{2
  234. return syntastic#util#shescape(a:0 ? expand(a:string, a:1) : expand(a:string, 1))
  235. endfunction " }}}2
  236. " Escape arguments
  237. function! syntastic#util#argsescape(opt) abort " {{{2
  238. if type(a:opt) == type('') && a:opt !=# ''
  239. return [a:opt]
  240. elseif type(a:opt) == type([])
  241. return map(copy(a:opt), 'syntastic#util#shescape(v:val)')
  242. endif
  243. return []
  244. endfunction " }}}2
  245. " decode XML entities
  246. function! syntastic#util#decodeXMLEntities(string) abort " {{{2
  247. let str = a:string
  248. let str = substitute(str, '\m&lt;', '<', 'g')
  249. let str = substitute(str, '\m&gt;', '>', 'g')
  250. let str = substitute(str, '\m&quot;', '"', 'g')
  251. let str = substitute(str, '\m&apos;', "'", 'g')
  252. let str = substitute(str, '\m&amp;', '\&', 'g')
  253. return str
  254. endfunction " }}}2
  255. function! syntastic#util#redraw(full) abort " {{{2
  256. if a:full
  257. redraw!
  258. else
  259. redraw
  260. endif
  261. endfunction " }}}2
  262. function! syntastic#util#dictFilter(errors, filter) abort " {{{2
  263. let rules = s:_translateFilter(a:filter)
  264. " call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE, "applying filter:", rules)
  265. try
  266. call filter(a:errors, rules)
  267. catch /\m^Vim\%((\a\+)\)\=:E/
  268. let msg = matchstr(v:exception, '\m^Vim\%((\a\+)\)\=:\zs.*')
  269. call syntastic#log#error('quiet_messages: ' . msg)
  270. endtry
  271. endfunction " }}}2
  272. " Return a [seconds, fractions] list of strings, representing the
  273. " (hopefully high resolution) time since program start
  274. function! syntastic#util#stamp() abort " {{{2
  275. return split( split(reltimestr(reltime(g:_SYNTASTIC_START)))[0], '\.' )
  276. endfunction " }}}2
  277. " }}}1
  278. " Private functions {{{1
  279. function! s:_translateFilter(filters) abort " {{{2
  280. let conditions = []
  281. for k in keys(a:filters)
  282. if type(a:filters[k]) == type([])
  283. call extend(conditions, map(copy(a:filters[k]), 's:_translateElement(k, v:val)'))
  284. else
  285. call add(conditions, s:_translateElement(k, a:filters[k]))
  286. endif
  287. endfor
  288. if conditions == []
  289. let conditions = ['1']
  290. endif
  291. return len(conditions) == 1 ? conditions[0] : join(map(conditions, '"(" . v:val . ")"'), ' && ')
  292. endfunction " }}}2
  293. function! s:_translateElement(key, term) abort " {{{2
  294. let fkey = a:key
  295. if fkey[0] ==# '!'
  296. let fkey = fkey[1:]
  297. let not = 1
  298. else
  299. let not = 0
  300. endif
  301. if fkey ==? 'level'
  302. let op = not ? ' ==? ' : ' !=? '
  303. let ret = 'v:val["type"]' . op . string(a:term[0])
  304. elseif fkey ==? 'type'
  305. if a:term ==? 'style'
  306. let op = not ? ' ==? ' : ' !=? '
  307. let ret = 'get(v:val, "subtype", "")' . op . '"style"'
  308. else
  309. let op = not ? '!' : ''
  310. let ret = op . 'has_key(v:val, "subtype")'
  311. endif
  312. elseif fkey ==? 'regex'
  313. let op = not ? ' =~? ' : ' !~? '
  314. let ret = 'v:val["text"]' . op . string(a:term)
  315. elseif fkey ==? 'file' || fkey[:4] ==? 'file:'
  316. let op = not ? ' =~# ' : ' !~# '
  317. let ret = 'bufname(str2nr(v:val["bufnr"]))'
  318. let mod = fkey[4:]
  319. if mod !=# ''
  320. let ret = 'fnamemodify(' . ret . ', ' . string(mod) . ')'
  321. endif
  322. let ret .= op . string(a:term)
  323. else
  324. call syntastic#log#warn('quiet_messages: ignoring invalid key ' . strtrans(string(fkey)))
  325. let ret = '1'
  326. endif
  327. return ret
  328. endfunction " }}}2
  329. function! s:_rmrf(what) abort " {{{2
  330. if !exists('s:rmdir')
  331. let s:rmdir = syntastic#util#shescape(get(g:, 'netrw_localrmdir', 'rmdir'))
  332. endif
  333. if getftype(a:what) ==# 'dir'
  334. if filewritable(a:what) != 2
  335. return
  336. endif
  337. for f in split(globpath(a:what, '*', 1), "\n")
  338. call s:_rmrf(f)
  339. endfor
  340. silent! call syntastic#util#system(s:rmdir . ' ' . syntastic#util#shescape(a:what))
  341. else
  342. silent! call delete(a:what)
  343. endif
  344. endfunction " }}}2
  345. " }}}1
  346. let &cpo = s:save_cpo
  347. unlet s:save_cpo
  348. " vim: set sw=4 sts=4 et fdm=marker: