1071d4279SBram Moolenaar" Vim filetype plugin file
2071d4279SBram Moolenaar" Language:         generic Changelog file
357657d85SBram Moolenaar" Maintainer:       Nikolai Weibull <[email protected]>
4*446beb48SBram Moolenaar" Latest Revision:  2011-05-02
5071d4279SBram Moolenaar" Variables:
657657d85SBram Moolenaar"   g:changelog_timeformat (deprecated: use g:changelog_dateformat instead) -
7071d4279SBram Moolenaar"       description: the timeformat used in ChangeLog entries.
8071d4279SBram Moolenaar"       default: "%Y-%m-%d".
957657d85SBram Moolenaar"   g:changelog_dateformat -
1057657d85SBram Moolenaar"       description: the format sent to strftime() to generate a date string.
1157657d85SBram Moolenaar"       default: "%Y-%m-%d".
12071d4279SBram Moolenaar"   g:changelog_username -
13071d4279SBram Moolenaar"       description: the username to use in ChangeLog entries
14071d4279SBram Moolenaar"       default: try to deduce it from environment variables and system files.
15071d4279SBram Moolenaar" Local Mappings:
16071d4279SBram Moolenaar"   <Leader>o -
17071d4279SBram Moolenaar"       adds a new changelog entry for the current user for the current date.
18071d4279SBram Moolenaar" Global Mappings:
19071d4279SBram Moolenaar"   <Leader>o -
20071d4279SBram Moolenaar"       switches to the ChangeLog buffer opened for the current directory, or
21071d4279SBram Moolenaar"       opens it in a new buffer if it exists in the current directory.  Then
22071d4279SBram Moolenaar"       it does the same as the local <Leader>o described above.
23071d4279SBram Moolenaar" Notes:
24071d4279SBram Moolenaar"   run 'runtime ftplugin/changelog.vim' to enable the global mapping for
25071d4279SBram Moolenaar"   changelog files.
26071d4279SBram Moolenaar" TODO:
27071d4279SBram Moolenaar"  should we perhaps open the ChangeLog file even if it doesn't exist already?
28071d4279SBram Moolenaar"  Problem is that you might end up with ChangeLog files all over the place.
29071d4279SBram Moolenaar
30071d4279SBram Moolenaar" If 'filetype' isn't "changelog", we must have been to add ChangeLog opener
3157657d85SBram Moolenaarif &filetype == 'changelog'
3257657d85SBram Moolenaar  if exists('b:did_ftplugin')
33071d4279SBram Moolenaar    finish
34071d4279SBram Moolenaar  endif
35071d4279SBram Moolenaar  let b:did_ftplugin = 1
36071d4279SBram Moolenaar
3742eeac35SBram Moolenaar  let s:cpo_save = &cpo
3842eeac35SBram Moolenaar  set cpo&vim
39071d4279SBram Moolenaar
4057657d85SBram Moolenaar  " Set up the format used for dates.
4157657d85SBram Moolenaar  if !exists('g:changelog_dateformat')
4257657d85SBram Moolenaar    if exists('g:changelog_timeformat')
4357657d85SBram Moolenaar      let g:changelog_dateformat = g:changelog_timeformat
4457657d85SBram Moolenaar    else
4557657d85SBram Moolenaar      let g:changelog_dateformat = "%Y-%m-%d"
4657657d85SBram Moolenaar    endif
47071d4279SBram Moolenaar  endif
48071d4279SBram Moolenaar
495c73622aSBram Moolenaar  function! s:username()
505c73622aSBram Moolenaar    if exists('g:changelog_username')
515c73622aSBram Moolenaar      return g:changelog_username
525c73622aSBram Moolenaar    elseif $EMAIL != ""
535c73622aSBram Moolenaar      return $EMAIL
545c73622aSBram Moolenaar    elseif $EMAIL_ADDRESS != ""
555c73622aSBram Moolenaar      return $EMAIL_ADDRESS
56071d4279SBram Moolenaar    endif
57071d4279SBram Moolenaar
585c73622aSBram Moolenaar    let login = s:login()
595c73622aSBram Moolenaar    return printf('%s <%s@%s>', s:name(login), login, s:hostname())
605c73622aSBram Moolenaar  endfunction
615c73622aSBram Moolenaar
625c73622aSBram Moolenaar  function! s:login()
635c73622aSBram Moolenaar    return s:trimmed_system_with_default('whoami', 'unknown')
645c73622aSBram Moolenaar  endfunction
655c73622aSBram Moolenaar
665c73622aSBram Moolenaar  function! s:trimmed_system_with_default(command, default)
675c73622aSBram Moolenaar    return s:first_line(s:system_with_default(a:command, a:default))
685c73622aSBram Moolenaar  endfunction
695c73622aSBram Moolenaar
705c73622aSBram Moolenaar  function! s:system_with_default(command, default)
715c73622aSBram Moolenaar    let output = system(a:command)
725c73622aSBram Moolenaar    if v:shell_error
735c73622aSBram Moolenaar      return default
74071d4279SBram Moolenaar    endif
755c73622aSBram Moolenaar    return output
765c73622aSBram Moolenaar  endfunction
775c73622aSBram Moolenaar
785c73622aSBram Moolenaar  function! s:first_line(string)
795c73622aSBram Moolenaar    return substitute(a:string, '\n.*$', "", "")
805c73622aSBram Moolenaar  endfunction
815c73622aSBram Moolenaar
825c73622aSBram Moolenaar  function! s:name(login)
835c73622aSBram Moolenaar    for name in [s:gecos_name(a:login), $NAME, s:capitalize(a:login)]
845c73622aSBram Moolenaar      if name != ""
855c73622aSBram Moolenaar        return name
8657657d85SBram Moolenaar      endif
8757657d85SBram Moolenaar    endfor
885c73622aSBram Moolenaar  endfunction
89071d4279SBram Moolenaar
905c73622aSBram Moolenaar  function! s:gecos_name(login)
915c73622aSBram Moolenaar    for line in s:try_reading_file('/etc/passwd')
925c73622aSBram Moolenaar      if line =~ '^' . a:login . ':'
935c73622aSBram Moolenaar        return substitute(s:passwd_field(line, 5), '&', s:capitalize(a:login), "")
9457657d85SBram Moolenaar      endif
955c73622aSBram Moolenaar    endfor
965c73622aSBram Moolenaar    return ""
975c73622aSBram Moolenaar  endfunction
9857657d85SBram Moolenaar
995c73622aSBram Moolenaar  function! s:try_reading_file(path)
1005c73622aSBram Moolenaar    try
1015c73622aSBram Moolenaar      return readfile(a:path)
102*446beb48SBram Moolenaar    catch
1035c73622aSBram Moolenaar      return []
104*446beb48SBram Moolenaar    endtry
1055c73622aSBram Moolenaar  endfunction
106071d4279SBram Moolenaar
1075c73622aSBram Moolenaar  function! s:passwd_field(line, field)
1085c73622aSBram Moolenaar    let fields = split(a:line, ':', 1)
1095c73622aSBram Moolenaar    if len(fields) < field
1105c73622aSBram Moolenaar      return ""
111071d4279SBram Moolenaar    endif
1125c73622aSBram Moolenaar    return fields[field - 1]
1135c73622aSBram Moolenaar  endfunction
1145c73622aSBram Moolenaar
1155c73622aSBram Moolenaar  function! s:capitalize(word)
1165c73622aSBram Moolenaar    return toupper(a:word[0]) . strpart(a:word, 1)
1175c73622aSBram Moolenaar  endfunction
1185c73622aSBram Moolenaar
1195c73622aSBram Moolenaar  function! s:hostname()
1205c73622aSBram Moolenaar    return s:trimmed_system_with_default('hostname', 'localhost')
1215c73622aSBram Moolenaar  endfunction
122071d4279SBram Moolenaar
12357657d85SBram Moolenaar  " Format used for new date entries.
12457657d85SBram Moolenaar  if !exists('g:changelog_new_date_format')
125071d4279SBram Moolenaar    let g:changelog_new_date_format = "%d  %u\n\n\t* %c\n\n"
126071d4279SBram Moolenaar  endif
127071d4279SBram Moolenaar
12857657d85SBram Moolenaar  " Format used for new entries to current date entry.
12957657d85SBram Moolenaar  if !exists('g:changelog_new_entry_format')
130071d4279SBram Moolenaar    let g:changelog_new_entry_format = "\t* %c"
131071d4279SBram Moolenaar  endif
132071d4279SBram Moolenaar
13357657d85SBram Moolenaar  " Regular expression used to find a given date entry.
13457657d85SBram Moolenaar  if !exists('g:changelog_date_entry_search')
135071d4279SBram Moolenaar    let g:changelog_date_entry_search = '^\s*%d\_s*%u'
136071d4279SBram Moolenaar  endif
137071d4279SBram Moolenaar
1382c7a7638SBram Moolenaar  " Regular expression used to find the end of a date entry
1392c7a7638SBram Moolenaar  if !exists('g:changelog_date_end_entry_search')
140446cb837SBram Moolenaar    let g:changelog_date_end_entry_search = '^\s*$'
1412c7a7638SBram Moolenaar  endif
1422c7a7638SBram Moolenaar
1432c7a7638SBram Moolenaar
14457657d85SBram Moolenaar  " Substitutes specific items in new date-entry formats and search strings.
14557657d85SBram Moolenaar  " Can be done with substitute of course, but unclean, and need \@! then.
146071d4279SBram Moolenaar  function! s:substitute_items(str, date, user)
147071d4279SBram Moolenaar    let str = a:str
14857657d85SBram Moolenaar    let middles = {'%': '%', 'd': a:date, 'u': a:user, 'c': '{cursor}'}
149071d4279SBram Moolenaar    let i = stridx(str, '%')
150071d4279SBram Moolenaar    while i != -1
15157657d85SBram Moolenaar      let inc = 0
15257657d85SBram Moolenaar      if has_key(middles, str[i + 1])
15357657d85SBram Moolenaar        let mid = middles[str[i + 1]]
15457657d85SBram Moolenaar        let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
15557657d85SBram Moolenaar        let inc = strlen(mid)
156071d4279SBram Moolenaar      endif
15757657d85SBram Moolenaar      let i = stridx(str, '%', i + 1 + inc)
158071d4279SBram Moolenaar    endwhile
159071d4279SBram Moolenaar    return str
160071d4279SBram Moolenaar  endfunction
161071d4279SBram Moolenaar
16257657d85SBram Moolenaar  " Position the cursor once we've done all the funky substitution.
163071d4279SBram Moolenaar  function! s:position_cursor()
164071d4279SBram Moolenaar    if search('{cursor}') > 0
16557657d85SBram Moolenaar      let lnum = line('.')
16657657d85SBram Moolenaar      let line = getline(lnum)
167071d4279SBram Moolenaar      let cursor = stridx(line, '{cursor}')
16857657d85SBram Moolenaar      call setline(lnum, substitute(line, '{cursor}', '', ''))
169071d4279SBram Moolenaar    endif
170071d4279SBram Moolenaar    startinsert!
171071d4279SBram Moolenaar  endfunction
172071d4279SBram Moolenaar
17357657d85SBram Moolenaar  " Internal function to create a new entry in the ChangeLog.
174*446beb48SBram Moolenaar  function! s:new_changelog_entry()
17557657d85SBram Moolenaar    " Deal with 'paste' option.
176071d4279SBram Moolenaar    let save_paste = &paste
177071d4279SBram Moolenaar    let &paste = 1
17857657d85SBram Moolenaar    call cursor(1, 1)
17957657d85SBram Moolenaar    " Look for an entry for today by our user.
18057657d85SBram Moolenaar    let date = strftime(g:changelog_dateformat)
181071d4279SBram Moolenaar    let search = s:substitute_items(g:changelog_date_entry_search, date,
182*446beb48SBram Moolenaar                                  \ s:username())
183071d4279SBram Moolenaar    if search(search) > 0
18457657d85SBram Moolenaar      " Ok, now we look for the end of the date entry, and add an entry.
18557657d85SBram Moolenaar      call cursor(nextnonblank(line('.') + 1), 1)
1862c7a7638SBram Moolenaar      if search(g:changelog_date_end_entry_search, 'W') > 0
1875c73622aSBram Moolenaar	let p = (line('.') == line('$')) ? line('.') : line('.') - 1
188071d4279SBram Moolenaar      else
18957657d85SBram Moolenaar        let p = line('.')
19057657d85SBram Moolenaar      endif
19157657d85SBram Moolenaar      let ls = split(s:substitute_items(g:changelog_new_entry_format, '', ''),
19257657d85SBram Moolenaar                   \ '\n')
19357657d85SBram Moolenaar      call append(p, ls)
19457657d85SBram Moolenaar      call cursor(p + 1, 1)
19557657d85SBram Moolenaar    else
19657657d85SBram Moolenaar      " Flag for removing empty lines at end of new ChangeLogs.
197071d4279SBram Moolenaar      let remove_empty = line('$') == 1
198071d4279SBram Moolenaar
19957657d85SBram Moolenaar      " No entry today, so create a date-user header and insert an entry.
200071d4279SBram Moolenaar      let todays_entry = s:substitute_items(g:changelog_new_date_format,
201*446beb48SBram Moolenaar                                          \ date, s:username())
20257657d85SBram Moolenaar      " Make sure we have a cursor positioning.
203071d4279SBram Moolenaar      if stridx(todays_entry, '{cursor}') == -1
204071d4279SBram Moolenaar        let todays_entry = todays_entry . '{cursor}'
205071d4279SBram Moolenaar      endif
206071d4279SBram Moolenaar
20757657d85SBram Moolenaar      " Now do the work.
20857657d85SBram Moolenaar      call append(0, split(todays_entry, '\n'))
20957657d85SBram Moolenaar
21057657d85SBram Moolenaar      " Remove empty lines at end of file.
211071d4279SBram Moolenaar      if remove_empty
21257657d85SBram Moolenaar        $-/^\s*$/-1,$delete
213071d4279SBram Moolenaar      endif
214071d4279SBram Moolenaar
21557657d85SBram Moolenaar      " Reposition cursor once we're done.
21657657d85SBram Moolenaar      call cursor(1, 1)
217071d4279SBram Moolenaar    endif
218071d4279SBram Moolenaar
219071d4279SBram Moolenaar    call s:position_cursor()
220071d4279SBram Moolenaar
221071d4279SBram Moolenaar    " And reset 'paste' option
222071d4279SBram Moolenaar    let &paste = save_paste
223071d4279SBram Moolenaar  endfunction
224071d4279SBram Moolenaar
225071d4279SBram Moolenaar  if exists(":NewChangelogEntry") != 2
2265c73622aSBram Moolenaar    noremap <buffer> <silent> <Leader>o <Esc>:call <SID>new_changelog_entry()<CR>
227071d4279SBram Moolenaar    command! -nargs=0 NewChangelogEntry call s:new_changelog_entry()
228071d4279SBram Moolenaar  endif
229071d4279SBram Moolenaar
23057657d85SBram Moolenaar  let b:undo_ftplugin = "setl com< fo< et< ai<"
231071d4279SBram Moolenaar
232071d4279SBram Moolenaar  setlocal comments=
233071d4279SBram Moolenaar  setlocal formatoptions+=t
234071d4279SBram Moolenaar  setlocal noexpandtab
23542eeac35SBram Moolenaar  setlocal autoindent
236071d4279SBram Moolenaar
23757657d85SBram Moolenaar  if &textwidth == 0
23857657d85SBram Moolenaar    setlocal textwidth=78
23957657d85SBram Moolenaar    let b:undo_ftplugin .= " tw<"
24057657d85SBram Moolenaar  endif
24157657d85SBram Moolenaar
24242eeac35SBram Moolenaar  let &cpo = s:cpo_save
24342eeac35SBram Moolenaar  unlet s:cpo_save
244071d4279SBram Moolenaarelse
2455c73622aSBram Moolenaar  let s:cpo_save = &cpo
2465c73622aSBram Moolenaar  set cpo&vim
2475c73622aSBram Moolenaar
248071d4279SBram Moolenaar  " Add the Changelog opening mapping
2495c73622aSBram Moolenaar  nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
250071d4279SBram Moolenaar
251071d4279SBram Moolenaar  function! s:open_changelog()
2525c73622aSBram Moolenaar    let path = expand('%:p:h')
2535c73622aSBram Moolenaar    if exists('b:changelog_path')
2545c73622aSBram Moolenaar      let changelog = b:changelog_path
2555c73622aSBram Moolenaar    else
2565c73622aSBram Moolenaar      if exists('b:changelog_name')
2575c73622aSBram Moolenaar        let name = b:changelog_name
2585c73622aSBram Moolenaar      else
2595c73622aSBram Moolenaar        let name = 'ChangeLog'
2605c73622aSBram Moolenaar      endif
2615c73622aSBram Moolenaar      while isdirectory(path)
2625c73622aSBram Moolenaar        let changelog = path . '/' . name
2635c73622aSBram Moolenaar        if filereadable(changelog)
2645c73622aSBram Moolenaar          break
2655c73622aSBram Moolenaar        endif
2665c73622aSBram Moolenaar        let parent = substitute(path, '/\+[^/]*$', "", "")
2675c73622aSBram Moolenaar        if path == parent
2685c73622aSBram Moolenaar          break
2695c73622aSBram Moolenaar        endif
2705c73622aSBram Moolenaar        let path = parent
2715c73622aSBram Moolenaar      endwhile
2725c73622aSBram Moolenaar    endif
2735c73622aSBram Moolenaar    if !filereadable(changelog)
27457657d85SBram Moolenaar      return
27557657d85SBram Moolenaar    endif
2765c73622aSBram Moolenaar
2775c73622aSBram Moolenaar    if exists('b:changelog_entry_prefix')
2785c73622aSBram Moolenaar      let prefix = call(b:changelog_entry_prefix, [])
2795c73622aSBram Moolenaar    else
2805c73622aSBram Moolenaar      let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "") . ':'
2815c73622aSBram Moolenaar    endif
2825c73622aSBram Moolenaar    if !empty(prefix)
2835c73622aSBram Moolenaar      let prefix = ' ' . prefix
2845c73622aSBram Moolenaar    endif
2855c73622aSBram Moolenaar
2865c73622aSBram Moolenaar    let buf = bufnr(changelog)
28757657d85SBram Moolenaar    if buf != -1
28857657d85SBram Moolenaar      if bufwinnr(buf) != -1
2892c7a7638SBram Moolenaar        execute bufwinnr(buf) . 'wincmd w'
29057657d85SBram Moolenaar      else
2912c7a7638SBram Moolenaar        execute 'sbuffer' buf
29257657d85SBram Moolenaar      endif
293071d4279SBram Moolenaar    else
2945c73622aSBram Moolenaar      execute 'split' fnameescape(changelog)
295071d4279SBram Moolenaar    endif
296071d4279SBram Moolenaar
2975c73622aSBram Moolenaar    call s:new_changelog_entry(prefix)
298071d4279SBram Moolenaar  endfunction
2995c73622aSBram Moolenaar
3005c73622aSBram Moolenaar  let &cpo = s:cpo_save
3015c73622aSBram Moolenaar  unlet s:cpo_save
302071d4279SBram Moolenaarendif
303