debchangelog.vim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. " Vim filetype plugin file (GUI menu, folding and completion)
  2. " Language: Debian Changelog
  3. " Maintainer: Debian Vim Maintainers
  4. " Former Maintainers: Michael Piefel <piefel@informatik.hu-berlin.de>
  5. " Stefano Zacchiroli <zack@debian.org>
  6. " Last Change: 2018-01-28
  7. " License: Vim License
  8. " URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/ftplugin/debchangelog.vim
  9. " Bug completion requires apt-listbugs installed for Debian packages or
  10. " python-launchpadlib installed for Ubuntu packages
  11. if exists('b:did_ftplugin')
  12. finish
  13. endif
  14. let b:did_ftplugin=1
  15. " {{{1 Local settings (do on every load)
  16. if exists('g:debchangelog_fold_enable')
  17. setlocal foldmethod=expr
  18. setlocal foldexpr=DebGetChangelogFold(v:lnum)
  19. setlocal foldtext=DebChangelogFoldText()
  20. endif
  21. " Debian changelogs are not supposed to have any other text width,
  22. " so the user cannot override this setting
  23. setlocal tw=78
  24. setlocal comments=f:*
  25. " Clean unloading
  26. let b:undo_ftplugin = 'setlocal tw< comments< foldmethod< foldexpr< foldtext<'
  27. " }}}1
  28. if exists('g:did_changelog_ftplugin')
  29. finish
  30. endif
  31. " Don't load another plugin (this is global)
  32. let g:did_changelog_ftplugin = 1
  33. " {{{1 GUI menu
  34. " Helper functions returning various data.
  35. " Returns full name, either from $DEBFULLNAME or debianfullname.
  36. " TODO Is there a way to determine name from anywhere else?
  37. function <SID>FullName()
  38. if exists('$DEBFULLNAME')
  39. return $DEBFULLNAME
  40. elseif exists('g:debianfullname')
  41. return g:debianfullname
  42. else
  43. return 'Your Name'
  44. endif
  45. endfunction
  46. " Returns email address, from $DEBEMAIL, $EMAIL or debianemail.
  47. function <SID>Email()
  48. if exists('$DEBEMAIL')
  49. return $DEBEMAIL
  50. elseif exists('$EMAIL')
  51. return $EMAIL
  52. elseif exists('g:debianemail')
  53. return g:debianemail
  54. else
  55. return 'your@email.address'
  56. endif
  57. endfunction
  58. " Returns date in RFC822 format.
  59. function <SID>Date()
  60. let savelang = v:lc_time
  61. execute 'language time C'
  62. let dateandtime = strftime('%a, %d %b %Y %X %z')
  63. execute 'language time ' . savelang
  64. return dateandtime
  65. endfunction
  66. function <SID>WarnIfNotUnfinalised()
  67. if match(getline('.'), ' -- [[:alpha:]][[:alnum:].]')!=-1
  68. echohl WarningMsg
  69. echo 'The entry has not been unfinalised before editing.'
  70. echohl None
  71. return 1
  72. endif
  73. return 0
  74. endfunction
  75. function <SID>Finalised()
  76. let savelinenum = line('.')
  77. 1
  78. call search('^ -- ')
  79. if match(getline('.'), ' -- [[:alpha:]][[:alnum:].]')!=-1
  80. let returnvalue = 1
  81. else
  82. let returnvalue = 0
  83. endif
  84. execute savelinenum
  85. return returnvalue
  86. endfunction
  87. " These functions implement the menus
  88. function NewVersion()
  89. " The new entry is unfinalised and shall be changed
  90. amenu disable Changelog.New\ Version
  91. amenu enable Changelog.Add\ Entry
  92. amenu enable Changelog.Close\ Bug
  93. amenu enable Changelog.Set\ Distribution
  94. amenu enable Changelog.Set\ Urgency
  95. amenu disable Changelog.Unfinalise
  96. amenu enable Changelog.Finalise
  97. call append(0, substitute(getline(1), '-\([[:digit:]]\+\))', '-$$\1)', ''))
  98. call append(1, '')
  99. call append(2, '')
  100. call append(3, ' -- ')
  101. call append(4, '')
  102. call Urgency('low')
  103. normal! 1G0
  104. call search(')')
  105. normal! h
  106. normal! 
  107. call setline(1, substitute(getline(1), '-\$\$', '-', ''))
  108. if exists('g:debchangelog_fold_enable')
  109. foldopen
  110. endif
  111. call AddEntry()
  112. endfunction
  113. function AddEntry()
  114. 1
  115. call search('^ -- ')
  116. .-2
  117. call append('.', ' * ')
  118. .+3
  119. let warn=<SID>WarnIfNotUnfinalised()
  120. .-2
  121. if warn
  122. echohl MoreMsg
  123. call input('Hit ENTER')
  124. echohl None
  125. endif
  126. startinsert!
  127. endfunction
  128. function CloseBug()
  129. 1
  130. call search('^ -- ')
  131. let warn=<SID>WarnIfNotUnfinalised()
  132. .-2
  133. call append('.', ' * (closes: #' . input('Bug number to close: ') . ')')
  134. normal! j^ll
  135. startinsert
  136. endfunction
  137. function Distribution(dist)
  138. call setline(1, substitute(getline(1), ') *\%(UNRELEASED\|\l\+\);', ') ' . a:dist . ';', ''))
  139. endfunction
  140. function Urgency(urg)
  141. call setline(1, substitute(getline(1), 'urgency=.*$', 'urgency=' . a:urg, ''))
  142. endfunction
  143. function <SID>UnfinaliseMenu()
  144. " This means the entry shall be changed
  145. amenu disable Changelog.New\ Version
  146. amenu enable Changelog.Add\ Entry
  147. amenu enable Changelog.Close\ Bug
  148. amenu enable Changelog.Set\ Distribution
  149. amenu enable Changelog.Set\ Urgency
  150. amenu disable Changelog.Unfinalise
  151. amenu enable Changelog.Finalise
  152. endfunction
  153. function Unfinalise()
  154. call <SID>UnfinaliseMenu()
  155. 1
  156. call search('^ -- ')
  157. call setline('.', ' -- ')
  158. endfunction
  159. function <SID>FinaliseMenu()
  160. " This means the entry should not be changed anymore
  161. amenu enable Changelog.New\ Version
  162. amenu disable Changelog.Add\ Entry
  163. amenu disable Changelog.Close\ Bug
  164. amenu disable Changelog.Set\ Distribution
  165. amenu disable Changelog.Set\ Urgency
  166. amenu enable Changelog.Unfinalise
  167. amenu disable Changelog.Finalise
  168. endfunction
  169. function Finalise()
  170. call <SID>FinaliseMenu()
  171. 1
  172. call search('^ -- ')
  173. call setline('.', ' -- ' . <SID>FullName() . ' <' . <SID>Email() . '> ' . <SID>Date())
  174. endfunction
  175. function <SID>MakeMenu()
  176. amenu &Changelog.&New\ Version :call NewVersion()<CR>
  177. amenu Changelog.&Add\ Entry :call AddEntry()<CR>
  178. amenu Changelog.&Close\ Bug :call CloseBug()<CR>
  179. menu Changelog.-sep- <nul>
  180. amenu Changelog.Set\ &Distribution.&unstable :call Distribution("unstable")<CR>
  181. amenu Changelog.Set\ Distribution.&frozen :call Distribution("frozen")<CR>
  182. amenu Changelog.Set\ Distribution.&stable :call Distribution("stable")<CR>
  183. menu Changelog.Set\ Distribution.-sep- <nul>
  184. amenu Changelog.Set\ Distribution.frozen\ unstable :call Distribution("frozen unstable")<CR>
  185. amenu Changelog.Set\ Distribution.stable\ unstable :call Distribution("stable unstable")<CR>
  186. amenu Changelog.Set\ Distribution.stable\ frozen :call Distribution("stable frozen")<CR>
  187. amenu Changelog.Set\ Distribution.stable\ frozen\ unstable :call Distribution("stable frozen unstable")<CR>
  188. amenu Changelog.Set\ &Urgency.&low :call Urgency("low")<CR>
  189. amenu Changelog.Set\ Urgency.&medium :call Urgency("medium")<CR>
  190. amenu Changelog.Set\ Urgency.&high :call Urgency("high")<CR>
  191. menu Changelog.-sep- <nul>
  192. amenu Changelog.U&nfinalise :call Unfinalise()<CR>
  193. amenu Changelog.&Finalise :call Finalise()<CR>
  194. if <SID>Finalised()
  195. call <SID>FinaliseMenu()
  196. else
  197. call <SID>UnfinaliseMenu()
  198. endif
  199. endfunction
  200. augroup changelogMenu
  201. au BufEnter * if &filetype == "debchangelog" | call <SID>MakeMenu() | endif
  202. au BufLeave * if &filetype == "debchangelog" | silent! aunmenu Changelog | endif
  203. augroup END
  204. " }}}
  205. " {{{1 folding
  206. " look for an author name in the [zonestart zoneend] lines searching backward
  207. function! s:getAuthor(zonestart, zoneend)
  208. let linepos = a:zoneend
  209. while linepos >= a:zonestart
  210. let line = getline(linepos)
  211. if line =~# '^ --'
  212. return substitute(line, '^ --\s*\([^<]\+\)\s*.*', '\1', '')
  213. endif
  214. let linepos -= 1
  215. endwhile
  216. return '[unknown]'
  217. endfunction
  218. " Look for a package source name searching backward from the givenline and
  219. " returns it. Return the empty string if the package name can't be found
  220. function! DebGetPkgSrcName(lineno)
  221. let lineidx = a:lineno
  222. let pkgname = ''
  223. while lineidx > 0
  224. let curline = getline(lineidx)
  225. if curline =~# '^\S'
  226. let pkgname = matchlist(curline, '^\(\S\+\).*$')[1]
  227. break
  228. endif
  229. let lineidx = lineidx - 1
  230. endwhile
  231. return pkgname
  232. endfunction
  233. function! DebChangelogFoldText()
  234. if v:folddashes ==# '-' " changelog entry fold
  235. return foldtext() . ' -- ' . s:getAuthor(v:foldstart, v:foldend) . ' '
  236. endif
  237. return foldtext()
  238. endfunction
  239. function! DebGetChangelogFold(lnum)
  240. let line = getline(a:lnum)
  241. if line =~# '^\w\+'
  242. return '>1' " beginning of a changelog entry
  243. endif
  244. if line =~# '^\s\+\[.*\]'
  245. return '>2' " beginning of an author-specific chunk
  246. endif
  247. if line =~# '^ --'
  248. return '1'
  249. endif
  250. return '='
  251. endfunction
  252. if exists('g:debchangelog_fold_enable')
  253. silent! foldopen! " unfold the entry the cursor is on (usually the first one)
  254. endif
  255. " }}}
  256. " {{{1 omnicompletion for Closes: #
  257. if !exists('g:debchangelog_listbugs_severities')
  258. let g:debchangelog_listbugs_severities = 'critical,grave,serious,important,normal,minor,wishlist'
  259. endif
  260. fun! DebCompleteBugs(findstart, base)
  261. if a:findstart
  262. let line = getline('.')
  263. " try to detect whether this is closes: or lp:
  264. let g:debchangelog_complete_mode = 'debbugs'
  265. let try_colidx = col('.') - 1
  266. let colidx = -1 " default to no-completion-possible
  267. while try_colidx > 0 && line[try_colidx - 1] =~# '\s\|\d\|#\|,\|:'
  268. let try_colidx = try_colidx - 1
  269. if line[try_colidx] ==# '#' && colidx == -1
  270. " found hash, where we complete from:
  271. let colidx = try_colidx
  272. elseif line[try_colidx] ==# ':'
  273. if try_colidx > 1 && strpart(line, try_colidx - 2, 3) =~? '\clp:'
  274. let g:debchangelog_complete_mode = 'lp'
  275. endif
  276. break
  277. endif
  278. endwhile
  279. return colidx
  280. else " return matches:
  281. let bug_lines = []
  282. if g:debchangelog_complete_mode ==? 'lp'
  283. if ! has('python')
  284. echoerr 'vim must be built with Python support to use LP bug completion'
  285. return
  286. endif
  287. let pkgsrc = DebGetPkgSrcName(line('.'))
  288. python << EOF
  289. import vim
  290. try:
  291. from launchpadlib.launchpad import Launchpad
  292. from lazr.restfulclient.errors import HTTPError
  293. # login anonymously
  294. lp = Launchpad.login_anonymously('debchangelog.vim', 'production')
  295. ubuntu = lp.distributions['ubuntu']
  296. try:
  297. sp = ubuntu.getSourcePackage(name=vim.eval('pkgsrc'))
  298. status = ('New', 'Incomplete', 'Confirmed', 'Triaged',
  299. 'In Progress', 'Fix Committed')
  300. tasklist = sp.searchTasks(status=status, order_by='id')
  301. liststr = '['
  302. for task in tasklist:
  303. bug = task.bug
  304. liststr += "'#%d - %s'," % (bug.id, bug.title.replace('\'', '\'\''))
  305. liststr += ']'
  306. vim.command('silent let bug_lines = %s' % liststr.encode('utf-8'))
  307. except HTTPError:
  308. pass
  309. except ImportError:
  310. vim.command('echoerr \'python-launchpadlib >= 1.5.4 needs to be installed to use Launchpad bug completion\'')
  311. EOF
  312. else
  313. if ! filereadable('/usr/sbin/apt-listbugs')
  314. echoerr 'apt-listbugs not found, you should install it to use Closes bug completion'
  315. return
  316. endif
  317. let pkgsrc = DebGetPkgSrcName(line('.'))
  318. let listbugs_output = system('/usr/sbin/apt-listbugs -s ' . g:debchangelog_listbugs_severities . ' list ' . pkgsrc . ' | grep "^ #" 2> /dev/null')
  319. let bug_lines = split(listbugs_output, '\n')
  320. endif
  321. let completions = []
  322. for line in bug_lines
  323. let parts = matchlist(line, '^\s*\(#\S\+\)\s*-\s*\(.*\)$')
  324. " filter only those which match a:base:
  325. if parts[1] !~ '^' . a:base
  326. continue
  327. endif
  328. let completion = {}
  329. let completion['word'] = parts[1]
  330. let completion['menu'] = parts[2]
  331. let completion['info'] = parts[0]
  332. let completions += [completion]
  333. endfor
  334. return completions
  335. endif
  336. endfun
  337. setlocal omnifunc=DebCompleteBugs
  338. " }}}
  339. " vim: set foldmethod=marker: