blob: ab73949be5d46a95c78ee561116a9c05a335de1e [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001" Vim filetype plugin file
Bram Moolenaard042dc82015-11-24 19:18:36 +01002" Language: generic Changelog file
3" Maintainer: Martin Florian <marfl@posteo.de>
4" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
Bram Moolenaar079ba762021-10-23 12:08:41 +01005" Latest Revision: 2021-10-17
Bram Moolenaar071d4272004-06-13 20:20:40 +00006" Variables:
Bram Moolenaar57657d82006-04-21 22:12:41 +00007" g:changelog_timeformat (deprecated: use g:changelog_dateformat instead) -
Bram Moolenaar42eeac32005-06-29 22:40:58 +00008" description: the timeformat used in ChangeLog entries.
9" default: "%Y-%m-%d".
Bram Moolenaar57657d82006-04-21 22:12:41 +000010" g:changelog_dateformat -
11" description: the format sent to strftime() to generate a date string.
12" default: "%Y-%m-%d".
Bram Moolenaar071d4272004-06-13 20:20:40 +000013" g:changelog_username -
Bram Moolenaar42eeac32005-06-29 22:40:58 +000014" description: the username to use in ChangeLog entries
15" default: try to deduce it from environment variables and system files.
Bram Moolenaar071d4272004-06-13 20:20:40 +000016" Local Mappings:
17" <Leader>o -
Bram Moolenaar42eeac32005-06-29 22:40:58 +000018" adds a new changelog entry for the current user for the current date.
Bram Moolenaar071d4272004-06-13 20:20:40 +000019" Global Mappings:
20" <Leader>o -
Bram Moolenaar42eeac32005-06-29 22:40:58 +000021" 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.
Bram Moolenaar071d4272004-06-13 20:20:40 +000024" 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
Bram Moolenaar57657d82006-04-21 22:12:41 +000032if &filetype == 'changelog'
33 if exists('b:did_ftplugin')
Bram Moolenaar071d4272004-06-13 20:20:40 +000034 finish
35 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000036 let b:did_ftplugin = 1
37
Bram Moolenaar42eeac32005-06-29 22:40:58 +000038 let s:cpo_save = &cpo
39 set cpo&vim
Bram Moolenaar071d4272004-06-13 20:20:40 +000040
Bram Moolenaar57657d82006-04-21 22:12:41 +000041 " 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
Bram Moolenaar071d4272004-06-13 20:20:40 +000048 endif
49
Bram Moolenaar5c736222010-01-06 20:54:52 +010050 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
Bram Moolenaar071d4272004-06-13 20:20:40 +000057 endif
Christian Brabandtf7ac0ef2023-09-06 20:41:25 +020058 let s:default_login = 'unknown'
Bram Moolenaar079ba762021-10-23 12:08:41 +010059
D. Ben Knoblecd8a3ea2023-11-04 05:11:17 -040060 " Disabled by default for security reasons.
61 if dist#vim#IsSafeExecutable('changelog', 'whoami')
Christian Brabandtf7ac0ef2023-09-06 20:41:25 +020062 let login = s:login()
63 else
64 let login = s:default_login
65 endif
Bram Moolenaar5c736222010-01-06 20:54:52 +010066 return printf('%s <%s@%s>', s:name(login), login, s:hostname())
67 endfunction
68
69 function! s:login()
Christian Brabandtf7ac0ef2023-09-06 20:41:25 +020070 return s:trimmed_system_with_default('whoami', s:default_login)
Bram Moolenaar5c736222010-01-06 20:54:52 +010071 endfunction
72
73 function! s:trimmed_system_with_default(command, default)
74 return s:first_line(s:system_with_default(a:command, a:default))
75 endfunction
76
77 function! s:system_with_default(command, default)
78 let output = system(a:command)
79 if v:shell_error
Christian Brabandtf7ac0ef2023-09-06 20:41:25 +020080 return a:default
Bram Moolenaar5c736222010-01-06 20:54:52 +010081 endif
82 return output
83 endfunction
84
85 function! s:first_line(string)
86 return substitute(a:string, '\n.*$', "", "")
87 endfunction
88
89 function! s:name(login)
90 for name in [s:gecos_name(a:login), $NAME, s:capitalize(a:login)]
91 if name != ""
92 return name
93 endif
94 endfor
95 endfunction
96
97 function! s:gecos_name(login)
98 for line in s:try_reading_file('/etc/passwd')
99 if line =~ '^' . a:login . ':'
100 return substitute(s:passwd_field(line, 5), '&', s:capitalize(a:login), "")
101 endif
102 endfor
103 return ""
104 endfunction
105
106 function! s:try_reading_file(path)
107 try
108 return readfile(a:path)
Bram Moolenaar446beb42011-05-10 17:18:44 +0200109 catch
110 return []
Bram Moolenaar5c736222010-01-06 20:54:52 +0100111 endtry
Bram Moolenaar5c736222010-01-06 20:54:52 +0100112 endfunction
113
114 function! s:passwd_field(line, field)
115 let fields = split(a:line, ':', 1)
Bram Moolenaard09acef2012-09-21 14:54:30 +0200116 if len(fields) < a:field
Bram Moolenaar5c736222010-01-06 20:54:52 +0100117 return ""
118 endif
Bram Moolenaard09acef2012-09-21 14:54:30 +0200119 return fields[a:field - 1]
Bram Moolenaar5c736222010-01-06 20:54:52 +0100120 endfunction
121
122 function! s:capitalize(word)
123 return toupper(a:word[0]) . strpart(a:word, 1)
124 endfunction
125
126 function! s:hostname()
127 return s:trimmed_system_with_default('hostname', 'localhost')
128 endfunction
Bram Moolenaar071d4272004-06-13 20:20:40 +0000129
Bram Moolenaar57657d82006-04-21 22:12:41 +0000130 " Format used for new date entries.
131 if !exists('g:changelog_new_date_format')
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100132 let g:changelog_new_date_format = "%d %u\n\n\t* %p%c\n\n"
Bram Moolenaar071d4272004-06-13 20:20:40 +0000133 endif
134
Bram Moolenaar57657d82006-04-21 22:12:41 +0000135 " Format used for new entries to current date entry.
136 if !exists('g:changelog_new_entry_format')
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100137 let g:changelog_new_entry_format = "\t* %p%c"
Bram Moolenaar071d4272004-06-13 20:20:40 +0000138 endif
139
Bram Moolenaar57657d82006-04-21 22:12:41 +0000140 " Regular expression used to find a given date entry.
141 if !exists('g:changelog_date_entry_search')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000142 let g:changelog_date_entry_search = '^\s*%d\_s*%u'
143 endif
144
Bram Moolenaar2c7a7632007-05-10 18:19:11 +0000145 " Regular expression used to find the end of a date entry
146 if !exists('g:changelog_date_end_entry_search')
Bram Moolenaar446cb832008-06-24 21:56:24 +0000147 let g:changelog_date_end_entry_search = '^\s*$'
Bram Moolenaar2c7a7632007-05-10 18:19:11 +0000148 endif
149
150
Bram Moolenaar57657d82006-04-21 22:12:41 +0000151 " Substitutes specific items in new date-entry formats and search strings.
152 " Can be done with substitute of course, but unclean, and need \@! then.
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100153 function! s:substitute_items(str, date, user, prefix)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000154 let str = a:str
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100155 let middles = {'%': '%', 'd': a:date, 'u': a:user, 'p': a:prefix, 'c': '{cursor}'}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000156 let i = stridx(str, '%')
157 while i != -1
Bram Moolenaar57657d82006-04-21 22:12:41 +0000158 let inc = 0
159 if has_key(middles, str[i + 1])
160 let mid = middles[str[i + 1]]
161 let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
Bram Moolenaar8d043172014-01-23 14:24:41 +0100162 let inc = strlen(mid) - 1
Bram Moolenaar071d4272004-06-13 20:20:40 +0000163 endif
Bram Moolenaar57657d82006-04-21 22:12:41 +0000164 let i = stridx(str, '%', i + 1 + inc)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000165 endwhile
166 return str
167 endfunction
168
Bram Moolenaar57657d82006-04-21 22:12:41 +0000169 " Position the cursor once we've done all the funky substitution.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000170 function! s:position_cursor()
171 if search('{cursor}') > 0
Bram Moolenaar57657d82006-04-21 22:12:41 +0000172 let lnum = line('.')
173 let line = getline(lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000174 let cursor = stridx(line, '{cursor}')
Bram Moolenaar57657d82006-04-21 22:12:41 +0000175 call setline(lnum, substitute(line, '{cursor}', '', ''))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000176 endif
Bram Moolenaard042dc82015-11-24 19:18:36 +0100177 startinsert
Bram Moolenaar071d4272004-06-13 20:20:40 +0000178 endfunction
179
Bram Moolenaar57657d82006-04-21 22:12:41 +0000180 " Internal function to create a new entry in the ChangeLog.
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100181 function! s:new_changelog_entry(prefix)
Bram Moolenaar57657d82006-04-21 22:12:41 +0000182 " Deal with 'paste' option.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000183 let save_paste = &paste
184 let &paste = 1
Bram Moolenaar57657d82006-04-21 22:12:41 +0000185 call cursor(1, 1)
186 " Look for an entry for today by our user.
187 let date = strftime(g:changelog_dateformat)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000188 let search = s:substitute_items(g:changelog_date_entry_search, date,
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100189 \ s:username(), a:prefix)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000190 if search(search) > 0
Bram Moolenaar57657d82006-04-21 22:12:41 +0000191 " Ok, now we look for the end of the date entry, and add an entry.
192 call cursor(nextnonblank(line('.') + 1), 1)
Bram Moolenaar2c7a7632007-05-10 18:19:11 +0000193 if search(g:changelog_date_end_entry_search, 'W') > 0
Bram Moolenaar5c736222010-01-06 20:54:52 +0100194 let p = (line('.') == line('$')) ? line('.') : line('.') - 1
Bram Moolenaar57657d82006-04-21 22:12:41 +0000195 else
196 let p = line('.')
197 endif
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100198 let ls = split(s:substitute_items(g:changelog_new_entry_format, '', '', a:prefix),
Bram Moolenaar57657d82006-04-21 22:12:41 +0000199 \ '\n')
200 call append(p, ls)
201 call cursor(p + 1, 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000202 else
Bram Moolenaar57657d82006-04-21 22:12:41 +0000203 " Flag for removing empty lines at end of new ChangeLogs.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000204 let remove_empty = line('$') == 1
205
Bram Moolenaar57657d82006-04-21 22:12:41 +0000206 " No entry today, so create a date-user header and insert an entry.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000207 let todays_entry = s:substitute_items(g:changelog_new_date_format,
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100208 \ date, s:username(), a:prefix)
Bram Moolenaar57657d82006-04-21 22:12:41 +0000209 " Make sure we have a cursor positioning.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000210 if stridx(todays_entry, '{cursor}') == -1
Bram Moolenaar57657d82006-04-21 22:12:41 +0000211 let todays_entry = todays_entry . '{cursor}'
Bram Moolenaar071d4272004-06-13 20:20:40 +0000212 endif
213
Bram Moolenaar57657d82006-04-21 22:12:41 +0000214 " Now do the work.
215 call append(0, split(todays_entry, '\n'))
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100216
Bram Moolenaar57657d82006-04-21 22:12:41 +0000217 " Remove empty lines at end of file.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000218 if remove_empty
Bram Moolenaar57657d82006-04-21 22:12:41 +0000219 $-/^\s*$/-1,$delete
Bram Moolenaar071d4272004-06-13 20:20:40 +0000220 endif
221
Bram Moolenaar57657d82006-04-21 22:12:41 +0000222 " Reposition cursor once we're done.
223 call cursor(1, 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000224 endif
225
226 call s:position_cursor()
227
228 " And reset 'paste' option
229 let &paste = save_paste
230 endfunction
231
Bram Moolenaar57657d82006-04-21 22:12:41 +0000232 let b:undo_ftplugin = "setl com< fo< et< ai<"
Bram Moolenaar071d4272004-06-13 20:20:40 +0000233
Bram Moolenaar071d4272004-06-13 20:20:40 +0000234 setlocal comments=
235 setlocal formatoptions+=t
236 setlocal noexpandtab
Bram Moolenaar42eeac32005-06-29 22:40:58 +0000237 setlocal autoindent
Bram Moolenaar071d4272004-06-13 20:20:40 +0000238
Bram Moolenaar57657d82006-04-21 22:12:41 +0000239 if &textwidth == 0
240 setlocal textwidth=78
241 let b:undo_ftplugin .= " tw<"
242 endif
243
Bram Moolenaar079ba762021-10-23 12:08:41 +0100244 if !exists("no_plugin_maps") && !exists("no_changelog_maps") && exists(":NewChangelogEntry") != 2
245 nnoremap <buffer> <silent> <Leader>o :<C-u>call <SID>new_changelog_entry('')<CR>
246 xnoremap <buffer> <silent> <Leader>o :<C-u>call <SID>new_changelog_entry('')<CR>
247 command! -buffer -nargs=0 NewChangelogEntry call s:new_changelog_entry('')
248 let b:undo_ftplugin .= " | sil! exe 'nunmap <buffer> <Leader>o'" .
249 \ " | sil! exe 'vunmap <buffer> <Leader>o'" .
250 \ " | sil! delc NewChangelogEntry"
251 endif
252
Bram Moolenaar42eeac32005-06-29 22:40:58 +0000253 let &cpo = s:cpo_save
254 unlet s:cpo_save
Bram Moolenaar071d4272004-06-13 20:20:40 +0000255else
Bram Moolenaar5c736222010-01-06 20:54:52 +0100256 let s:cpo_save = &cpo
257 set cpo&vim
258
Bram Moolenaar079ba762021-10-23 12:08:41 +0100259 if !exists("no_plugin_maps") && !exists("no_changelog_maps")
260 " Add the Changelog opening mapping
261 nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
262 let b:undo_ftplugin .= " | silent! exe 'nunmap <buffer> <Leader>o"
263 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000264
265 function! s:open_changelog()
Bram Moolenaar5c736222010-01-06 20:54:52 +0100266 let path = expand('%:p:h')
267 if exists('b:changelog_path')
268 let changelog = b:changelog_path
269 else
270 if exists('b:changelog_name')
271 let name = b:changelog_name
272 else
273 let name = 'ChangeLog'
274 endif
275 while isdirectory(path)
276 let changelog = path . '/' . name
277 if filereadable(changelog)
278 break
279 endif
280 let parent = substitute(path, '/\+[^/]*$', "", "")
281 if path == parent
282 break
283 endif
284 let path = parent
285 endwhile
286 endif
287 if !filereadable(changelog)
Bram Moolenaar57657d82006-04-21 22:12:41 +0000288 return
Bram Moolenaar071d4272004-06-13 20:20:40 +0000289 endif
Bram Moolenaar5c736222010-01-06 20:54:52 +0100290
291 if exists('b:changelog_entry_prefix')
292 let prefix = call(b:changelog_entry_prefix, [])
293 else
Bram Moolenaar4b550b42013-12-15 10:02:33 +0100294 let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "")
Bram Moolenaar5c736222010-01-06 20:54:52 +0100295 endif
296
297 let buf = bufnr(changelog)
Bram Moolenaar57657d82006-04-21 22:12:41 +0000298 if buf != -1
299 if bufwinnr(buf) != -1
Bram Moolenaar2c7a7632007-05-10 18:19:11 +0000300 execute bufwinnr(buf) . 'wincmd w'
Bram Moolenaar57657d82006-04-21 22:12:41 +0000301 else
Bram Moolenaar2c7a7632007-05-10 18:19:11 +0000302 execute 'sbuffer' buf
Bram Moolenaar57657d82006-04-21 22:12:41 +0000303 endif
304 else
Bram Moolenaar5c736222010-01-06 20:54:52 +0100305 execute 'split' fnameescape(changelog)
Bram Moolenaar57657d82006-04-21 22:12:41 +0000306 endif
307
Bram Moolenaar5c736222010-01-06 20:54:52 +0100308 call s:new_changelog_entry(prefix)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000309 endfunction
Bram Moolenaar5c736222010-01-06 20:54:52 +0100310
311 let &cpo = s:cpo_save
312 unlet s:cpo_save
Bram Moolenaar071d4272004-06-13 20:20:40 +0000313endif