neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

debchangelog.vim (11986B)


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