7
|
1 " Vim filetype plugin file
|
375
|
2 " Language: generic Changelog file
|
839
|
3 " Maintainer: Nikolai Weibull <now@bitwi.se>
|
5618
|
4 " Latest Revision: 2014-01-10
|
7
|
5 " Variables:
|
839
|
6 " g:changelog_timeformat (deprecated: use g:changelog_dateformat instead) -
|
375
|
7 " description: the timeformat used in ChangeLog entries.
|
|
8 " default: "%Y-%m-%d".
|
839
|
9 " g:changelog_dateformat -
|
|
10 " description: the format sent to strftime() to generate a date string.
|
|
11 " default: "%Y-%m-%d".
|
7
|
12 " g:changelog_username -
|
375
|
13 " description: the username to use in ChangeLog entries
|
|
14 " default: try to deduce it from environment variables and system files.
|
7
|
15 " Local Mappings:
|
|
16 " <Leader>o -
|
375
|
17 " adds a new changelog entry for the current user for the current date.
|
7
|
18 " Global Mappings:
|
|
19 " <Leader>o -
|
375
|
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.
|
7
|
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
|
839
|
31 if &filetype == 'changelog'
|
|
32 if exists('b:did_ftplugin')
|
7
|
33 finish
|
|
34 endif
|
|
35 let b:did_ftplugin = 1
|
|
36
|
375
|
37 let s:cpo_save = &cpo
|
|
38 set cpo&vim
|
7
|
39
|
839
|
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
|
7
|
47 endif
|
|
48
|
2034
|
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
|
7
|
81
|
2034
|
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
|
7
|
86 endif
|
2034
|
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
|
7
|
98
|
2034
|
99 function! s:try_reading_file(path)
|
|
100 try
|
|
101 return readfile(a:path)
|
2826
|
102 catch
|
|
103 return []
|
2034
|
104 endtry
|
|
105 endfunction
|
7
|
106
|
2034
|
107 function! s:passwd_field(line, field)
|
|
108 let fields = split(a:line, ':', 1)
|
3830
|
109 if len(fields) < a:field
|
2034
|
110 return ""
|
|
111 endif
|
3830
|
112 return fields[a:field - 1]
|
2034
|
113 endfunction
|
7
|
114
|
2034
|
115 function! s:capitalize(word)
|
|
116 return toupper(a:word[0]) . strpart(a:word, 1)
|
|
117 endfunction
|
|
118
|
|
119 function! s:hostname()
|
|
120 return s:trimmed_system_with_default('hostname', 'localhost')
|
|
121 endfunction
|
7
|
122
|
839
|
123 " Format used for new date entries.
|
|
124 if !exists('g:changelog_new_date_format')
|
5568
|
125 let g:changelog_new_date_format = "%d %u\n\n\t* %p%c\n\n"
|
7
|
126 endif
|
|
127
|
839
|
128 " Format used for new entries to current date entry.
|
|
129 if !exists('g:changelog_new_entry_format')
|
5568
|
130 let g:changelog_new_entry_format = "\t* %p%c"
|
7
|
131 endif
|
|
132
|
839
|
133 " Regular expression used to find a given date entry.
|
|
134 if !exists('g:changelog_date_entry_search')
|
7
|
135 let g:changelog_date_entry_search = '^\s*%d\_s*%u'
|
|
136 endif
|
|
137
|
1213
|
138 " Regular expression used to find the end of a date entry
|
|
139 if !exists('g:changelog_date_end_entry_search')
|
1621
|
140 let g:changelog_date_end_entry_search = '^\s*$'
|
1213
|
141 endif
|
|
142
|
|
143
|
839
|
144 " Substitutes specific items in new date-entry formats and search strings.
|
|
145 " Can be done with substitute of course, but unclean, and need \@! then.
|
5568
|
146 function! s:substitute_items(str, date, user, prefix)
|
7
|
147 let str = a:str
|
5568
|
148 let middles = {'%': '%', 'd': a:date, 'u': a:user, 'p': a:prefix, 'c': '{cursor}'}
|
7
|
149 let i = stridx(str, '%')
|
|
150 while i != -1
|
839
|
151 let inc = 0
|
|
152 if has_key(middles, str[i + 1])
|
|
153 let mid = middles[str[i + 1]]
|
|
154 let str = strpart(str, 0, i) . mid . strpart(str, i + 2)
|
5618
|
155 let inc = strlen(mid) - 1
|
7
|
156 endif
|
839
|
157 let i = stridx(str, '%', i + 1 + inc)
|
7
|
158 endwhile
|
|
159 return str
|
|
160 endfunction
|
|
161
|
839
|
162 " Position the cursor once we've done all the funky substitution.
|
7
|
163 function! s:position_cursor()
|
|
164 if search('{cursor}') > 0
|
839
|
165 let lnum = line('.')
|
|
166 let line = getline(lnum)
|
7
|
167 let cursor = stridx(line, '{cursor}')
|
839
|
168 call setline(lnum, substitute(line, '{cursor}', '', ''))
|
7
|
169 endif
|
|
170 startinsert!
|
|
171 endfunction
|
|
172
|
839
|
173 " Internal function to create a new entry in the ChangeLog.
|
5568
|
174 function! s:new_changelog_entry(prefix)
|
839
|
175 " Deal with 'paste' option.
|
7
|
176 let save_paste = &paste
|
|
177 let &paste = 1
|
839
|
178 call cursor(1, 1)
|
|
179 " Look for an entry for today by our user.
|
|
180 let date = strftime(g:changelog_dateformat)
|
7
|
181 let search = s:substitute_items(g:changelog_date_entry_search, date,
|
5568
|
182 \ s:username(), a:prefix)
|
7
|
183 if search(search) > 0
|
839
|
184 " Ok, now we look for the end of the date entry, and add an entry.
|
|
185 call cursor(nextnonblank(line('.') + 1), 1)
|
1213
|
186 if search(g:changelog_date_end_entry_search, 'W') > 0
|
2034
|
187 let p = (line('.') == line('$')) ? line('.') : line('.') - 1
|
839
|
188 else
|
|
189 let p = line('.')
|
|
190 endif
|
5568
|
191 let ls = split(s:substitute_items(g:changelog_new_entry_format, '', '', a:prefix),
|
839
|
192 \ '\n')
|
|
193 call append(p, ls)
|
|
194 call cursor(p + 1, 1)
|
7
|
195 else
|
839
|
196 " Flag for removing empty lines at end of new ChangeLogs.
|
7
|
197 let remove_empty = line('$') == 1
|
|
198
|
839
|
199 " No entry today, so create a date-user header and insert an entry.
|
7
|
200 let todays_entry = s:substitute_items(g:changelog_new_date_format,
|
5568
|
201 \ date, s:username(), a:prefix)
|
839
|
202 " Make sure we have a cursor positioning.
|
7
|
203 if stridx(todays_entry, '{cursor}') == -1
|
839
|
204 let todays_entry = todays_entry . '{cursor}'
|
7
|
205 endif
|
|
206
|
839
|
207 " Now do the work.
|
|
208 call append(0, split(todays_entry, '\n'))
|
5568
|
209
|
839
|
210 " Remove empty lines at end of file.
|
7
|
211 if remove_empty
|
839
|
212 $-/^\s*$/-1,$delete
|
7
|
213 endif
|
|
214
|
839
|
215 " Reposition cursor once we're done.
|
|
216 call cursor(1, 1)
|
7
|
217 endif
|
|
218
|
|
219 call s:position_cursor()
|
|
220
|
|
221 " And reset 'paste' option
|
|
222 let &paste = save_paste
|
|
223 endfunction
|
|
224
|
|
225 if exists(":NewChangelogEntry") != 2
|
5568
|
226 noremap <buffer> <silent> <Leader>o <Esc>:call <SID>new_changelog_entry('')<CR>
|
|
227 command! -nargs=0 NewChangelogEntry call s:new_changelog_entry('')
|
7
|
228 endif
|
|
229
|
839
|
230 let b:undo_ftplugin = "setl com< fo< et< ai<"
|
7
|
231
|
|
232 setlocal comments=
|
|
233 setlocal formatoptions+=t
|
|
234 setlocal noexpandtab
|
375
|
235 setlocal autoindent
|
7
|
236
|
839
|
237 if &textwidth == 0
|
|
238 setlocal textwidth=78
|
|
239 let b:undo_ftplugin .= " tw<"
|
|
240 endif
|
|
241
|
375
|
242 let &cpo = s:cpo_save
|
|
243 unlet s:cpo_save
|
7
|
244 else
|
2034
|
245 let s:cpo_save = &cpo
|
|
246 set cpo&vim
|
|
247
|
7
|
248 " Add the Changelog opening mapping
|
2034
|
249 nnoremap <silent> <Leader>o :call <SID>open_changelog()<CR>
|
7
|
250
|
|
251 function! s:open_changelog()
|
2034
|
252 let path = expand('%:p:h')
|
|
253 if exists('b:changelog_path')
|
|
254 let changelog = b:changelog_path
|
|
255 else
|
|
256 if exists('b:changelog_name')
|
|
257 let name = b:changelog_name
|
|
258 else
|
|
259 let name = 'ChangeLog'
|
|
260 endif
|
|
261 while isdirectory(path)
|
|
262 let changelog = path . '/' . name
|
|
263 if filereadable(changelog)
|
|
264 break
|
|
265 endif
|
|
266 let parent = substitute(path, '/\+[^/]*$', "", "")
|
|
267 if path == parent
|
|
268 break
|
|
269 endif
|
|
270 let path = parent
|
|
271 endwhile
|
|
272 endif
|
|
273 if !filereadable(changelog)
|
839
|
274 return
|
|
275 endif
|
2034
|
276
|
|
277 if exists('b:changelog_entry_prefix')
|
|
278 let prefix = call(b:changelog_entry_prefix, [])
|
|
279 else
|
5568
|
280 let prefix = substitute(strpart(expand('%:p'), strlen(path)), '^/\+', "", "")
|
2034
|
281 endif
|
|
282
|
|
283 let buf = bufnr(changelog)
|
839
|
284 if buf != -1
|
|
285 if bufwinnr(buf) != -1
|
1213
|
286 execute bufwinnr(buf) . 'wincmd w'
|
7
|
287 else
|
1213
|
288 execute 'sbuffer' buf
|
7
|
289 endif
|
839
|
290 else
|
2034
|
291 execute 'split' fnameescape(changelog)
|
839
|
292 endif
|
7
|
293
|
2034
|
294 call s:new_changelog_entry(prefix)
|
7
|
295 endfunction
|
2034
|
296
|
|
297 let &cpo = s:cpo_save
|
|
298 unlet s:cpo_save
|
7
|
299 endif
|