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