1
0

loclist.vim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. if exists('g:loaded_syntastic_loclist') || !exists('g:loaded_syntastic_plugin')
  2. finish
  3. endif
  4. let g:loaded_syntastic_loclist = 1
  5. let g:SyntasticLoclist = {}
  6. " Public methods {{{1
  7. function! g:SyntasticLoclist.New(rawLoclist) abort " {{{2
  8. let newObj = copy(self)
  9. let llist = filter(copy(a:rawLoclist), 'v:val["valid"] == 1')
  10. for e in llist
  11. if get(e, 'type', '') ==# ''
  12. let e['type'] = 'E'
  13. endif
  14. endfor
  15. let newObj._rawLoclist = llist
  16. let newObj._name = ''
  17. let newObj._owner = bufnr('')
  18. let newObj._sorted = 0
  19. let newObj._columns = g:syntastic_cursor_columns
  20. return newObj
  21. endfunction " }}}2
  22. function! g:SyntasticLoclist.current() abort " {{{2
  23. if !exists('b:syntastic_loclist') || empty(b:syntastic_loclist)
  24. let b:syntastic_loclist = g:SyntasticLoclist.New([])
  25. endif
  26. return b:syntastic_loclist
  27. endfunction " }}}2
  28. function! g:SyntasticLoclist.extend(other) abort " {{{2
  29. let list = self.copyRaw()
  30. call extend(list, a:other.copyRaw())
  31. return g:SyntasticLoclist.New(list)
  32. endfunction " }}}2
  33. function! g:SyntasticLoclist.sort() abort " {{{2
  34. if !self._sorted
  35. for e in self._rawLoclist
  36. call s:_set_screen_column(e)
  37. endfor
  38. call sort(self._rawLoclist, self._columns ? 's:_compare_error_items_by_columns' : 's:_compare_error_items_by_lines')
  39. let self._sorted = 1
  40. endif
  41. endfunction " }}}2
  42. function! g:SyntasticLoclist.isEmpty() abort " {{{2
  43. return empty(self._rawLoclist)
  44. endfunction " }}}2
  45. function! g:SyntasticLoclist.isNewerThan(stamp) abort " {{{2
  46. if !exists('self._stamp')
  47. let self._stamp = []
  48. return 0
  49. endif
  50. return syntastic#util#compareLexi(self._stamp, a:stamp) > 0
  51. endfunction " }}}2
  52. function! g:SyntasticLoclist.copyRaw() abort " {{{2
  53. return copy(self._rawLoclist)
  54. endfunction " }}}2
  55. function! g:SyntasticLoclist.getRaw() abort " {{{2
  56. return self._rawLoclist
  57. endfunction " }}}2
  58. function! g:SyntasticLoclist.getBuffers() abort " {{{2
  59. return syntastic#util#unique(map(copy(self._rawLoclist), 'str2nr(v:val["bufnr"])') + [self._owner])
  60. endfunction " }}}2
  61. function! g:SyntasticLoclist.getCursorColumns() abort " {{{2
  62. return self._columns
  63. endfunction " }}}2
  64. function! g:SyntasticLoclist.getStatuslineFlag() abort " {{{2
  65. if !exists('self._stl_format')
  66. let self._stl_format = ''
  67. endif
  68. if !exists('self._stl_flag')
  69. let self._stl_flag = ''
  70. endif
  71. if g:syntastic_stl_format !=# self._stl_format
  72. let self._stl_format = g:syntastic_stl_format
  73. if !empty(self._rawLoclist)
  74. let errors = self.errors()
  75. let warnings = self.warnings()
  76. let num_errors = len(errors)
  77. let num_warnings = len(warnings)
  78. let num_issues = len(self._rawLoclist)
  79. let output = self._stl_format
  80. "hide stuff wrapped in %E(...) unless there are errors
  81. let output = substitute(output, '\m\C%E{\([^}]*\)}', num_errors ? '\1' : '' , 'g')
  82. "hide stuff wrapped in %W(...) unless there are warnings
  83. let output = substitute(output, '\m\C%W{\([^}]*\)}', num_warnings ? '\1' : '' , 'g')
  84. "hide stuff wrapped in %B(...) unless there are both errors and warnings
  85. let output = substitute(output, '\m\C%B{\([^}]*\)}', (num_warnings && num_errors) ? '\1' : '' , 'g')
  86. "sub in the total errors/warnings/both
  87. let output = substitute(output, '\m\C%w', num_warnings, 'g')
  88. let output = substitute(output, '\m\C%e', num_errors, 'g')
  89. let output = substitute(output, '\m\C%t', num_issues, 'g')
  90. "first error/warning line num
  91. let output = substitute(output, '\m\C%F', num_issues ? self._rawLoclist[0]['lnum'] : '', 'g')
  92. "first error line num
  93. let output = substitute(output, '\m\C%fe', num_errors ? errors[0]['lnum'] : '', 'g')
  94. "first warning line num
  95. let output = substitute(output, '\m\C%fw', num_warnings ? warnings[0]['lnum'] : '', 'g')
  96. let self._stl_flag = output
  97. else
  98. let self._stl_flag = ''
  99. endif
  100. endif
  101. return self._stl_flag
  102. endfunction " }}}2
  103. function! g:SyntasticLoclist.getFirstError(...) abort " {{{2
  104. let max_issues = len(self._rawLoclist)
  105. if a:0 && a:1 < max_issues
  106. let max_issues = a:1
  107. endif
  108. for idx in range(max_issues)
  109. if get(self._rawLoclist[idx], 'type', '') ==? 'E'
  110. return idx + 1
  111. endif
  112. endfor
  113. return 0
  114. endfunction " }}}2
  115. function! g:SyntasticLoclist.getName() abort " {{{2
  116. return len(self._name)
  117. endfunction " }}}2
  118. function! g:SyntasticLoclist.setName(name) abort " {{{2
  119. let self._name = a:name
  120. endfunction " }}}2
  121. function! g:SyntasticLoclist.getOwner() abort " {{{2
  122. return self._owner
  123. endfunction " }}}2
  124. function! g:SyntasticLoclist.setOwner(buffer) abort " {{{2
  125. let self._owner = type(a:buffer) == type(0) ? a:buffer : str2nr(a:buffer)
  126. endfunction " }}}2
  127. function! g:SyntasticLoclist.deploy() abort " {{{2
  128. call self.setOwner(bufnr(''))
  129. let self._stamp = syntastic#util#stamp()
  130. for buf in self.getBuffers()
  131. call setbufvar(buf, 'syntastic_loclist', self)
  132. endfor
  133. endfunction " }}}2
  134. function! g:SyntasticLoclist.destroy() abort " {{{2
  135. for buf in self.getBuffers()
  136. call setbufvar(buf, 'syntastic_loclist', {})
  137. endfor
  138. endfunction " }}}2
  139. function! g:SyntasticLoclist.decorate(tag) abort " {{{2
  140. for e in self._rawLoclist
  141. let e['text'] .= ' [' . a:tag . ']'
  142. endfor
  143. endfunction " }}}2
  144. function! g:SyntasticLoclist.balloons() abort " {{{2
  145. if !exists('self._cachedBalloons')
  146. let sep = has('balloon_multiline') ? "\n" : ' | '
  147. let self._cachedBalloons = {}
  148. for e in self._rawLoclist
  149. let buf = e['bufnr']
  150. if !has_key(self._cachedBalloons, buf)
  151. let self._cachedBalloons[buf] = {}
  152. endif
  153. if has_key(self._cachedBalloons[buf], e['lnum'])
  154. let self._cachedBalloons[buf][e['lnum']] .= sep . e['text']
  155. else
  156. let self._cachedBalloons[buf][e['lnum']] = e['text']
  157. endif
  158. endfor
  159. endif
  160. return get(self._cachedBalloons, bufnr(''), {})
  161. endfunction " }}}2
  162. function! g:SyntasticLoclist.errors() abort " {{{2
  163. if !exists('self._cachedErrors')
  164. let self._cachedErrors = self.filter({'type': 'E'})
  165. endif
  166. return self._cachedErrors
  167. endfunction " }}}2
  168. function! g:SyntasticLoclist.warnings() abort " {{{2
  169. if !exists('self._cachedWarnings')
  170. let self._cachedWarnings = self.filter({'type': 'W'})
  171. endif
  172. return self._cachedWarnings
  173. endfunction " }}}2
  174. " Legacy function. Syntastic no longer calls it, but we keep it
  175. " around because other plugins (f.i. powerline) depend on it.
  176. function! g:SyntasticLoclist.hasErrorsOrWarningsToDisplay() abort " {{{2
  177. return !self.isEmpty()
  178. endfunction " }}}2
  179. " cache used by EchoCurrentError()
  180. function! g:SyntasticLoclist.messages(buf) abort " {{{2
  181. if !exists('self._cachedMessages')
  182. let self._cachedMessages = {}
  183. let errors = self.errors() + self.warnings()
  184. for e in errors
  185. let b = e['bufnr']
  186. let l = e['lnum']
  187. if !has_key(self._cachedMessages, b)
  188. let self._cachedMessages[b] = {}
  189. endif
  190. if !has_key(self._cachedMessages[b], l)
  191. let self._cachedMessages[b][l] = [e]
  192. elseif self._columns
  193. call add(self._cachedMessages[b][l], e)
  194. endif
  195. endfor
  196. if self._columns
  197. if !self._sorted
  198. for b in keys(self._cachedMessages)
  199. for l in keys(self._cachedMessages[b])
  200. if len(self._cachedMessages[b][l]) > 1
  201. for e in self._cachedMessages[b][l]
  202. call s:_set_screen_column(e)
  203. endfor
  204. call sort(self._cachedMessages[b][l], 's:_compare_error_items_by_columns')
  205. endif
  206. endfor
  207. endfor
  208. endif
  209. for b in keys(self._cachedMessages)
  210. for l in keys(self._cachedMessages[b])
  211. call s:_remove_shadowed_items(self._cachedMessages[b][l])
  212. endfor
  213. endfor
  214. endif
  215. endif
  216. return get(self._cachedMessages, a:buf, {})
  217. endfunction " }}}2
  218. "Filter the list and return new native loclist
  219. "e.g.
  220. " .filter({'bufnr': 10, 'type': 'e'})
  221. "
  222. "would return all errors for buffer 10.
  223. "
  224. "Note that all comparisons are done with ==?
  225. function! g:SyntasticLoclist.filter(filters) abort " {{{2
  226. let conditions = values(map(copy(a:filters), 's:_translate(v:key, v:val)'))
  227. let filter = len(conditions) == 1 ?
  228. \ conditions[0] : join(map(conditions, '"(" . v:val . ")"'), ' && ')
  229. return filter(copy(self._rawLoclist), filter)
  230. endfunction " }}}2
  231. function! g:SyntasticLoclist.setloclist() abort " {{{2
  232. if !exists('w:syntastic_loclist_set')
  233. let w:syntastic_loclist_set = 0
  234. endif
  235. let replace = g:syntastic_reuse_loc_lists && w:syntastic_loclist_set
  236. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_NOTIFICATIONS, 'loclist: setloclist ' . (replace ? '(replace)' : '(new)'))
  237. call setloclist(0, self.getRaw(), replace ? 'r' : ' ')
  238. let w:syntastic_loclist_set = 1
  239. endfunction " }}}2
  240. "display the cached errors for this buf in the location list
  241. function! g:SyntasticLoclist.show() abort " {{{2
  242. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_NOTIFICATIONS, 'loclist: show')
  243. call self.setloclist()
  244. if !self.isEmpty()
  245. let num = winnr()
  246. execute 'lopen ' . syntastic#util#var('loc_list_height')
  247. if num != winnr()
  248. execute num . 'wincmd w'
  249. endif
  250. " try to find the loclist window and set w:quickfix_title
  251. let errors = getloclist(0)
  252. for buf in tabpagebuflist()
  253. if buflisted(buf) && bufloaded(buf) && getbufvar(buf, '&buftype') ==# 'quickfix'
  254. let win = bufwinnr(buf)
  255. let title = getwinvar(win, 'quickfix_title')
  256. " TODO: try to make sure we actually own this window; sadly,
  257. " errors == getloclist(0) is the only somewhat safe way to
  258. " achieve that
  259. if strpart(title, 0, 16) ==# ':SyntasticCheck ' ||
  260. \ ( (title ==# '' || title ==# ':setloclist()') && errors == getloclist(0) )
  261. call setwinvar(win, 'quickfix_title', ':SyntasticCheck ' . self._name)
  262. call setbufvar(buf, 'syntastic_owner_buffer', self._owner)
  263. endif
  264. endif
  265. endfor
  266. endif
  267. endfunction " }}}2
  268. " }}}1
  269. " Public functions {{{1
  270. function! SyntasticLoclistHide() abort " {{{2
  271. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_NOTIFICATIONS, 'loclist: hide')
  272. silent! lclose
  273. endfunction " }}}2
  274. " }}}1
  275. " Utilities {{{1
  276. function! s:_translate(key, val) abort " {{{2
  277. return 'get(v:val, ' . string(a:key) . ', "") ==? ' . string(a:val)
  278. endfunction " }}}2
  279. function! s:_set_screen_column(item) abort " {{{2
  280. if !has_key(a:item, 'scol')
  281. let col = get(a:item, 'col', 0)
  282. if col != 0 && get(a:item, 'vcol', 0) == 0
  283. let buf = str2nr(a:item['bufnr'])
  284. try
  285. let line = getbufline(buf, a:item['lnum'])[0]
  286. catch /\m^Vim\%((\a\+)\)\=:E684/
  287. let line = ''
  288. endtry
  289. let a:item['scol'] = syntastic#util#screenWidth(strpart(line, 0, col), getbufvar(buf, '&tabstop'))
  290. else
  291. let a:item['scol'] = col
  292. endif
  293. endif
  294. endfunction " }}}2
  295. function! s:_remove_shadowed_items(errors) abort " {{{2
  296. " keep only the first message at a given column
  297. let i = 0
  298. while i < len(a:errors) - 1
  299. let j = i + 1
  300. let dupes = 0
  301. while j < len(a:errors) && a:errors[j].scol == a:errors[i].scol
  302. let dupes = 1
  303. let j += 1
  304. endwhile
  305. if dupes
  306. call remove(a:errors, i + 1, j - 1)
  307. endif
  308. let i += 1
  309. endwhile
  310. " merge messages with the same text
  311. let i = 0
  312. while i < len(a:errors) - 1
  313. let j = i + 1
  314. let dupes = 0
  315. while j < len(a:errors) && a:errors[j].text == a:errors[i].text
  316. let dupes = 1
  317. let j += 1
  318. endwhile
  319. if dupes
  320. call remove(a:errors, i + 1, j - 1)
  321. endif
  322. let i += 1
  323. endwhile
  324. endfunction " }}}2
  325. function! s:_compare_error_items_by_columns(a, b) abort " {{{2
  326. if a:a['bufnr'] != a:b['bufnr']
  327. " group by file
  328. return a:a['bufnr'] - a:b['bufnr']
  329. elseif a:a['lnum'] != a:b['lnum']
  330. " sort by line
  331. return a:a['lnum'] - a:b['lnum']
  332. elseif a:a['scol'] != a:b['scol']
  333. " sort by screen column
  334. return a:a['scol'] - a:b['scol']
  335. elseif a:a['type'] !=? a:b['type']
  336. " errors take precedence over warnings
  337. return a:a['type'] ==? 'E' ? -1 : 1
  338. else
  339. return 0
  340. endif
  341. endfunction " }}}2
  342. function! s:_compare_error_items_by_lines(a, b) abort " {{{2
  343. if a:a['bufnr'] != a:b['bufnr']
  344. " group by file
  345. return a:a['bufnr'] - a:b['bufnr']
  346. elseif a:a['lnum'] != a:b['lnum']
  347. " sort by line
  348. return a:a['lnum'] - a:b['lnum']
  349. elseif a:a['type'] !=? a:b['type']
  350. " errors take precedence over warnings
  351. return a:a['type'] ==? 'E' ? -1 : 1
  352. else
  353. " sort by screen column
  354. return a:a['scol'] - a:b['scol']
  355. endif
  356. endfunction " }}}2
  357. " }}}1
  358. " vim: set sw=4 sts=4 et fdm=marker: