1071d4279SBram Moolenaar" Vim filetype plugin file
2071d4279SBram Moolenaar" Language:             generic Changelog file
3d042dc82SBram Moolenaar" Maintainer:           Martin Florian <[email protected]>
4d042dc82SBram Moolenaar" Previous Maintainer:  Nikolai Weibull <[email protected]>
5*079ba76aSBram Moolenaar" Latest Revision:      2021-10-17
6071d4279SBram Moolenaar" Variables:
757657d85SBram Moolenaar"   g:changelog_timeformat (deprecated: use g:changelog_dateformat instead) -
8071d4279SBram Moolenaar"       description: the timeformat used in ChangeLog entries.
9071d4279SBram Moolenaar"       default: "%Y-%m-%d".
1057657d85SBram Moolenaar"   g:changelog_dateformat -
1157657d85SBram Moolenaar"       description: the format sent to strftime() to generate a date string.
1257657d85SBram Moolenaar"       default: "%Y-%m-%d".
13071d4279SBram Moolenaar"   g:changelog_username -
14071d4279SBram Moolenaar"       description: the username to use in ChangeLog entries
15071d4279SBram Moolenaar"       default: try to deduce it from environment variables and system files.
16071d4279SBram Moolenaar" Local Mappings:
17071d4279SBram Moolenaar"   <Leader>o -
18071d4279SBram Moolenaar"       adds a new changelog entry for the current user for the current date.
19071d4279SBram Moolenaar" Global Mappings:
20071d4279SBram Moolenaar"   <Leader>o -
21071d4279SBram Moolenaar"       switches to the ChangeLog buffer opened for the current directory, or
22071d4279SBram Moolenaar"       opens it in a new buffer if it exists in the current directory.  Then
23071d4279SBram Moolenaar"       it does the same as the local <Leader>o described above.
24071d4279SBram Moolenaar" Notes:
25071d4279SBram Moolenaar"   run 'runtime ftplugin/changelog.vim' to enable the global mapping for
26071d4279SBram Moolenaar"   changelog files.
27071d4279SBram Moolenaar" TODO:
28071d4279SBram Moolenaar"  should we perhaps open the ChangeLog file even if it doesn't exist already?
29071d4279SBram Moolenaar"  Problem is that you might end up with ChangeLog files all over the place.
30071d4279SBram Moolenaar
31071d4279SBram Moolenaar" If 'filetype' isn't "changelog", we must have been to add ChangeLog opener
3257657d85SBram Moolenaarif &filetype == 'changelog'
3357657d85SBram Moolenaar  if exists('b:did_ftplugin')
34071d4279SBram Moolenaar    finish
35071d4279SBram Moolenaar  endif
36071d4279SBram Moolenaar  let b:did_ftplugin = 1
37071d4279SBram Moolenaar
3842eeac35SBram Moolenaar  let s:cpo_save = &cpo
3942eeac35SBram Moolenaar  set cpo&vim
40071d4279SBram Moolenaar
4157657d85SBram Moolenaar  " Set up the format used for dates.
4257657d85SBram Moolenaar  if !exists('g:changelog_dateformat')
4357657d85SBram Moolenaar    if exists('g:changelog_timeformat')
4457657d85SBram Moolenaar      let g:changelog_dateformat = g:changelog_timeformat
4557657d85SBram Moolenaar    else
4657657d85SBram Moolenaar      let g:changelog_dateformat = "%Y-%m-%d"
4757657d85SBram Moolenaar    endif
48071d4279SBram Moolenaar  endif
49071d4279SBram Moolenaar
505c73622aSBram Moolenaar  function! s:username()
515c73622aSBram Moolenaar    if exists('g:changelog_username')
525c73622aSBram Moolenaar      return g:changelog_username
535c73622aSBram Moolenaar    elseif $EMAIL != ""
545c73622aSBram Moolenaar      return $EMAIL
555c73622aSBram Moolenaar    elseif $EMAIL_ADDRESS != ""
565c73622aSBram Moolenaar      return $EMAIL_ADDRESS
57071d4279SBram Moolenaar    endif
58071d4279SBram Moolenaar
595c73622aSBram Moolenaar    let login = s:login()
605c73622aSBram Moolenaar    return printf('%s <%s@%s>', s:name(login), login, s:hostname())
615c73622aSBram Moolenaar  endfunction
625c73622aSBram Moolenaar
635c73622aSBram Moolenaar  function! s:login()
645c73622aSBram Moolenaar    return s:trimmed_system_with_default('whoami', 'unknown')
655c73622aSBram Moolenaar  endfunction
665c73622aSBram Moolenaar
675c73622aSBram Moolenaar  function! s:trimmed_system_with_default(command, default)
685c73622aSBram Moolenaar    return s:first_line(s:system_with_default(a:command, a:default))
695c73622aSBram Moolenaar  endfunction
705c73622aSBram Moolenaar
715c73622aSBram Moolenaar  function! s:system_with_default(command, default)
725c73622aSBram Moolenaar    let output = system(a:command)
735c73622aSBram Moolenaar    if v:shell_error
745c73622aSBram Moolenaar      return default
75071d4279SBram Moolenaar    endif
765c73622aSBram Moolenaar    return output
775c73622aSBram Moolenaar  endfunction
785c73622aSBram Moolenaar
795c73622aSBram Moolenaar  function! s:first_line(string)
805c73622aSBram Moolenaar    return substitute(a:string, '\n.*$', "", "")
815c73622aSBram Moolenaar  endfunction
825c73622aSBram Moolenaar
835c73622aSBram Moolenaar  function! s:name(login)
845c73622aSBram Moolenaar    for name in [s:gecos_name(a:login), $NAME, s:capitalize(a:login)]
855c73622aSBram Moolenaar      if name != ""
865c73622aSBram Moolenaar        return name
8757657d85SBram Moolenaar      endif
8857657d85SBram Moolenaar    endfor
895c73622aSBram Moolenaar  endfunction
90071d4279SBram Moolenaar
915c73622aSBram Moolenaar  function! s:gecos_name(login)
925c73622aSBram Moolenaar    for line in s:try_reading_file('/etc/passwd')
935c73622aSBram Moolenaar      if line =~ '^' . a:login . ':'
945c73622aSBram Moolenaar        return substitute(s:passwd_field(line, 5), '&', s:capitalize(a:login), "")
9557657d85SBram Moolenaar      endif
965c73622aSBram Moolenaar    endfor
975c73622aSBram Moolenaar    return ""
985c73622aSBram Moolenaar  endfunction
9957657d85SBram Moolenaar
1005c73622aSBram Moolenaar  function! s:try_reading_file(path)
1015c73622aSBram Moolenaar    try
1025c73622aSBram Moolenaar      return readfile(a:path)
103446beb48SBram Moolenaar    catch
1045c73622aSBram Moolenaar      return []
105446beb48SBram Moolenaar    endtry
1065c73622aSBram Moolenaar  endfunction
107071d4279SBram Moolenaar
1085c73622aSBram Moolenaar  function! s:passwd_field(line, field)
1095c73622aSBram Moolenaar    let fields = split(a:line, ':', 1)
110d09acef4SBram Moolenaar    if len(fields) < a:field
1115c73622aSBram Moolenaar      return ""
112071d4279SBram Moolenaar    endif
113d09acef4SBram Moolenaar    return fields[a:field - 1]
1145c73622aSBram Moolenaar  endfunction
1155c73622aSBram Moolenaar
1165c73622aSBram Moolenaar  function! s:capitalize(word)
1175c73622aSBram Moolenaar    return toupper(a:word[0]) . strpart(a:word, 1)
1185c73622aSBram Moolenaar  endfunction
1195c73622aSBram Moolenaar
1205c73622aSBram Moolenaar  function! s:hostname()
1215c73622aSBram Moolenaar    return s:trimmed_system_with_default('hostname', 'localhost')
1225c73622aSBram Moolenaar  endfunction
123071d4279SBram Moolenaar
12457657d85SBram Moolenaar  " Format used for new date entries.
12557657d85SBram Moolenaar  if !exists('g:changelog_new_date_format')
1264b550b4cSBram Moolenaar    let g:changelog_new_date_format = "%d  %u\n\n\t* %p%c\n\n"
127071d4279SBram Moolenaar  endif
128071d4279SBram Moolenaar
12957657d85SBram Moolenaar  " Format used for new entries to current date entry.
13057657d85SBram Moolenaar  if !exists('g:changelog_new_entry_format')
1314b550b4cSBram Moolenaar    let g:changelog_new_entry_format = "\t* %p%c"
132071d4279SBram Moolenaar  endif
133071d4279SBram Moolenaar
13457657d85SBram Moolenaar  " Regular expression used to find a given date entry.
13557657d85SBram Moolenaar  if !exists('g:changelog_date_entry_search')
136071d4279SBram Moolenaar    let g:changelog_date_entry_search = '^\s*%d\_s*%u'
137071d4279SBram Moolenaar  endif
138071d4279SBram Moolenaar
1392c7a7638SBram Moolenaar  " Regular expression used to find the end of a date entry
1402c7a7638SBram Moolenaar  if !exists('g:changelog_date_end_entry_search')
141446cb837SBram Moolenaar    let g:changelog_date_end_entry_search = '^\s*$'
1422c7a7638SBram Moolenaar  endif
1432c7a7638SBram Moolenaar
1442c7a7638SBram Moolenaar
14557657d85SBram Moolenaar  " Substitutes specific items in new date-entry formats and search strings.
14657657d85SBram Moolenaar  " Can be done with substitute of course, but unclean, and need \@! then.
1474b550b4cSBram Moolenaar  function! s:substitute_items(str, date, user, prefix)
148071d4279SBram Moolenaar    let str = a:str
1494b550b4cSBram Moolenaar    let middles = {'%': '%', 'd': a:date, 'u': a:user, 'p': a:prefix, 'c': '{cursor}'}
150071d4279SBram Moolenaar    let i = stridx(str, '%')
151071d4279SBram Moolenaar    while i != -1
15257657d85SBram Moolenaar      let inc = 0
15357657d85SBram Moolenaar      if has_key(middles, str[i + 1])
15457657d85SBram Moolenaar        let mid = middles[str[i + 1]]
15557657d85SBram Moolenaar        let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
1568d043171SBram Moolenaar        let inc = strlen(mid) - 1
157071d4279SBram Moolenaar      endif
15857657d85SBram Moolenaar      let i = stridx(str, '%', i + 1 + inc)
159071d4279SBram Moolenaar    endwhile
160071d4279SBram Moolenaar    return str
161071d4279SBram Moolenaar  endfunction
162071d4279SBram Moolenaar
16357657d85SBram Moolenaar  " Position the cursor once we've done all the funky substitution.
164071d4279SBram Moolenaar  function! s:position_cursor()
165071d4279SBram Moolenaar    if search('{cursor}') > 0
16657657d85SBram Moolenaar      let lnum = line('.')
16757657d85SBram Moolenaar      let line = getline(lnum)
168071d4279SBram Moolenaar      let cursor = stridx(line, '{cursor}')
16957657d85SBram Moolenaar      call setline(lnum, substitute(line, '{cursor}', '', ''))
170071d4279SBram Moolenaar    endif
171d042dc82SBram Moolenaar    startinsert
172071d4279SBram Moolenaar  endfunction
173071d4279SBram Moolenaar
17457657d85SBram Moolenaar  " Internal function to create a new entry in the ChangeLog.
1754b550b4cSBram Moolenaar  function! s:new_changelog_entry(prefix)
17657657d85SBram Moolenaar    " Deal with 'paste' option.
177071d4279SBram Moolenaar    let save_paste = &paste
178071d4279SBram Moolenaar    let &paste = 1
17957657d85SBram Moolenaar    call cursor(1, 1)
18057657d85SBram Moolenaar    " Look for an entry for today by our user.
18157657d85SBram Moolenaar    let date = strftime(g:changelog_dateformat)
182071d4279SBram Moolenaar    let search = s:substitute_items(g:changelog_date_entry_search, date,
1834b550b4cSBram Moolenaar                                  \ s:username(), a:prefix)
184071d4279SBram Moolenaar    if search(search) > 0
18557657d85SBram Moolenaar      " Ok, now we look for the end of the date entry, and add an entry.
18657657d85SBram Moolenaar      call cursor(nextnonblank(line('.') + 1), 1)
1872c7a7638SBram Moolenaar      if search(g:changelog_date_end_entry_search, 'W') > 0
1885c73622aSBram Moolenaar	let p = (line('.') == line('$')) ? line('.') : line('.') - 1
189071d4279SBram Moolenaar      else
19057657d85SBram Moolenaar        let p = line('.')
19157657d85SBram Moolenaar      endif
1924b550b4cSBram Moolenaar      let ls = split(s:substitute_items(g:changelog_new_entry_format, '', '', a:prefix),
19357657d85SBram Moolenaar                   \ '\n')
19457657d85SBram Moolenaar      call append(p, ls)
19557657d85SBram Moolenaar      call cursor(p + 1, 1)
19657657d85SBram Moolenaar    else
19757657d85SBram Moolenaar      " Flag for removing empty lines at end of new ChangeLogs.
198071d4279SBram Moolenaar      let remove_empty = line('$') == 1
199071d4279SBram Moolenaar
20057657d85SBram Moolenaar      " No entry today, so create a date-user header and insert an entry.
201071d4279SBram Moolenaar      let todays_entry = s:substitute_items(g:changelog_new_date_format,
2024b550b4cSBram Moolenaar                                          \ date, s:username(), a:prefix)
20357657d85SBram Moolenaar      " Make sure we have a cursor positioning.
204071d4279SBram Moolenaar      if stridx(todays_entry, '{cursor}') == -1
205071d4279SBram Moolenaar        let todays_entry = todays_entry . '{cursor}'
206071d4279SBram Moolenaar      endif
207071d4279SBram Moolenaar
20857657d85SBram Moolenaar      " Now do the work.
20957657d85SBram Moolenaar      call append(0, split(todays_entry, '\n'))
21057657d85SBram Moolenaar
21157657d85SBram Moolenaar      " Remove empty lines at end of file.
212071d4279SBram Moolenaar      if remove_empty
21357657d85SBram Moolenaar        $-/^\s*$/-1,$delete
214071d4279SBram Moolenaar      endif
215071d4279SBram Moolenaar
21657657d85SBram Moolenaar      " Reposition cursor once we're done.
21757657d85SBram Moolenaar      call cursor(1, 1)
218071d4279SBram Moolenaar    endif
219071d4279SBram Moolenaar
220071d4279SBram Moolenaar    call s:position_cursor()
221071d4279SBram Moolenaar
222071d4279SBram Moolenaar    " And reset 'paste' option
223071d4279SBram Moolenaar    let &paste = save_paste
224071d4279SBram Moolenaar  endfunction
225071d4279SBram Moolenaar
22657657d85SBram Moolenaar  let b:undo_ftplugin = "setl com< fo< et< ai<"
227071d4279SBram Moolenaar
228071d4279SBram Moolenaar  setlocal comments=
229071d4279SBram Moolenaar  setlocal formatoptions+=t
230071d4279SBram Moolenaar  setlocal noexpandtab
23142eeac35SBram Moolenaar  setlocal autoindent
232071d4279SBram Moolenaar
23357657d85SBram Moolenaar  if &textwidth == 0
23457657d85SBram Moolenaar    setlocal textwidth=78
23557657d85SBram Moolenaar    let b:undo_ftplugin .= " tw<"
23657657d85SBram Moolenaar  endif
23757657d85SBram Moolenaar
238*079ba76aSBram Moolenaar  if !exists("no_plugin_maps") && !exists("no_changelog_maps") && exists(":NewChangelogEntry") != 2
239*079ba76aSBram Moolenaar    nnoremap <buffer> <silent> <Leader>o :<C-u>call <SID>new_changelog_entry('')<CR>
240*079ba76aSBram Moolenaar    xnoremap <buffer> <silent> <Leader>o :<C-u>call <SID>new_changelog_entry('')<CR>
241*079ba76aSBram Moolenaar    command! -buffer -nargs=0 NewChangelogEntry call s:new_changelog_entry('')
242*079ba76aSBram Moolenaar    let b:undo_ftplugin .= " | sil! exe 'nunmap <buffer> <Leader>o'" .
243*079ba76aSBram Moolenaar          \                " | sil! exe 'vunmap <buffer> <Leader>o'" .
244*079ba76aSBram Moolenaar          \                " | sil! delc NewChangelogEntry"
245*079ba76aSBram Moolenaar  endif
246*079ba76aSBram Moolenaar
24742eeac35SBram Moolenaar  let &cpo = s:cpo_save
24842eeac35SBram Moolenaar  unlet s:cpo_save
249071d4279SBram Moolenaarelse
2505c73622aSBram Moolenaar  let s:cpo_save = &cpo
2515c73622aSBram Moolenaar  set cpo&vim
2525c73622aSBram Moolenaar
253*079ba76aSBram Moolenaar  if !exists("no_plugin_maps") && !exists("no_changelog_maps")
254071d4279SBram Moolenaar    " Add the Changelog opening mapping
2555c73622aSBram Moolenaar    nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
256*079ba76aSBram Moolenaar    let b:undo_ftplugin .= " | silent! exe 'nunmap <buffer> <Leader>o"
257*079ba76aSBram Moolenaar  endif
258071d4279SBram Moolenaar
259071d4279SBram Moolenaar  function! s:open_changelog()
2605c73622aSBram Moolenaar    let path = expand('%:p:h')
2615c73622aSBram Moolenaar    if exists('b:changelog_path')
2625c73622aSBram Moolenaar      let changelog = b:changelog_path
2635c73622aSBram Moolenaar    else
2645c73622aSBram Moolenaar      if exists('b:changelog_name')
2655c73622aSBram Moolenaar        let name = b:changelog_name
2665c73622aSBram Moolenaar      else
2675c73622aSBram Moolenaar        let name = 'ChangeLog'
2685c73622aSBram Moolenaar      endif
2695c73622aSBram Moolenaar      while isdirectory(path)
2705c73622aSBram Moolenaar        let changelog = path . '/' . name
2715c73622aSBram Moolenaar        if filereadable(changelog)
2725c73622aSBram Moolenaar          break
2735c73622aSBram Moolenaar        endif
2745c73622aSBram Moolenaar        let parent = substitute(path, '/\+[^/]*$', "", "")
2755c73622aSBram Moolenaar        if path == parent
2765c73622aSBram Moolenaar          break
2775c73622aSBram Moolenaar        endif
2785c73622aSBram Moolenaar        let path = parent
2795c73622aSBram Moolenaar      endwhile
2805c73622aSBram Moolenaar    endif
2815c73622aSBram Moolenaar    if !filereadable(changelog)
28257657d85SBram Moolenaar      return
28357657d85SBram Moolenaar    endif
2845c73622aSBram Moolenaar
2855c73622aSBram Moolenaar    if exists('b:changelog_entry_prefix')
2865c73622aSBram Moolenaar      let prefix = call(b:changelog_entry_prefix, [])
2875c73622aSBram Moolenaar    else
2884b550b4cSBram Moolenaar      let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "")
2895c73622aSBram Moolenaar    endif
2905c73622aSBram Moolenaar
2915c73622aSBram Moolenaar    let buf = bufnr(changelog)
29257657d85SBram Moolenaar    if buf != -1
29357657d85SBram Moolenaar      if bufwinnr(buf) != -1
2942c7a7638SBram Moolenaar        execute bufwinnr(buf) . 'wincmd w'
29557657d85SBram Moolenaar      else
2962c7a7638SBram Moolenaar        execute 'sbuffer' buf
29757657d85SBram Moolenaar      endif
298071d4279SBram Moolenaar    else
2995c73622aSBram Moolenaar      execute 'split' fnameescape(changelog)
300071d4279SBram Moolenaar    endif
301071d4279SBram Moolenaar
3025c73622aSBram Moolenaar    call s:new_changelog_entry(prefix)
303071d4279SBram Moolenaar  endfunction
3045c73622aSBram Moolenaar
3055c73622aSBram Moolenaar  let &cpo = s:cpo_save
3065c73622aSBram Moolenaar  unlet s:cpo_save
307071d4279SBram Moolenaarendif
308