1071d4279SBram Moolenaar" Vim filetype plugin file
2071d4279SBram Moolenaar" Language:         generic Changelog file
357657d85SBram Moolenaar" Maintainer:       Nikolai Weibull <[email protected]>
4*5c73622aSBram Moolenaar" Latest Revision:  2009-05-25
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
49*5c73622aSBram Moolenaar  function! s:username()
50*5c73622aSBram Moolenaar    if exists('g:changelog_username')
51*5c73622aSBram Moolenaar      return g:changelog_username
52*5c73622aSBram Moolenaar    elseif $EMAIL != ""
53*5c73622aSBram Moolenaar      return $EMAIL
54*5c73622aSBram Moolenaar    elseif $EMAIL_ADDRESS != ""
55*5c73622aSBram Moolenaar      return $EMAIL_ADDRESS
56071d4279SBram Moolenaar    endif
57071d4279SBram Moolenaar
58*5c73622aSBram Moolenaar    let login = s:login()
59*5c73622aSBram Moolenaar    return printf('%s <%s@%s>', s:name(login), login, s:hostname())
60*5c73622aSBram Moolenaar  endfunction
61*5c73622aSBram Moolenaar
62*5c73622aSBram Moolenaar  function! s:login()
63*5c73622aSBram Moolenaar    return s:trimmed_system_with_default('whoami', 'unknown')
64*5c73622aSBram Moolenaar  endfunction
65*5c73622aSBram Moolenaar
66*5c73622aSBram Moolenaar  function! s:trimmed_system_with_default(command, default)
67*5c73622aSBram Moolenaar    return s:first_line(s:system_with_default(a:command, a:default))
68*5c73622aSBram Moolenaar  endfunction
69*5c73622aSBram Moolenaar
70*5c73622aSBram Moolenaar  function! s:system_with_default(command, default)
71*5c73622aSBram Moolenaar    let output = system(a:command)
72*5c73622aSBram Moolenaar    if v:shell_error
73*5c73622aSBram Moolenaar      return default
74071d4279SBram Moolenaar    endif
75*5c73622aSBram Moolenaar    return output
76*5c73622aSBram Moolenaar  endfunction
77*5c73622aSBram Moolenaar
78*5c73622aSBram Moolenaar  function! s:first_line(string)
79*5c73622aSBram Moolenaar    return substitute(a:string, '\n.*$', "", "")
80*5c73622aSBram Moolenaar  endfunction
81*5c73622aSBram Moolenaar
82*5c73622aSBram Moolenaar  function! s:name(login)
83*5c73622aSBram Moolenaar    for name in [s:gecos_name(a:login), $NAME, s:capitalize(a:login)]
84*5c73622aSBram Moolenaar      if name != ""
85*5c73622aSBram Moolenaar        return name
8657657d85SBram Moolenaar      endif
8757657d85SBram Moolenaar    endfor
88*5c73622aSBram Moolenaar  endfunction
89071d4279SBram Moolenaar
90*5c73622aSBram Moolenaar  function! s:gecos_name(login)
91*5c73622aSBram Moolenaar    for line in s:try_reading_file('/etc/passwd')
92*5c73622aSBram Moolenaar      if line =~ '^' . a:login . ':'
93*5c73622aSBram Moolenaar        return substitute(s:passwd_field(line, 5), '&', s:capitalize(a:login), "")
9457657d85SBram Moolenaar      endif
95*5c73622aSBram Moolenaar    endfor
96*5c73622aSBram Moolenaar    return ""
97*5c73622aSBram Moolenaar  endfunction
9857657d85SBram Moolenaar
99*5c73622aSBram Moolenaar  function! s:try_reading_file(path)
100*5c73622aSBram Moolenaar    try
101*5c73622aSBram Moolenaar      return readfile(a:path)
102*5c73622aSBram Moolenaar    endtry
103*5c73622aSBram Moolenaar    return []
104*5c73622aSBram Moolenaar  endfunction
105071d4279SBram Moolenaar
106*5c73622aSBram Moolenaar  function! s:passwd_field(line, field)
107*5c73622aSBram Moolenaar    let fields = split(a:line, ':', 1)
108*5c73622aSBram Moolenaar    if len(fields) < field
109*5c73622aSBram Moolenaar      return ""
110071d4279SBram Moolenaar    endif
111*5c73622aSBram Moolenaar    return fields[field - 1]
112*5c73622aSBram Moolenaar  endfunction
113*5c73622aSBram Moolenaar
114*5c73622aSBram Moolenaar  function! s:capitalize(word)
115*5c73622aSBram Moolenaar    return toupper(a:word[0]) . strpart(a:word, 1)
116*5c73622aSBram Moolenaar  endfunction
117*5c73622aSBram Moolenaar
118*5c73622aSBram Moolenaar  function! s:hostname()
119*5c73622aSBram Moolenaar    return s:trimmed_system_with_default('hostname', 'localhost')
120*5c73622aSBram Moolenaar  endfunction
121071d4279SBram Moolenaar
12257657d85SBram Moolenaar  " Format used for new date entries.
12357657d85SBram Moolenaar  if !exists('g:changelog_new_date_format')
124071d4279SBram Moolenaar    let g:changelog_new_date_format = "%d  %u\n\n\t* %c\n\n"
125071d4279SBram Moolenaar  endif
126071d4279SBram Moolenaar
12757657d85SBram Moolenaar  " Format used for new entries to current date entry.
12857657d85SBram Moolenaar  if !exists('g:changelog_new_entry_format')
129071d4279SBram Moolenaar    let g:changelog_new_entry_format = "\t* %c"
130071d4279SBram Moolenaar  endif
131071d4279SBram Moolenaar
13257657d85SBram Moolenaar  " Regular expression used to find a given date entry.
13357657d85SBram Moolenaar  if !exists('g:changelog_date_entry_search')
134071d4279SBram Moolenaar    let g:changelog_date_entry_search = '^\s*%d\_s*%u'
135071d4279SBram Moolenaar  endif
136071d4279SBram Moolenaar
1372c7a7638SBram Moolenaar  " Regular expression used to find the end of a date entry
1382c7a7638SBram Moolenaar  if !exists('g:changelog_date_end_entry_search')
139446cb837SBram Moolenaar    let g:changelog_date_end_entry_search = '^\s*$'
1402c7a7638SBram Moolenaar  endif
1412c7a7638SBram Moolenaar
1422c7a7638SBram Moolenaar
14357657d85SBram Moolenaar  " Substitutes specific items in new date-entry formats and search strings.
14457657d85SBram Moolenaar  " Can be done with substitute of course, but unclean, and need \@! then.
145071d4279SBram Moolenaar  function! s:substitute_items(str, date, user)
146071d4279SBram Moolenaar    let str = a:str
14757657d85SBram Moolenaar    let middles = {'%': '%', 'd': a:date, 'u': a:user, 'c': '{cursor}'}
148071d4279SBram Moolenaar    let i = stridx(str, '%')
149071d4279SBram Moolenaar    while i != -1
15057657d85SBram Moolenaar      let inc = 0
15157657d85SBram Moolenaar      if has_key(middles, str[i + 1])
15257657d85SBram Moolenaar        let mid = middles[str[i + 1]]
15357657d85SBram Moolenaar        let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
15457657d85SBram Moolenaar        let inc = strlen(mid)
155071d4279SBram Moolenaar      endif
15657657d85SBram Moolenaar      let i = stridx(str, '%', i + 1 + inc)
157071d4279SBram Moolenaar    endwhile
158071d4279SBram Moolenaar    return str
159071d4279SBram Moolenaar  endfunction
160071d4279SBram Moolenaar
16157657d85SBram Moolenaar  " Position the cursor once we've done all the funky substitution.
162071d4279SBram Moolenaar  function! s:position_cursor()
163071d4279SBram Moolenaar    if search('{cursor}') > 0
16457657d85SBram Moolenaar      let lnum = line('.')
16557657d85SBram Moolenaar      let line = getline(lnum)
166071d4279SBram Moolenaar      let cursor = stridx(line, '{cursor}')
16757657d85SBram Moolenaar      call setline(lnum, substitute(line, '{cursor}', '', ''))
168071d4279SBram Moolenaar    endif
169071d4279SBram Moolenaar    startinsert!
170071d4279SBram Moolenaar  endfunction
171071d4279SBram Moolenaar
17257657d85SBram Moolenaar  " Internal function to create a new entry in the ChangeLog.
173071d4279SBram Moolenaar  function! s:new_changelog_entry()
17457657d85SBram Moolenaar    " Deal with 'paste' option.
175071d4279SBram Moolenaar    let save_paste = &paste
176071d4279SBram Moolenaar    let &paste = 1
17757657d85SBram Moolenaar    call cursor(1, 1)
17857657d85SBram Moolenaar    " Look for an entry for today by our user.
17957657d85SBram Moolenaar    let date = strftime(g:changelog_dateformat)
180071d4279SBram Moolenaar    let search = s:substitute_items(g:changelog_date_entry_search, date,
181071d4279SBram Moolenaar                                  \ g:changelog_username)
182071d4279SBram Moolenaar    if search(search) > 0
18357657d85SBram Moolenaar      " Ok, now we look for the end of the date entry, and add an entry.
18457657d85SBram Moolenaar      call cursor(nextnonblank(line('.') + 1), 1)
1852c7a7638SBram Moolenaar      if search(g:changelog_date_end_entry_search, 'W') > 0
186*5c73622aSBram Moolenaar	let p = (line('.') == line('$')) ? line('.') : line('.') - 1
187071d4279SBram Moolenaar      else
18857657d85SBram Moolenaar        let p = line('.')
18957657d85SBram Moolenaar      endif
19057657d85SBram Moolenaar      let ls = split(s:substitute_items(g:changelog_new_entry_format, '', ''),
19157657d85SBram Moolenaar                   \ '\n')
19257657d85SBram Moolenaar      call append(p, ls)
19357657d85SBram Moolenaar      call cursor(p + 1, 1)
19457657d85SBram Moolenaar    else
19557657d85SBram Moolenaar      " Flag for removing empty lines at end of new ChangeLogs.
196071d4279SBram Moolenaar      let remove_empty = line('$') == 1
197071d4279SBram Moolenaar
19857657d85SBram Moolenaar      " No entry today, so create a date-user header and insert an entry.
199071d4279SBram Moolenaar      let todays_entry = s:substitute_items(g:changelog_new_date_format,
200071d4279SBram Moolenaar                                          \ date, g:changelog_username)
20157657d85SBram Moolenaar      " Make sure we have a cursor positioning.
202071d4279SBram Moolenaar      if stridx(todays_entry, '{cursor}') == -1
203071d4279SBram Moolenaar        let todays_entry = todays_entry . '{cursor}'
204071d4279SBram Moolenaar      endif
205071d4279SBram Moolenaar
20657657d85SBram Moolenaar      " Now do the work.
20757657d85SBram Moolenaar      call append(0, split(todays_entry, '\n'))
20857657d85SBram Moolenaar
20957657d85SBram Moolenaar      " Remove empty lines at end of file.
210071d4279SBram Moolenaar      if remove_empty
21157657d85SBram Moolenaar        $-/^\s*$/-1,$delete
212071d4279SBram Moolenaar      endif
213071d4279SBram Moolenaar
21457657d85SBram Moolenaar      " Reposition cursor once we're done.
21557657d85SBram Moolenaar      call cursor(1, 1)
216071d4279SBram Moolenaar    endif
217071d4279SBram Moolenaar
218071d4279SBram Moolenaar    call s:position_cursor()
219071d4279SBram Moolenaar
220071d4279SBram Moolenaar    " And reset 'paste' option
221071d4279SBram Moolenaar    let &paste = save_paste
222071d4279SBram Moolenaar  endfunction
223071d4279SBram Moolenaar
224071d4279SBram Moolenaar  if exists(":NewChangelogEntry") != 2
225*5c73622aSBram Moolenaar    noremap <buffer> <silent> <Leader>o <Esc>:call <SID>new_changelog_entry()<CR>
226071d4279SBram Moolenaar    command! -nargs=0 NewChangelogEntry call s:new_changelog_entry()
227071d4279SBram Moolenaar  endif
228071d4279SBram Moolenaar
22957657d85SBram Moolenaar  let b:undo_ftplugin = "setl com< fo< et< ai<"
230071d4279SBram Moolenaar
231071d4279SBram Moolenaar  setlocal comments=
232071d4279SBram Moolenaar  setlocal formatoptions+=t
233071d4279SBram Moolenaar  setlocal noexpandtab
23442eeac35SBram Moolenaar  setlocal autoindent
235071d4279SBram Moolenaar
23657657d85SBram Moolenaar  if &textwidth == 0
23757657d85SBram Moolenaar    setlocal textwidth=78
23857657d85SBram Moolenaar    let b:undo_ftplugin .= " tw<"
23957657d85SBram Moolenaar  endif
24057657d85SBram Moolenaar
24142eeac35SBram Moolenaar  let &cpo = s:cpo_save
24242eeac35SBram Moolenaar  unlet s:cpo_save
243071d4279SBram Moolenaarelse
244*5c73622aSBram Moolenaar  let s:cpo_save = &cpo
245*5c73622aSBram Moolenaar  set cpo&vim
246*5c73622aSBram Moolenaar
247071d4279SBram Moolenaar  " Add the Changelog opening mapping
248*5c73622aSBram Moolenaar  nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
249071d4279SBram Moolenaar
250071d4279SBram Moolenaar  function! s:open_changelog()
251*5c73622aSBram Moolenaar    let path = expand('%:p:h')
252*5c73622aSBram Moolenaar    if exists('b:changelog_path')
253*5c73622aSBram Moolenaar      let changelog = b:changelog_path
254*5c73622aSBram Moolenaar    else
255*5c73622aSBram Moolenaar      if exists('b:changelog_name')
256*5c73622aSBram Moolenaar        let name = b:changelog_name
257*5c73622aSBram Moolenaar      else
258*5c73622aSBram Moolenaar        let name = 'ChangeLog'
259*5c73622aSBram Moolenaar      endif
260*5c73622aSBram Moolenaar      while isdirectory(path)
261*5c73622aSBram Moolenaar        let changelog = path . '/' . name
262*5c73622aSBram Moolenaar        if filereadable(changelog)
263*5c73622aSBram Moolenaar          break
264*5c73622aSBram Moolenaar        endif
265*5c73622aSBram Moolenaar        let parent = substitute(path, '/\+[^/]*$', "", "")
266*5c73622aSBram Moolenaar        if path == parent
267*5c73622aSBram Moolenaar          break
268*5c73622aSBram Moolenaar        endif
269*5c73622aSBram Moolenaar        let path = parent
270*5c73622aSBram Moolenaar      endwhile
271*5c73622aSBram Moolenaar    endif
272*5c73622aSBram Moolenaar    if !filereadable(changelog)
27357657d85SBram Moolenaar      return
27457657d85SBram Moolenaar    endif
275*5c73622aSBram Moolenaar
276*5c73622aSBram Moolenaar    if exists('b:changelog_entry_prefix')
277*5c73622aSBram Moolenaar      let prefix = call(b:changelog_entry_prefix, [])
278*5c73622aSBram Moolenaar    else
279*5c73622aSBram Moolenaar      let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "") . ':'
280*5c73622aSBram Moolenaar    endif
281*5c73622aSBram Moolenaar    if !empty(prefix)
282*5c73622aSBram Moolenaar      let prefix = ' ' . prefix
283*5c73622aSBram Moolenaar    endif
284*5c73622aSBram Moolenaar
285*5c73622aSBram Moolenaar    let buf = bufnr(changelog)
28657657d85SBram Moolenaar    if buf != -1
28757657d85SBram Moolenaar      if bufwinnr(buf) != -1
2882c7a7638SBram Moolenaar        execute bufwinnr(buf) . 'wincmd w'
28957657d85SBram Moolenaar      else
2902c7a7638SBram Moolenaar        execute 'sbuffer' buf
29157657d85SBram Moolenaar      endif
292071d4279SBram Moolenaar    else
293*5c73622aSBram Moolenaar      execute 'split' fnameescape(changelog)
294071d4279SBram Moolenaar    endif
295071d4279SBram Moolenaar
296*5c73622aSBram Moolenaar    call s:new_changelog_entry(prefix)
297071d4279SBram Moolenaar  endfunction
298*5c73622aSBram Moolenaar
299*5c73622aSBram Moolenaar  let &cpo = s:cpo_save
300*5c73622aSBram Moolenaar  unlet s:cpo_save
301071d4279SBram Moolenaarendif
302