Avoid exceptions in charRangeForRow:::
[MacVim.git] / runtime / ftplugin / changelog.vim
blob3e19b6e021576559773c985bdae7f023a3b2aa4b
1 " Vim filetype plugin file
2 " Language:         generic Changelog file
3 " Maintainer:       Nikolai Weibull <now@bitwi.se>
4 " Latest Revision:  2009-05-25
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.
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
37   let s:cpo_save = &cpo
38   set cpo&vim
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
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
62   function! s:login()
63     return s:trimmed_system_with_default('whoami', 'unknown')
64   endfunction
66   function! s:trimmed_system_with_default(command, default)
67     return s:first_line(s:system_with_default(a:command, a:default))
68   endfunction
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
78   function! s:first_line(string)
79     return substitute(a:string, '\n.*$', "", "")
80   endfunction
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
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
99   function! s:try_reading_file(path)
100     try
101       return readfile(a:path)
102     endtry
103     return []
104   endfunction
106   function! s:passwd_field(line, field)
107     let fields = split(a:line, ':', 1)
108     if len(fields) < field
109       return ""
110     endif
111     return fields[field - 1]
112   endfunction
114   function! s:capitalize(word)
115     return toupper(a:word[0]) . strpart(a:word, 1)
116   endfunction
118   function! s:hostname()
119     return s:trimmed_system_with_default('hostname', 'localhost')
120   endfunction
122   " Format used for new date entries.
123   if !exists('g:changelog_new_date_format')
124     let g:changelog_new_date_format = "%d  %u\n\n\t* %c\n\n"
125   endif
127   " Format used for new entries to current date entry.
128   if !exists('g:changelog_new_entry_format')
129     let g:changelog_new_entry_format = "\t* %c"
130   endif
132   " Regular expression used to find a given date entry.
133   if !exists('g:changelog_date_entry_search')
134     let g:changelog_date_entry_search = '^\s*%d\_s*%u'
135   endif
137   " Regular expression used to find the end of a date entry
138   if !exists('g:changelog_date_end_entry_search')
139     let g:changelog_date_end_entry_search = '^\s*$'
140   endif
143   " Substitutes specific items in new date-entry formats and search strings.
144   " Can be done with substitute of course, but unclean, and need \@! then.
145   function! s:substitute_items(str, date, user)
146     let str = a:str
147     let middles = {'%': '%', 'd': a:date, 'u': a:user, 'c': '{cursor}'}
148     let i = stridx(str, '%')
149     while i != -1
150       let inc = 0
151       if has_key(middles, str[i + 1])
152         let mid = middles[str[i + 1]]
153         let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
154         let inc = strlen(mid)
155       endif
156       let i = stridx(str, '%', i + 1 + inc)
157     endwhile
158     return str
159   endfunction
161   " Position the cursor once we've done all the funky substitution.
162   function! s:position_cursor()
163     if search('{cursor}') > 0
164       let lnum = line('.')
165       let line = getline(lnum)
166       let cursor = stridx(line, '{cursor}')
167       call setline(lnum, substitute(line, '{cursor}', '', ''))
168     endif
169     startinsert!
170   endfunction
172   " Internal function to create a new entry in the ChangeLog.
173   function! s:new_changelog_entry()
174     " Deal with 'paste' option.
175     let save_paste = &paste
176     let &paste = 1
177     call cursor(1, 1)
178     " Look for an entry for today by our user.
179     let date = strftime(g:changelog_dateformat)
180     let search = s:substitute_items(g:changelog_date_entry_search, date,
181                                   \ g:changelog_username)
182     if search(search) > 0
183       " Ok, now we look for the end of the date entry, and add an entry.
184       call cursor(nextnonblank(line('.') + 1), 1)
185       if search(g:changelog_date_end_entry_search, 'W') > 0
186         let p = (line('.') == line('$')) ? line('.') : line('.') - 1
187       else
188         let p = line('.')
189       endif
190       let ls = split(s:substitute_items(g:changelog_new_entry_format, '', ''),
191                    \ '\n')
192       call append(p, ls)
193       call cursor(p + 1, 1)
194     else
195       " Flag for removing empty lines at end of new ChangeLogs.
196       let remove_empty = line('$') == 1
198       " No entry today, so create a date-user header and insert an entry.
199       let todays_entry = s:substitute_items(g:changelog_new_date_format,
200                                           \ date, g:changelog_username)
201       " Make sure we have a cursor positioning.
202       if stridx(todays_entry, '{cursor}') == -1
203         let todays_entry = todays_entry . '{cursor}'
204       endif
206       " Now do the work.
207       call append(0, split(todays_entry, '\n'))
208       
209       " Remove empty lines at end of file.
210       if remove_empty
211         $-/^\s*$/-1,$delete
212       endif
214       " Reposition cursor once we're done.
215       call cursor(1, 1)
216     endif
218     call s:position_cursor()
220     " And reset 'paste' option
221     let &paste = save_paste
222   endfunction
224   if exists(":NewChangelogEntry") != 2
225     noremap <buffer> <silent> <Leader>o <Esc>:call <SID>new_changelog_entry()<CR>
226     command! -nargs=0 NewChangelogEntry call s:new_changelog_entry()
227   endif
229   let b:undo_ftplugin = "setl com< fo< et< ai<"
231   setlocal comments=
232   setlocal formatoptions+=t
233   setlocal noexpandtab
234   setlocal autoindent
236   if &textwidth == 0
237     setlocal textwidth=78
238     let b:undo_ftplugin .= " tw<"
239   endif
241   let &cpo = s:cpo_save
242   unlet s:cpo_save
243 else
244   " Add the Changelog opening mapping
245   nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
247   function! s:open_changelog()
248     let path = expand('%:p:h')
249     if exists('b:changelog_path')
250       let changelog = b:changelog_path
251     else
252       if exists('b:changelog_name')
253         let name = b:changelog_name
254       else
255         let name = 'ChangeLog'
256       endif
257       while isdirectory(path)
258         let changelog = path . '/' . name
259         if filereadable(changelog)
260           break
261         endif
262         let parent = substitute(path, '/\+[^/]*$', "", "")
263         if path == parent
264           break
265         endif
266         let path = parent
267       endwhile
268     endif
269     if !filereadable(changelog)
270       return
271     endif
273     if exists('b:changelog_entry_prefix')
274       let prefix = call(b:changelog_entry_prefix, [])
275     else
276       let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "") . ':'
277     endif
278     if !empty(prefix)
279       let prefix = ' ' . prefix
280     endif
282     let buf = bufnr(changelog)
283     if buf != -1
284       if bufwinnr(buf) != -1
285         execute bufwinnr(buf) . 'wincmd w'
286       else
287         execute 'sbuffer' buf
288       endif
289     else
290       execute 'split' fnameescape(changelog)
291     endif
293     call s:new_changelog_entry(prefix)
294   endfunction
295 endif