Tizen 2.0 Release
[external/vim.git] / runtime / ftplugin / changelog.vim
1 " Vim filetype plugin file
2 " Language:         generic Changelog file
3 " Maintainer:       Nikolai Weibull <now@bitwi.se>
4 " Latest Revision:  2011-05-02
5 " Variables:
6 "   g:changelog_timeformat (deprecated: use g:changelog_dateformat instead) -
7 "       description: the timeformat used in ChangeLog entries.
8 "       default: "%Y-%m-%d".
9 "   g:changelog_dateformat -
10 "       description: the format sent to strftime() to generate a date string.
11 "       default: "%Y-%m-%d".
12 "   g:changelog_username -
13 "       description: the username to use in ChangeLog entries
14 "       default: try to deduce it from environment variables and system files.
15 " Local Mappings:
16 "   <Leader>o -
17 "       adds a new changelog entry for the current user for the current date.
18 " Global Mappings:
19 "   <Leader>o -
20 "       switches to the ChangeLog buffer opened for the current directory, or
21 "       opens it in a new buffer if it exists in the current directory.  Then
22 "       it does the same as the local <Leader>o described above.
23 " Notes:
24 "   run 'runtime ftplugin/changelog.vim' to enable the global mapping for
25 "   changelog files.
26 " TODO:
27 "  should we perhaps open the ChangeLog file even if it doesn't exist already?
28 "  Problem is that you might end up with ChangeLog files all over the place.
29
30 " If 'filetype' isn't "changelog", we must have been to add ChangeLog opener
31 if &filetype == 'changelog'
32   if exists('b:did_ftplugin')
33     finish
34   endif
35   let b:did_ftplugin = 1
36
37   let s:cpo_save = &cpo
38   set cpo&vim
39
40   " Set up the format used for dates.
41   if !exists('g:changelog_dateformat')
42     if exists('g:changelog_timeformat')
43       let g:changelog_dateformat = g:changelog_timeformat
44     else
45       let g:changelog_dateformat = "%Y-%m-%d"
46     endif
47   endif
48
49   function! s:username()
50     if exists('g:changelog_username')
51       return g:changelog_username
52     elseif $EMAIL != ""
53       return $EMAIL
54     elseif $EMAIL_ADDRESS != ""
55       return $EMAIL_ADDRESS
56     endif
57     
58     let login = s:login()
59     return printf('%s <%s@%s>', s:name(login), login, s:hostname())
60   endfunction
61
62   function! s:login()
63     return s:trimmed_system_with_default('whoami', 'unknown')
64   endfunction
65
66   function! s:trimmed_system_with_default(command, default)
67     return s:first_line(s:system_with_default(a:command, a:default))
68   endfunction
69
70   function! s:system_with_default(command, default)
71     let output = system(a:command)
72     if v:shell_error
73       return default
74     endif
75     return output
76   endfunction
77
78   function! s:first_line(string)
79     return substitute(a:string, '\n.*$', "", "")
80   endfunction
81
82   function! s:name(login)
83     for name in [s:gecos_name(a:login), $NAME, s:capitalize(a:login)]
84       if name != ""
85         return name
86       endif
87     endfor
88   endfunction
89
90   function! s:gecos_name(login)
91     for line in s:try_reading_file('/etc/passwd')
92       if line =~ '^' . a:login . ':'
93         return substitute(s:passwd_field(line, 5), '&', s:capitalize(a:login), "")
94       endif
95     endfor
96     return ""
97   endfunction
98
99   function! s:try_reading_file(path)
100     try
101       return readfile(a:path)
102     catch
103       return []
104     endtry
105   endfunction
106
107   function! s:passwd_field(line, field)
108     let fields = split(a:line, ':', 1)
109     if len(fields) < field
110       return ""
111     endif
112     return fields[field - 1]
113   endfunction
114
115   function! s:capitalize(word)
116     return toupper(a:word[0]) . strpart(a:word, 1)
117   endfunction
118
119   function! s:hostname()
120     return s:trimmed_system_with_default('hostname', 'localhost')
121   endfunction
122
123   " Format used for new date entries.
124   if !exists('g:changelog_new_date_format')
125     let g:changelog_new_date_format = "%d  %u\n\n\t* %c\n\n"
126   endif
127
128   " Format used for new entries to current date entry.
129   if !exists('g:changelog_new_entry_format')
130     let g:changelog_new_entry_format = "\t* %c"
131   endif
132
133   " Regular expression used to find a given date entry.
134   if !exists('g:changelog_date_entry_search')
135     let g:changelog_date_entry_search = '^\s*%d\_s*%u'
136   endif
137
138   " Regular expression used to find the end of a date entry
139   if !exists('g:changelog_date_end_entry_search')
140     let g:changelog_date_end_entry_search = '^\s*$'
141   endif
142
143
144   " Substitutes specific items in new date-entry formats and search strings.
145   " Can be done with substitute of course, but unclean, and need \@! then.
146   function! s:substitute_items(str, date, user)
147     let str = a:str
148     let middles = {'%': '%', 'd': a:date, 'u': a:user, 'c': '{cursor}'}
149     let i = stridx(str, '%')
150     while i != -1
151       let inc = 0
152       if has_key(middles, str[i + 1])
153         let mid = middles[str[i + 1]]
154         let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
155         let inc = strlen(mid)
156       endif
157       let i = stridx(str, '%', i + 1 + inc)
158     endwhile
159     return str
160   endfunction
161
162   " Position the cursor once we've done all the funky substitution.
163   function! s:position_cursor()
164     if search('{cursor}') > 0
165       let lnum = line('.')
166       let line = getline(lnum)
167       let cursor = stridx(line, '{cursor}')
168       call setline(lnum, substitute(line, '{cursor}', '', ''))
169     endif
170     startinsert!
171   endfunction
172
173   " Internal function to create a new entry in the ChangeLog.
174   function! s:new_changelog_entry()
175     " Deal with 'paste' option.
176     let save_paste = &paste
177     let &paste = 1
178     call cursor(1, 1)
179     " Look for an entry for today by our user.
180     let date = strftime(g:changelog_dateformat)
181     let search = s:substitute_items(g:changelog_date_entry_search, date,
182                                   \ s:username())
183     if search(search) > 0
184       " Ok, now we look for the end of the date entry, and add an entry.
185       call cursor(nextnonblank(line('.') + 1), 1)
186       if search(g:changelog_date_end_entry_search, 'W') > 0
187         let p = (line('.') == line('$')) ? line('.') : line('.') - 1
188       else
189         let p = line('.')
190       endif
191       let ls = split(s:substitute_items(g:changelog_new_entry_format, '', ''),
192                    \ '\n')
193       call append(p, ls)
194       call cursor(p + 1, 1)
195     else
196       " Flag for removing empty lines at end of new ChangeLogs.
197       let remove_empty = line('$') == 1
198
199       " No entry today, so create a date-user header and insert an entry.
200       let todays_entry = s:substitute_items(g:changelog_new_date_format,
201                                           \ date, s:username())
202       " Make sure we have a cursor positioning.
203       if stridx(todays_entry, '{cursor}') == -1
204         let todays_entry = todays_entry . '{cursor}'
205       endif
206
207       " Now do the work.
208       call append(0, split(todays_entry, '\n'))
209       
210       " Remove empty lines at end of file.
211       if remove_empty
212         $-/^\s*$/-1,$delete
213       endif
214
215       " Reposition cursor once we're done.
216       call cursor(1, 1)
217     endif
218
219     call s:position_cursor()
220
221     " And reset 'paste' option
222     let &paste = save_paste
223   endfunction
224
225   if exists(":NewChangelogEntry") != 2
226     noremap <buffer> <silent> <Leader>o <Esc>:call <SID>new_changelog_entry()<CR>
227     command! -nargs=0 NewChangelogEntry call s:new_changelog_entry()
228   endif
229
230   let b:undo_ftplugin = "setl com< fo< et< ai<"
231
232   setlocal comments=
233   setlocal formatoptions+=t
234   setlocal noexpandtab
235   setlocal autoindent
236
237   if &textwidth == 0
238     setlocal textwidth=78
239     let b:undo_ftplugin .= " tw<"
240   endif
241
242   let &cpo = s:cpo_save
243   unlet s:cpo_save
244 else
245   let s:cpo_save = &cpo
246   set cpo&vim
247
248   " Add the Changelog opening mapping
249   nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
250
251   function! s:open_changelog()
252     let path = expand('%:p:h')
253     if exists('b:changelog_path')
254       let changelog = b:changelog_path
255     else
256       if exists('b:changelog_name')
257         let name = b:changelog_name
258       else
259         let name = 'ChangeLog'
260       endif
261       while isdirectory(path)
262         let changelog = path . '/' . name
263         if filereadable(changelog)
264           break
265         endif
266         let parent = substitute(path, '/\+[^/]*$', "", "")
267         if path == parent
268           break
269         endif
270         let path = parent
271       endwhile
272     endif
273     if !filereadable(changelog)
274       return
275     endif
276
277     if exists('b:changelog_entry_prefix')
278       let prefix = call(b:changelog_entry_prefix, [])
279     else
280       let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "") . ':'
281     endif
282     if !empty(prefix)
283       let prefix = ' ' . prefix
284     endif
285
286     let buf = bufnr(changelog)
287     if buf != -1
288       if bufwinnr(buf) != -1
289         execute bufwinnr(buf) . 'wincmd w'
290       else
291         execute 'sbuffer' buf
292       endif
293     else
294       execute 'split' fnameescape(changelog)
295     endif
296
297     call s:new_changelog_entry(prefix)
298   endfunction
299
300   let &cpo = s:cpo_save
301   unlet s:cpo_save
302 endif