debchangelog.vim 12 KB

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