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