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. 29 30" If 'filetype' isn't "changelog", we must have been to add ChangeLog opener 31if &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 endtry 103 return [] 104 endfunction 105 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 113 114 function! s:capitalize(word) 115 return toupper(a:word[0]) . strpart(a:word, 1) 116 endfunction 117 118 function! s:hostname() 119 return s:trimmed_system_with_default('hostname', 'localhost') 120 endfunction 121 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 126 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 131 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 136 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 141 142 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 160 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 171 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 197 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 205 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 213 214 " Reposition cursor once we're done. 215 call cursor(1, 1) 216 endif 217 218 call s:position_cursor() 219 220 " And reset 'paste' option 221 let &paste = save_paste 222 endfunction 223 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 228 229 let b:undo_ftplugin = "setl com< fo< et< ai<" 230 231 setlocal comments= 232 setlocal formatoptions+=t 233 setlocal noexpandtab 234 setlocal autoindent 235 236 if &textwidth == 0 237 setlocal textwidth=78 238 let b:undo_ftplugin .= " tw<" 239 endif 240 241 let &cpo = s:cpo_save 242 unlet s:cpo_save 243else 244 let s:cpo_save = &cpo 245 set cpo&vim 246 247 " Add the Changelog opening mapping 248 nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR> 249 250 function! s:open_changelog() 251 let path = expand('%:p:h') 252 if exists('b:changelog_path') 253 let changelog = b:changelog_path 254 else 255 if exists('b:changelog_name') 256 let name = b:changelog_name 257 else 258 let name = 'ChangeLog' 259 endif 260 while isdirectory(path) 261 let changelog = path . '/' . name 262 if filereadable(changelog) 263 break 264 endif 265 let parent = substitute(path, '/\+[^/]*$', "", "") 266 if path == parent 267 break 268 endif 269 let path = parent 270 endwhile 271 endif 272 if !filereadable(changelog) 273 return 274 endif 275 276 if exists('b:changelog_entry_prefix') 277 let prefix = call(b:changelog_entry_prefix, []) 278 else 279 let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "") . ':' 280 endif 281 if !empty(prefix) 282 let prefix = ' ' . prefix 283 endif 284 285 let buf = bufnr(changelog) 286 if buf != -1 287 if bufwinnr(buf) != -1 288 execute bufwinnr(buf) . 'wincmd w' 289 else 290 execute 'sbuffer' buf 291 endif 292 else 293 execute 'split' fnameescape(changelog) 294 endif 295 296 call s:new_changelog_entry(prefix) 297 endfunction 298 299 let &cpo = s:cpo_save 300 unlet s:cpo_save 301endif 302