1071d4279SBram Moolenaar" Vim filetype plugin file 2071d4279SBram Moolenaar" Language: generic Changelog file 3d042dc82SBram Moolenaar" Maintainer: Martin Florian <[email protected]> 4d042dc82SBram Moolenaar" Previous Maintainer: Nikolai Weibull <[email protected]> 5*079ba76aSBram Moolenaar" Latest Revision: 2021-10-17 6071d4279SBram Moolenaar" Variables: 757657d85SBram Moolenaar" g:changelog_timeformat (deprecated: use g:changelog_dateformat instead) - 8071d4279SBram Moolenaar" description: the timeformat used in ChangeLog entries. 9071d4279SBram Moolenaar" default: "%Y-%m-%d". 1057657d85SBram Moolenaar" g:changelog_dateformat - 1157657d85SBram Moolenaar" description: the format sent to strftime() to generate a date string. 1257657d85SBram Moolenaar" default: "%Y-%m-%d". 13071d4279SBram Moolenaar" g:changelog_username - 14071d4279SBram Moolenaar" description: the username to use in ChangeLog entries 15071d4279SBram Moolenaar" default: try to deduce it from environment variables and system files. 16071d4279SBram Moolenaar" Local Mappings: 17071d4279SBram Moolenaar" <Leader>o - 18071d4279SBram Moolenaar" adds a new changelog entry for the current user for the current date. 19071d4279SBram Moolenaar" Global Mappings: 20071d4279SBram Moolenaar" <Leader>o - 21071d4279SBram Moolenaar" switches to the ChangeLog buffer opened for the current directory, or 22071d4279SBram Moolenaar" opens it in a new buffer if it exists in the current directory. Then 23071d4279SBram Moolenaar" it does the same as the local <Leader>o described above. 24071d4279SBram Moolenaar" Notes: 25071d4279SBram Moolenaar" run 'runtime ftplugin/changelog.vim' to enable the global mapping for 26071d4279SBram Moolenaar" changelog files. 27071d4279SBram Moolenaar" TODO: 28071d4279SBram Moolenaar" should we perhaps open the ChangeLog file even if it doesn't exist already? 29071d4279SBram Moolenaar" Problem is that you might end up with ChangeLog files all over the place. 30071d4279SBram Moolenaar 31071d4279SBram Moolenaar" If 'filetype' isn't "changelog", we must have been to add ChangeLog opener 3257657d85SBram Moolenaarif &filetype == 'changelog' 3357657d85SBram Moolenaar if exists('b:did_ftplugin') 34071d4279SBram Moolenaar finish 35071d4279SBram Moolenaar endif 36071d4279SBram Moolenaar let b:did_ftplugin = 1 37071d4279SBram Moolenaar 3842eeac35SBram Moolenaar let s:cpo_save = &cpo 3942eeac35SBram Moolenaar set cpo&vim 40071d4279SBram Moolenaar 4157657d85SBram Moolenaar " Set up the format used for dates. 4257657d85SBram Moolenaar if !exists('g:changelog_dateformat') 4357657d85SBram Moolenaar if exists('g:changelog_timeformat') 4457657d85SBram Moolenaar let g:changelog_dateformat = g:changelog_timeformat 4557657d85SBram Moolenaar else 4657657d85SBram Moolenaar let g:changelog_dateformat = "%Y-%m-%d" 4757657d85SBram Moolenaar endif 48071d4279SBram Moolenaar endif 49071d4279SBram Moolenaar 505c73622aSBram Moolenaar function! s:username() 515c73622aSBram Moolenaar if exists('g:changelog_username') 525c73622aSBram Moolenaar return g:changelog_username 535c73622aSBram Moolenaar elseif $EMAIL != "" 545c73622aSBram Moolenaar return $EMAIL 555c73622aSBram Moolenaar elseif $EMAIL_ADDRESS != "" 565c73622aSBram Moolenaar return $EMAIL_ADDRESS 57071d4279SBram Moolenaar endif 58071d4279SBram Moolenaar 595c73622aSBram Moolenaar let login = s:login() 605c73622aSBram Moolenaar return printf('%s <%s@%s>', s:name(login), login, s:hostname()) 615c73622aSBram Moolenaar endfunction 625c73622aSBram Moolenaar 635c73622aSBram Moolenaar function! s:login() 645c73622aSBram Moolenaar return s:trimmed_system_with_default('whoami', 'unknown') 655c73622aSBram Moolenaar endfunction 665c73622aSBram Moolenaar 675c73622aSBram Moolenaar function! s:trimmed_system_with_default(command, default) 685c73622aSBram Moolenaar return s:first_line(s:system_with_default(a:command, a:default)) 695c73622aSBram Moolenaar endfunction 705c73622aSBram Moolenaar 715c73622aSBram Moolenaar function! s:system_with_default(command, default) 725c73622aSBram Moolenaar let output = system(a:command) 735c73622aSBram Moolenaar if v:shell_error 745c73622aSBram Moolenaar return default 75071d4279SBram Moolenaar endif 765c73622aSBram Moolenaar return output 775c73622aSBram Moolenaar endfunction 785c73622aSBram Moolenaar 795c73622aSBram Moolenaar function! s:first_line(string) 805c73622aSBram Moolenaar return substitute(a:string, '\n.*$', "", "") 815c73622aSBram Moolenaar endfunction 825c73622aSBram Moolenaar 835c73622aSBram Moolenaar function! s:name(login) 845c73622aSBram Moolenaar for name in [s:gecos_name(a:login), $NAME, s:capitalize(a:login)] 855c73622aSBram Moolenaar if name != "" 865c73622aSBram Moolenaar return name 8757657d85SBram Moolenaar endif 8857657d85SBram Moolenaar endfor 895c73622aSBram Moolenaar endfunction 90071d4279SBram Moolenaar 915c73622aSBram Moolenaar function! s:gecos_name(login) 925c73622aSBram Moolenaar for line in s:try_reading_file('/etc/passwd') 935c73622aSBram Moolenaar if line =~ '^' . a:login . ':' 945c73622aSBram Moolenaar return substitute(s:passwd_field(line, 5), '&', s:capitalize(a:login), "") 9557657d85SBram Moolenaar endif 965c73622aSBram Moolenaar endfor 975c73622aSBram Moolenaar return "" 985c73622aSBram Moolenaar endfunction 9957657d85SBram Moolenaar 1005c73622aSBram Moolenaar function! s:try_reading_file(path) 1015c73622aSBram Moolenaar try 1025c73622aSBram Moolenaar return readfile(a:path) 103446beb48SBram Moolenaar catch 1045c73622aSBram Moolenaar return [] 105446beb48SBram Moolenaar endtry 1065c73622aSBram Moolenaar endfunction 107071d4279SBram Moolenaar 1085c73622aSBram Moolenaar function! s:passwd_field(line, field) 1095c73622aSBram Moolenaar let fields = split(a:line, ':', 1) 110d09acef4SBram Moolenaar if len(fields) < a:field 1115c73622aSBram Moolenaar return "" 112071d4279SBram Moolenaar endif 113d09acef4SBram Moolenaar return fields[a:field - 1] 1145c73622aSBram Moolenaar endfunction 1155c73622aSBram Moolenaar 1165c73622aSBram Moolenaar function! s:capitalize(word) 1175c73622aSBram Moolenaar return toupper(a:word[0]) . strpart(a:word, 1) 1185c73622aSBram Moolenaar endfunction 1195c73622aSBram Moolenaar 1205c73622aSBram Moolenaar function! s:hostname() 1215c73622aSBram Moolenaar return s:trimmed_system_with_default('hostname', 'localhost') 1225c73622aSBram Moolenaar endfunction 123071d4279SBram Moolenaar 12457657d85SBram Moolenaar " Format used for new date entries. 12557657d85SBram Moolenaar if !exists('g:changelog_new_date_format') 1264b550b4cSBram Moolenaar let g:changelog_new_date_format = "%d %u\n\n\t* %p%c\n\n" 127071d4279SBram Moolenaar endif 128071d4279SBram Moolenaar 12957657d85SBram Moolenaar " Format used for new entries to current date entry. 13057657d85SBram Moolenaar if !exists('g:changelog_new_entry_format') 1314b550b4cSBram Moolenaar let g:changelog_new_entry_format = "\t* %p%c" 132071d4279SBram Moolenaar endif 133071d4279SBram Moolenaar 13457657d85SBram Moolenaar " Regular expression used to find a given date entry. 13557657d85SBram Moolenaar if !exists('g:changelog_date_entry_search') 136071d4279SBram Moolenaar let g:changelog_date_entry_search = '^\s*%d\_s*%u' 137071d4279SBram Moolenaar endif 138071d4279SBram Moolenaar 1392c7a7638SBram Moolenaar " Regular expression used to find the end of a date entry 1402c7a7638SBram Moolenaar if !exists('g:changelog_date_end_entry_search') 141446cb837SBram Moolenaar let g:changelog_date_end_entry_search = '^\s*$' 1422c7a7638SBram Moolenaar endif 1432c7a7638SBram Moolenaar 1442c7a7638SBram Moolenaar 14557657d85SBram Moolenaar " Substitutes specific items in new date-entry formats and search strings. 14657657d85SBram Moolenaar " Can be done with substitute of course, but unclean, and need \@! then. 1474b550b4cSBram Moolenaar function! s:substitute_items(str, date, user, prefix) 148071d4279SBram Moolenaar let str = a:str 1494b550b4cSBram Moolenaar let middles = {'%': '%', 'd': a:date, 'u': a:user, 'p': a:prefix, 'c': '{cursor}'} 150071d4279SBram Moolenaar let i = stridx(str, '%') 151071d4279SBram Moolenaar while i != -1 15257657d85SBram Moolenaar let inc = 0 15357657d85SBram Moolenaar if has_key(middles, str[i + 1]) 15457657d85SBram Moolenaar let mid = middles[str[i + 1]] 15557657d85SBram Moolenaar let str = strpart(str, 0, i) . mid . strpart(str, i + 2) 1568d043171SBram Moolenaar let inc = strlen(mid) - 1 157071d4279SBram Moolenaar endif 15857657d85SBram Moolenaar let i = stridx(str, '%', i + 1 + inc) 159071d4279SBram Moolenaar endwhile 160071d4279SBram Moolenaar return str 161071d4279SBram Moolenaar endfunction 162071d4279SBram Moolenaar 16357657d85SBram Moolenaar " Position the cursor once we've done all the funky substitution. 164071d4279SBram Moolenaar function! s:position_cursor() 165071d4279SBram Moolenaar if search('{cursor}') > 0 16657657d85SBram Moolenaar let lnum = line('.') 16757657d85SBram Moolenaar let line = getline(lnum) 168071d4279SBram Moolenaar let cursor = stridx(line, '{cursor}') 16957657d85SBram Moolenaar call setline(lnum, substitute(line, '{cursor}', '', '')) 170071d4279SBram Moolenaar endif 171d042dc82SBram Moolenaar startinsert 172071d4279SBram Moolenaar endfunction 173071d4279SBram Moolenaar 17457657d85SBram Moolenaar " Internal function to create a new entry in the ChangeLog. 1754b550b4cSBram Moolenaar function! s:new_changelog_entry(prefix) 17657657d85SBram Moolenaar " Deal with 'paste' option. 177071d4279SBram Moolenaar let save_paste = &paste 178071d4279SBram Moolenaar let &paste = 1 17957657d85SBram Moolenaar call cursor(1, 1) 18057657d85SBram Moolenaar " Look for an entry for today by our user. 18157657d85SBram Moolenaar let date = strftime(g:changelog_dateformat) 182071d4279SBram Moolenaar let search = s:substitute_items(g:changelog_date_entry_search, date, 1834b550b4cSBram Moolenaar \ s:username(), a:prefix) 184071d4279SBram Moolenaar if search(search) > 0 18557657d85SBram Moolenaar " Ok, now we look for the end of the date entry, and add an entry. 18657657d85SBram Moolenaar call cursor(nextnonblank(line('.') + 1), 1) 1872c7a7638SBram Moolenaar if search(g:changelog_date_end_entry_search, 'W') > 0 1885c73622aSBram Moolenaar let p = (line('.') == line('$')) ? line('.') : line('.') - 1 189071d4279SBram Moolenaar else 19057657d85SBram Moolenaar let p = line('.') 19157657d85SBram Moolenaar endif 1924b550b4cSBram Moolenaar let ls = split(s:substitute_items(g:changelog_new_entry_format, '', '', a:prefix), 19357657d85SBram Moolenaar \ '\n') 19457657d85SBram Moolenaar call append(p, ls) 19557657d85SBram Moolenaar call cursor(p + 1, 1) 19657657d85SBram Moolenaar else 19757657d85SBram Moolenaar " Flag for removing empty lines at end of new ChangeLogs. 198071d4279SBram Moolenaar let remove_empty = line('$') == 1 199071d4279SBram Moolenaar 20057657d85SBram Moolenaar " No entry today, so create a date-user header and insert an entry. 201071d4279SBram Moolenaar let todays_entry = s:substitute_items(g:changelog_new_date_format, 2024b550b4cSBram Moolenaar \ date, s:username(), a:prefix) 20357657d85SBram Moolenaar " Make sure we have a cursor positioning. 204071d4279SBram Moolenaar if stridx(todays_entry, '{cursor}') == -1 205071d4279SBram Moolenaar let todays_entry = todays_entry . '{cursor}' 206071d4279SBram Moolenaar endif 207071d4279SBram Moolenaar 20857657d85SBram Moolenaar " Now do the work. 20957657d85SBram Moolenaar call append(0, split(todays_entry, '\n')) 21057657d85SBram Moolenaar 21157657d85SBram Moolenaar " Remove empty lines at end of file. 212071d4279SBram Moolenaar if remove_empty 21357657d85SBram Moolenaar $-/^\s*$/-1,$delete 214071d4279SBram Moolenaar endif 215071d4279SBram Moolenaar 21657657d85SBram Moolenaar " Reposition cursor once we're done. 21757657d85SBram Moolenaar call cursor(1, 1) 218071d4279SBram Moolenaar endif 219071d4279SBram Moolenaar 220071d4279SBram Moolenaar call s:position_cursor() 221071d4279SBram Moolenaar 222071d4279SBram Moolenaar " And reset 'paste' option 223071d4279SBram Moolenaar let &paste = save_paste 224071d4279SBram Moolenaar endfunction 225071d4279SBram Moolenaar 22657657d85SBram Moolenaar let b:undo_ftplugin = "setl com< fo< et< ai<" 227071d4279SBram Moolenaar 228071d4279SBram Moolenaar setlocal comments= 229071d4279SBram Moolenaar setlocal formatoptions+=t 230071d4279SBram Moolenaar setlocal noexpandtab 23142eeac35SBram Moolenaar setlocal autoindent 232071d4279SBram Moolenaar 23357657d85SBram Moolenaar if &textwidth == 0 23457657d85SBram Moolenaar setlocal textwidth=78 23557657d85SBram Moolenaar let b:undo_ftplugin .= " tw<" 23657657d85SBram Moolenaar endif 23757657d85SBram Moolenaar 238*079ba76aSBram Moolenaar if !exists("no_plugin_maps") && !exists("no_changelog_maps") && exists(":NewChangelogEntry") != 2 239*079ba76aSBram Moolenaar nnoremap <buffer> <silent> <Leader>o :<C-u>call <SID>new_changelog_entry('')<CR> 240*079ba76aSBram Moolenaar xnoremap <buffer> <silent> <Leader>o :<C-u>call <SID>new_changelog_entry('')<CR> 241*079ba76aSBram Moolenaar command! -buffer -nargs=0 NewChangelogEntry call s:new_changelog_entry('') 242*079ba76aSBram Moolenaar let b:undo_ftplugin .= " | sil! exe 'nunmap <buffer> <Leader>o'" . 243*079ba76aSBram Moolenaar \ " | sil! exe 'vunmap <buffer> <Leader>o'" . 244*079ba76aSBram Moolenaar \ " | sil! delc NewChangelogEntry" 245*079ba76aSBram Moolenaar endif 246*079ba76aSBram Moolenaar 24742eeac35SBram Moolenaar let &cpo = s:cpo_save 24842eeac35SBram Moolenaar unlet s:cpo_save 249071d4279SBram Moolenaarelse 2505c73622aSBram Moolenaar let s:cpo_save = &cpo 2515c73622aSBram Moolenaar set cpo&vim 2525c73622aSBram Moolenaar 253*079ba76aSBram Moolenaar if !exists("no_plugin_maps") && !exists("no_changelog_maps") 254071d4279SBram Moolenaar " Add the Changelog opening mapping 2555c73622aSBram Moolenaar nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR> 256*079ba76aSBram Moolenaar let b:undo_ftplugin .= " | silent! exe 'nunmap <buffer> <Leader>o" 257*079ba76aSBram Moolenaar endif 258071d4279SBram Moolenaar 259071d4279SBram Moolenaar function! s:open_changelog() 2605c73622aSBram Moolenaar let path = expand('%:p:h') 2615c73622aSBram Moolenaar if exists('b:changelog_path') 2625c73622aSBram Moolenaar let changelog = b:changelog_path 2635c73622aSBram Moolenaar else 2645c73622aSBram Moolenaar if exists('b:changelog_name') 2655c73622aSBram Moolenaar let name = b:changelog_name 2665c73622aSBram Moolenaar else 2675c73622aSBram Moolenaar let name = 'ChangeLog' 2685c73622aSBram Moolenaar endif 2695c73622aSBram Moolenaar while isdirectory(path) 2705c73622aSBram Moolenaar let changelog = path . '/' . name 2715c73622aSBram Moolenaar if filereadable(changelog) 2725c73622aSBram Moolenaar break 2735c73622aSBram Moolenaar endif 2745c73622aSBram Moolenaar let parent = substitute(path, '/\+[^/]*$', "", "") 2755c73622aSBram Moolenaar if path == parent 2765c73622aSBram Moolenaar break 2775c73622aSBram Moolenaar endif 2785c73622aSBram Moolenaar let path = parent 2795c73622aSBram Moolenaar endwhile 2805c73622aSBram Moolenaar endif 2815c73622aSBram Moolenaar if !filereadable(changelog) 28257657d85SBram Moolenaar return 28357657d85SBram Moolenaar endif 2845c73622aSBram Moolenaar 2855c73622aSBram Moolenaar if exists('b:changelog_entry_prefix') 2865c73622aSBram Moolenaar let prefix = call(b:changelog_entry_prefix, []) 2875c73622aSBram Moolenaar else 2884b550b4cSBram Moolenaar let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "") 2895c73622aSBram Moolenaar endif 2905c73622aSBram Moolenaar 2915c73622aSBram Moolenaar let buf = bufnr(changelog) 29257657d85SBram Moolenaar if buf != -1 29357657d85SBram Moolenaar if bufwinnr(buf) != -1 2942c7a7638SBram Moolenaar execute bufwinnr(buf) . 'wincmd w' 29557657d85SBram Moolenaar else 2962c7a7638SBram Moolenaar execute 'sbuffer' buf 29757657d85SBram Moolenaar endif 298071d4279SBram Moolenaar else 2995c73622aSBram Moolenaar execute 'split' fnameescape(changelog) 300071d4279SBram Moolenaar endif 301071d4279SBram Moolenaar 3025c73622aSBram Moolenaar call s:new_changelog_entry(prefix) 303071d4279SBram Moolenaar endfunction 3045c73622aSBram Moolenaar 3055c73622aSBram Moolenaar let &cpo = s:cpo_save 3065c73622aSBram Moolenaar unlet s:cpo_save 307071d4279SBram Moolenaarendif 308