Mercurial > vim-lawrencium
comparison plugin/lawrencium.vim @ 139:065625e1bb31
Split plugin file into multiple extensions.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 13 Jun 2016 09:32:34 -0700 |
parents | a2d823c82e5f |
children | 78176bdfe0be |
comparison
equal
deleted
inserted
replaced
138:a2d823c82e5f | 139:065625e1bb31 |
---|---|
51 | 51 |
52 if !exists('g:lawrencium_record_start_in_working_buffer') | 52 if !exists('g:lawrencium_record_start_in_working_buffer') |
53 let g:lawrencium_record_start_in_working_buffer = 0 | 53 let g:lawrencium_record_start_in_working_buffer = 0 |
54 endif | 54 endif |
55 | 55 |
56 " }}} | 56 if !exists('g:lawrencium_extensions') |
57 | 57 let g:lawrencium_extensions = [] |
58 " Utility {{{ | 58 endif |
59 | |
60 " Strips the ending slash in a path. | |
61 function! s:stripslash(path) | |
62 return fnamemodify(a:path, ':s?[/\\]$??') | |
63 endfunction | |
64 | |
65 " Returns whether a path is absolute. | |
66 function! s:isabspath(path) | |
67 return a:path =~# '\v^(\w\:)?[/\\]' | |
68 endfunction | |
69 | |
70 " Normalizes the slashes in a path. | |
71 function! s:normalizepath(path) | |
72 if exists('+shellslash') && &shellslash | |
73 return substitute(a:path, '\v/', '\\', 'g') | |
74 elseif has('win32') | |
75 return substitute(a:path, '\v/', '\\', 'g') | |
76 else | |
77 return a:path | |
78 endif | |
79 endfunction | |
80 | |
81 " Shell-slashes the path (opposite of `normalizepath`). | |
82 function! s:shellslash(path) | |
83 if exists('+shellslash') && !&shellslash | |
84 return substitute(a:path, '\v\\', '/', 'g') | |
85 else | |
86 return a:path | |
87 endif | |
88 endfunction | |
89 | |
90 " Like tempname() but with some control over the filename. | |
91 function! s:tempname(name, ...) | |
92 let l:path = tempname() | |
93 let l:result = fnamemodify(l:path, ':h') . '/' . a:name . fnamemodify(l:path, ':t') | |
94 if a:0 > 0 | |
95 let l:result = l:result . a:1 | |
96 endif | |
97 return l:result | |
98 endfunction | |
99 | |
100 " Delete a temporary file if it exists. | |
101 function! s:clean_tempfile(path) | |
102 if filewritable(a:path) | |
103 call s:trace("Cleaning up temporary file: " . a:path) | |
104 call delete(a:path) | |
105 endif | |
106 endfunction | |
107 | |
108 " Prints a message if debug tracing is enabled. | |
109 function! s:trace(message, ...) | |
110 if g:lawrencium_trace || (a:0 && a:1) | |
111 let l:message = "lawrencium: " . a:message | |
112 echom l:message | |
113 endif | |
114 endfunction | |
115 | |
116 " Prints an error message with 'lawrencium error' prefixed to it. | |
117 function! s:error(message) | |
118 echom "lawrencium error: " . a:message | |
119 endfunction | |
120 | |
121 " Throw a Lawrencium exception message. | |
122 function! s:throw(message) | |
123 let v:errmsg = "lawrencium: " . a:message | |
124 throw v:errmsg | |
125 endfunction | |
126 | |
127 " Finds the repository root given a path inside that repository. | |
128 " Throw an error if not repository is found. | |
129 function! s:find_repo_root(path) | |
130 let l:path = s:stripslash(a:path) | |
131 let l:previous_path = "" | |
132 while l:path != l:previous_path | |
133 if isdirectory(l:path . '/.hg') | |
134 return s:normalizepath(simplify(fnamemodify(l:path, ':p'))) | |
135 endif | |
136 let l:previous_path = l:path | |
137 let l:path = fnamemodify(l:path, ':h') | |
138 endwhile | |
139 call s:throw("No Mercurial repository found above: " . a:path) | |
140 endfunction | |
141 | |
142 " Given a Lawrencium path (e.g: 'lawrencium:///repo/root_dir//foo/bar/file.py//rev=34'), extract | |
143 " the repository root, relative file path and revision number/changeset ID. | |
144 " | |
145 " If a second argument exists, it must be: | |
146 " - `relative`: to make the file path relative to the repository root. | |
147 " - `absolute`: to make the file path absolute. | |
148 " | |
149 function! s:parse_lawrencium_path(lawrencium_path, ...) | |
150 let l:repo_path = s:shellslash(a:lawrencium_path) | |
151 let l:repo_path = substitute(l:repo_path, '\\ ', ' ', 'g') | |
152 if l:repo_path =~? '\v^lawrencium://' | |
153 let l:repo_path = strpart(l:repo_path, strlen('lawrencium://')) | |
154 endif | |
155 | |
156 let l:root_dir = '' | |
157 let l:at_idx = stridx(l:repo_path, '//') | |
158 if l:at_idx >= 0 | |
159 let l:root_dir = strpart(l:repo_path, 0, l:at_idx) | |
160 let l:repo_path = strpart(l:repo_path, l:at_idx + 2) | |
161 endif | |
162 | |
163 let l:value = '' | |
164 let l:action = '' | |
165 let l:actionidx = stridx(l:repo_path, '//') | |
166 if l:actionidx >= 0 | |
167 let l:action = strpart(l:repo_path, l:actionidx + 2) | |
168 let l:repo_path = strpart(l:repo_path, 0, l:actionidx) | |
169 | |
170 let l:equalidx = stridx(l:action, '=') | |
171 if l:equalidx >= 0 | |
172 let l:value = strpart(l:action, l:equalidx + 1) | |
173 let l:action = strpart(l:action, 0, l:equalidx) | |
174 endif | |
175 endif | |
176 | |
177 if a:0 > 0 | |
178 execute 'cd! ' . fnameescape(l:root_dir) | |
179 if a:1 == 'relative' | |
180 let l:repo_path = fnamemodify(l:repo_path, ':.') | |
181 elseif a:1 == 'absolute' | |
182 let l:repo_path = fnamemodify(l:repo_path, ':p') | |
183 endif | |
184 execute 'cd! -' | |
185 endif | |
186 | |
187 let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value } | |
188 return l:result | |
189 endfunction | |
190 | |
191 " Finds a window whose displayed buffer has a given variable | |
192 " set to the given value. | |
193 function! s:find_buffer_window(varname, varvalue) abort | |
194 for wnr in range(1, winnr('$')) | |
195 let l:bnr = winbufnr(wnr) | |
196 if getbufvar(l:bnr, a:varname) == a:varvalue | |
197 return l:wnr | |
198 endif | |
199 endfor | |
200 return -1 | |
201 endfunction | |
202 | |
203 " Opens a buffer in a way that makes it easy to delete it later: | |
204 " - if the about-to-be previous buffer doesn't have a given variable, | |
205 " just open the new buffer. | |
206 " - if the about-to-be previous buffer has a given variable, open the | |
207 " new buffer with the `keepalt` option to make it so that the | |
208 " actual previous buffer (returned by things like `bufname('#')`) | |
209 " is the original buffer that was there before the first deletable | |
210 " buffer was opened. | |
211 function! s:edit_deletable_buffer(varname, varvalue, path) abort | |
212 let l:edit_cmd = 'edit ' | |
213 if getbufvar('%', a:varname) != '' | |
214 let l:edit_cmd = 'keepalt edit ' | |
215 endif | |
216 execute l:edit_cmd . fnameescape(a:path) | |
217 call setbufvar('%', a:varname, a:varvalue) | |
218 endfunction | |
219 | |
220 " Deletes all buffers that have a given variable set to a given value. | |
221 " For each buffer, if it is not shown in any window, it will be just deleted. | |
222 " If it is shown in a window, that window will be switched to the alternate | |
223 " buffer before the buffer is deleted, unless the `lawrencium_quit_on_delete` | |
224 " variable is set to `1`, in which case the window is closed too. | |
225 function! s:delete_dependency_buffers(varname, varvalue) abort | |
226 let l:cur_winnr = winnr() | |
227 for bnr in range(1, bufnr('$')) | |
228 if getbufvar(bnr, a:varname) == a:varvalue | |
229 " Delete this buffer if it is not shown in any window. | |
230 " Otherwise, display the alternate buffer before deleting | |
231 " it so the window is not closed. | |
232 let l:bwnr = bufwinnr(bnr) | |
233 if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1 | |
234 if bufloaded(l:bnr) | |
235 call s:trace("Deleting dependency buffer " . bnr) | |
236 execute "bdelete! " . bnr | |
237 else | |
238 call s:trace("Dependency buffer " . bnr . " is already unladed.") | |
239 endif | |
240 else | |
241 execute l:bwnr . "wincmd w" | |
242 " TODO: better handle case where there's no previous/alternate buffer? | |
243 let l:prev_bnr = bufnr('#') | |
244 if l:prev_bnr > 0 && bufloaded(l:prev_bnr) | |
245 execute "buffer " . l:prev_bnr | |
246 if bufloaded(l:bnr) | |
247 call s:trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr) | |
248 execute "bdelete! " . bnr | |
249 else | |
250 call s:trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr) | |
251 endif | |
252 else | |
253 call s:trace("Deleting dependency buffer " . bnr . " and window.") | |
254 bdelete! | |
255 endif | |
256 endif | |
257 endif | |
258 endfor | |
259 if l:cur_winnr != winnr() | |
260 call s:trace("Returning to window " . l:cur_winnr) | |
261 execute l:cur_winnr . "wincmd w" | |
262 endif | |
263 endfunction | |
264 | |
265 " Clean up all the 'HG:' lines from a commit message, and see if there's | |
266 " any message left (Mercurial does this automatically, usually, but | |
267 " apparently not when you feed it a log file...). | |
268 function! s:clean_commit_file(log_file) abort | |
269 let l:lines = readfile(a:log_file) | |
270 call filter(l:lines, "v:val !~# '\\v^HG:'") | |
271 if len(filter(copy(l:lines), "v:val !~# '\\v^\\s*$'")) == 0 | |
272 return 0 | |
273 endif | |
274 call writefile(l:lines, a:log_file) | |
275 return 1 | |
276 endfunction | |
277 | 59 |
278 " }}} | 60 " }}} |
279 | 61 |
280 " Mercurial Repository Object {{{ | 62 " Setup {{{ |
281 | 63 |
282 " Let's define a Mercurial repo 'class' using prototype-based object-oriented | 64 call lawrencium#init() |
283 " programming. | |
284 " | |
285 " The prototype dictionary. | |
286 let s:HgRepo = {} | |
287 | |
288 " Constructor. | |
289 function! s:HgRepo.New(path) abort | |
290 let l:newRepo = copy(self) | |
291 let l:newRepo.root_dir = s:find_repo_root(a:path) | |
292 call s:trace("Built new Mercurial repository object at : " . l:newRepo.root_dir) | |
293 return l:newRepo | |
294 endfunction | |
295 | |
296 " Gets a full path given a repo-relative path. | |
297 function! s:HgRepo.GetFullPath(path) abort | |
298 let l:root_dir = self.root_dir | |
299 if s:isabspath(a:path) | |
300 call s:throw("Expected relative path, got absolute path: " . a:path) | |
301 endif | |
302 return s:normalizepath(l:root_dir . a:path) | |
303 endfunction | |
304 | |
305 " Gets a repo-relative path given any path. | |
306 function! s:HgRepo.GetRelativePath(path) abort | |
307 execute 'lcd! ' . fnameescape(self.root_dir) | |
308 let l:relative_path = fnamemodify(a:path, ':.') | |
309 execute 'lcd! -' | |
310 return l:relative_path | |
311 endfunction | |
312 | |
313 " Gets, and optionally creates, a temp folder for some operation in the `.hg` | |
314 " directory. | |
315 function! s:HgRepo.GetTempDir(path, ...) abort | |
316 let l:tmp_dir = self.GetFullPath('.hg/lawrencium/' . a:path) | |
317 if !isdirectory(l:tmp_dir) | |
318 if a:0 > 0 && !a:1 | |
319 return '' | |
320 endif | |
321 call mkdir(l:tmp_dir, 'p') | |
322 endif | |
323 return l:tmp_dir | |
324 endfunction | |
325 | |
326 " Gets a list of files matching a root-relative pattern. | |
327 " If a flag is passed and is TRUE, a slash will be appended to all | |
328 " directories. | |
329 function! s:HgRepo.Glob(pattern, ...) abort | |
330 let l:root_dir = self.root_dir | |
331 if (a:pattern =~# '\v^[/\\]') | |
332 let l:root_dir = s:stripslash(l:root_dir) | |
333 endif | |
334 let l:matches = split(glob(l:root_dir . a:pattern), '\n') | |
335 if a:0 && a:1 | |
336 for l:idx in range(len(l:matches)) | |
337 if !filereadable(l:matches[l:idx]) | |
338 let l:matches[l:idx] = l:matches[l:idx] . '/' | |
339 endif | |
340 endfor | |
341 endif | |
342 let l:strip_len = len(l:root_dir) | |
343 call map(l:matches, 'v:val[l:strip_len : -1]') | |
344 return l:matches | |
345 endfunction | |
346 | |
347 " Gets a full Mercurial command. | |
348 function! s:HgRepo.GetCommand(command, ...) abort | |
349 " If there's only one argument, and it's a list, then use that as the | |
350 " argument list. | |
351 let l:arg_list = a:000 | |
352 if a:0 == 1 && type(a:1) == type([]) | |
353 let l:arg_list = a:1 | |
354 endif | |
355 let l:prev_shellslash = &shellslash | |
356 setlocal noshellslash | |
357 let l:hg_command = g:lawrencium_hg_executable . ' --repository ' . shellescape(s:stripslash(self.root_dir)) | |
358 let l:hg_command = l:hg_command . ' ' . a:command | |
359 for l:arg in l:arg_list | |
360 let l:hg_command = l:hg_command . ' ' . shellescape(l:arg) | |
361 endfor | |
362 if l:prev_shellslash | |
363 setlocal shellslash | |
364 endif | |
365 return l:hg_command | |
366 endfunction | |
367 | |
368 " Runs a Mercurial command in the repo. | |
369 function! s:HgRepo.RunCommand(command, ...) abort | |
370 let l:all_args = [1, a:command] + a:000 | |
371 return call(self['RunCommandEx'], l:all_args, self) | |
372 endfunction | |
373 | |
374 function! s:HgRepo.RunCommandEx(plain_mode, command, ...) abort | |
375 let l:prev_hgplain = $HGPLAIN | |
376 if a:plain_mode | |
377 let $HGPLAIN = 'true' | |
378 endif | |
379 let l:all_args = [a:command] + a:000 | |
380 let l:hg_command = call(self['GetCommand'], l:all_args, self) | |
381 call s:trace("Running Mercurial command: " . l:hg_command) | |
382 let l:cmd_out = system(l:hg_command) | |
383 if a:plain_mode | |
384 let $HGPLAIN = l:prev_hgplain | |
385 endif | |
386 return l:cmd_out | |
387 endfunction | |
388 | |
389 " Runs a Mercurial command in the repo and reads its output into the current | |
390 " buffer. | |
391 function! s:HgRepo.ReadCommandOutput(command, ...) abort | |
392 function! s:PutOutputIntoBuffer(command_line) | |
393 let l:was_buffer_empty = (line('$') == 1 && getline(1) == '') | |
394 execute '0read!' . escape(a:command_line, '%#\') | |
395 if l:was_buffer_empty " (Always true?) | |
396 " '0read' inserts before the cursor, leaving a blank line which | |
397 " needs to be deleted... but if there are folds in this thing, we | |
398 " must open them all first otherwise we could delete the whole | |
399 " contents of the last fold (since Vim may close them all by | |
400 " default). | |
401 normal! zRG"_dd | |
402 endif | |
403 endfunction | |
404 | |
405 let l:all_args = [a:command] + a:000 | |
406 let l:hg_command = call(self['GetCommand'], l:all_args, self) | |
407 call s:trace("Running Mercurial command: " . l:hg_command) | |
408 call s:PutOutputIntoBuffer(l:hg_command) | |
409 endfunction | |
410 | |
411 " Build a Lawrencium path for the given file and action. | |
412 " By default, the given path will be made relative to the repository root, | |
413 " unless '0' is passed as the 4th argument. | |
414 function! s:HgRepo.GetLawrenciumPath(path, action, value, ...) abort | |
415 let l:path = a:path | |
416 if a:0 == 0 || !a:1 | |
417 let l:path = self.GetRelativePath(a:path) | |
418 endif | |
419 let l:path = fnameescape(l:path) | |
420 let l:result = 'lawrencium://' . s:stripslash(self.root_dir) . '//' . l:path | |
421 if a:action !=? '' | |
422 let l:result = l:result . '//' . a:action | |
423 if a:value !=? '' | |
424 let l:result = l:result . '=' . a:value | |
425 endif | |
426 endif | |
427 return l:result | |
428 endfunction | |
429 | |
430 " Repo cache map. | |
431 let s:buffer_repos = {} | |
432 | |
433 " Get a cached repo. | |
434 function! s:hg_repo(...) abort | |
435 " Use the given path, or the mercurial directory of the current buffer. | |
436 if a:0 == 0 | |
437 if exists('b:mercurial_dir') | |
438 let l:path = b:mercurial_dir | |
439 else | |
440 let l:path = s:find_repo_root(expand('%:p')) | |
441 endif | |
442 else | |
443 let l:path = a:1 | |
444 endif | |
445 " Find a cache repo instance, or make a new one. | |
446 if has_key(s:buffer_repos, l:path) | |
447 return get(s:buffer_repos, l:path) | |
448 else | |
449 let l:repo = s:HgRepo.New(l:path) | |
450 let s:buffer_repos[l:path] = l:repo | |
451 return l:repo | |
452 endif | |
453 endfunction | |
454 | |
455 " Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo. | |
456 " If the file is not in a Mercurial repo, just exit silently. | |
457 function! s:setup_buffer_commands() abort | |
458 call s:trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...") | |
459 let l:do_setup = 1 | |
460 if exists('b:mercurial_dir') | |
461 if b:mercurial_dir =~# '\v^\s*$' | |
462 unlet b:mercurial_dir | |
463 else | |
464 let l:do_setup = 0 | |
465 endif | |
466 endif | |
467 try | |
468 let l:repo = s:hg_repo() | |
469 catch /^lawrencium\:/ | |
470 return | |
471 endtry | |
472 let b:mercurial_dir = l:repo.root_dir | |
473 if exists('b:mercurial_dir') && l:do_setup | |
474 call s:trace("Setting Mercurial commands for buffer '" . bufname('%')) | |
475 call s:trace(" with repo : " . expand(b:mercurial_dir)) | |
476 silent doautocmd User Lawrencium | |
477 endif | |
478 endfunction | |
479 | 65 |
480 augroup lawrencium_detect | 66 augroup lawrencium_detect |
481 autocmd! | 67 autocmd! |
482 autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands() | 68 autocmd BufNewFile,BufReadPost * call lawrencium#setup_buffer_commands() |
483 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif | 69 autocmd VimEnter * if expand('<amatch>')==''|call lawrencium#setup_buffer_commands()|endif |
484 augroup end | 70 augroup end |
485 | 71 |
486 " }}} | |
487 | |
488 " Buffer Object {{{ | |
489 | |
490 " The prototype dictionary. | |
491 let s:Buffer = {} | |
492 | |
493 " Constructor. | |
494 function! s:Buffer.New(number) dict abort | |
495 let l:newBuffer = copy(self) | |
496 let l:newBuffer.nr = a:number | |
497 let l:newBuffer.var_backup = {} | |
498 let l:newBuffer.cmd_names = {} | |
499 let l:newBuffer.on_delete = [] | |
500 let l:newBuffer.on_winleave = [] | |
501 let l:newBuffer.on_unload = [] | |
502 execute 'augroup lawrencium_buffer_' . a:number | |
503 execute ' autocmd!' | |
504 execute ' autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')' | |
505 execute 'augroup end' | |
506 call s:trace("Built new buffer object for buffer: " . a:number) | |
507 return l:newBuffer | |
508 endfunction | |
509 | |
510 function! s:Buffer.GetName(...) dict abort | |
511 let l:name = bufname(self.nr) | |
512 if a:0 > 0 | |
513 let l:name = fnamemodify(l:name, a:1) | |
514 endif | |
515 return l:name | |
516 endfunction | |
517 | |
518 function! s:Buffer.GetVar(var) dict abort | |
519 return getbufvar(self.nr, a:var) | |
520 endfunction | |
521 | |
522 function! s:Buffer.SetVar(var, value) dict abort | |
523 if !has_key(self.var_backup, a:var) | |
524 let self.var_backup[a:var] = getbufvar(self.nr, a:var) | |
525 endif | |
526 return setbufvar(self.nr, a:var, a:value) | |
527 endfunction | |
528 | |
529 function! s:Buffer.RestoreVars() dict abort | |
530 for key in keys(self.var_backup) | |
531 setbufvar(self.nr, key, self.var_backup[key]) | |
532 endfor | |
533 endfunction | |
534 | |
535 function! s:Buffer.DefineCommand(name, ...) dict abort | |
536 if a:0 == 0 | |
537 call s:throw("Not enough parameters for s:Buffer.DefineCommands()") | |
538 endif | |
539 if a:0 == 1 | |
540 let l:flags = '' | |
541 let l:cmd = a:1 | |
542 else | |
543 let l:flags = a:1 | |
544 let l:cmd = a:2 | |
545 endif | |
546 if has_key(self.cmd_names, a:name) | |
547 call s:throw("Command '".a:name."' is already defined in buffer ".self.nr) | |
548 endif | |
549 if bufnr('%') != self.nr | |
550 call s:throw("You must move to buffer ".self.nr."first before defining local commands") | |
551 endif | |
552 let self.cmd_names[a:name] = 1 | |
553 let l:real_flags = '' | |
554 if type(l:flags) == type('') | |
555 let l:real_flags = l:flags | |
556 endif | |
557 execute 'command -buffer '.l:real_flags.' '.a:name.' '.l:cmd | |
558 endfunction | |
559 | |
560 function! s:Buffer.DeleteCommand(name) dict abort | |
561 if !has_key(self.cmd_names, a:name) | |
562 call s:throw("Command '".a:name."' has not been defined in buffer ".self.nr) | |
563 endif | |
564 if bufnr('%') != self.nr | |
565 call s:throw("You must move to buffer ".self.nr."first before deleting local commands") | |
566 endif | |
567 execute 'delcommand '.a:name | |
568 call remove(self.cmd_names, a:name) | |
569 endfunction | |
570 | |
571 function! s:Buffer.DeleteCommands() dict abort | |
572 if bufnr('%') != self.nr | |
573 call s:throw("You must move to buffer ".self.nr."first before deleting local commands") | |
574 endif | |
575 for name in keys(self.cmd_names) | |
576 execute 'delcommand '.name | |
577 endfor | |
578 let self.cmd_names = {} | |
579 endfunction | |
580 | |
581 function! s:Buffer.MoveToFirstWindow() dict abort | |
582 let l:win_nr = bufwinnr(self.nr) | |
583 if l:win_nr < 0 | |
584 if a:0 > 0 && a:1 == 0 | |
585 return 0 | |
586 endif | |
587 call s:throw("No windows currently showing buffer ".self.nr) | |
588 endif | |
589 execute l:win_nr.'wincmd w' | |
590 return 1 | |
591 endfunction | |
592 | |
593 function! s:Buffer.OnDelete(cmd) dict abort | |
594 call s:trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd) | |
595 call add(self.on_delete, a:cmd) | |
596 endfunction | |
597 | |
598 function! s:Buffer.OnWinLeave(cmd) dict abort | |
599 if len(self.on_winleave) == 0 | |
600 call s:trace("Adding BufWinLeave auto-command on buffer " . self.nr) | |
601 execute 'augroup lawrencium_buffer_' . self.nr . '_winleave' | |
602 execute ' autocmd!' | |
603 execute ' autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')' | |
604 execute 'augroup end' | |
605 endif | |
606 call s:trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd) | |
607 call add(self.on_winleave, a:cmd) | |
608 endfunction | |
609 | |
610 function! s:Buffer.OnUnload(cmd) dict abort | |
611 if len(self.on_unload) == 0 | |
612 call s:trace("Adding BufUnload auto-command on buffer " . self.nr) | |
613 execute 'augroup lawrencium_buffer_' . self.nr . '_unload' | |
614 execute ' autocmd!' | |
615 execute ' autocmd BufUnload <buffer=' . self.nr . '> call s:buffer_on_unload(' . self.nr . ')' | |
616 execute 'augroup end' | |
617 endif | |
618 call s:trace("Adding BufUnload callback for buffer " . self.nr . ": " . a:cmd) | |
619 call add(self.on_unload, a:cmd) | |
620 endfunction | |
621 | |
622 let s:buffer_objects = {} | |
623 | |
624 " Get a buffer instance for the specified buffer number, or the | |
625 " current buffer if nothing is specified. | |
626 function! s:buffer_obj(...) abort | |
627 let l:bufnr = a:0 ? a:1 : bufnr('%') | |
628 if !has_key(s:buffer_objects, l:bufnr) | |
629 let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr) | |
630 endif | |
631 return s:buffer_objects[l:bufnr] | |
632 endfunction | |
633 | |
634 " Execute all the "on delete" callbacks. | |
635 function! s:buffer_on_delete(number) abort | |
636 let l:bufobj = s:buffer_objects[a:number] | |
637 call s:trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr) | |
638 for cmd in l:bufobj.on_delete | |
639 call s:trace(" [" . cmd . "]") | |
640 execute cmd | |
641 endfor | |
642 call s:trace("Deleted buffer object " . l:bufobj.nr) | |
643 call remove(s:buffer_objects, l:bufobj.nr) | |
644 execute 'augroup lawrencium_buffer_' . l:bufobj.nr | |
645 execute ' autocmd!' | |
646 execute 'augroup end' | |
647 endfunction | |
648 | |
649 " Execute all the "on winleave" callbacks. | |
650 function! s:buffer_on_winleave(number) abort | |
651 let l:bufobj = s:buffer_objects[a:number] | |
652 call s:trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr) | |
653 for cmd in l:bufobj.on_winleave | |
654 call s:trace(" [" . cmd . "]") | |
655 execute cmd | |
656 endfor | |
657 execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_winleave' | |
658 execute ' autocmd!' | |
659 execute 'augroup end' | |
660 endfunction | |
661 | |
662 " Execute all the "on unload" callbacks. | |
663 function! s:buffer_on_unload(number) abort | |
664 let l:bufobj = s:buffer_objects[a:number] | |
665 call s:trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr) | |
666 for cmd in l:bufobj.on_unload | |
667 call s:trace(" [" . cmd . "]") | |
668 execute cmd | |
669 endfor | |
670 execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload' | |
671 execute ' autocmd!' | |
672 execute 'augroup end' | |
673 endfunction | |
674 | |
675 " }}} | |
676 | |
677 " Lawrencium Files {{{ | |
678 | |
679 " Read revision (`hg cat`) | |
680 function! s:read_lawrencium_rev(repo, path_parts, full_path) abort | |
681 let l:rev = a:path_parts['value'] | |
682 if l:rev == '' | |
683 call a:repo.ReadCommandOutput('cat', a:full_path) | |
684 else | |
685 call a:repo.ReadCommandOutput('cat', '-r', l:rev, a:full_path) | |
686 endif | |
687 endfunction | |
688 | |
689 " Status (`hg status`) | |
690 function! s:read_lawrencium_status(repo, path_parts, full_path) abort | |
691 if a:path_parts['path'] == '' | |
692 call a:repo.ReadCommandOutput('status') | |
693 else | |
694 call a:repo.ReadCommandOutput('status', a:full_path) | |
695 endif | |
696 setlocal nomodified | |
697 setlocal filetype=hgstatus | |
698 setlocal bufhidden=delete | |
699 setlocal buftype=nofile | |
700 endfunction | |
701 | |
702 " Log (`hg log`) | |
703 let s:log_style_file = expand("<sfile>:h:h") . "/resources/hg_log.style" | |
704 | |
705 function! s:read_lawrencium_log(repo, path_parts, full_path) abort | |
706 let l:log_opts = join(split(a:path_parts['value'], ',')) | |
707 let l:log_cmd = "log " . l:log_opts | |
708 | |
709 if a:path_parts['path'] == '' | |
710 call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file) | |
711 else | |
712 call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file, a:full_path) | |
713 endif | |
714 setlocal filetype=hglog | |
715 endfunction | |
716 | |
717 function! s:read_lawrencium_logpatch(repo, path_parts, full_path) abort | |
718 let l:log_cmd = 'log --patch --verbose --rev ' . a:path_parts['value'] | |
719 | |
720 if a:path_parts['path'] == '' | |
721 call a:repo.ReadCommandOutput(l:log_cmd) | |
722 else | |
723 call a:repo.ReadCommandOutput(l:log_cmd, a:full_path) | |
724 endif | |
725 setlocal filetype=diff | |
726 endfunction | |
727 | |
728 " Diff revisions (`hg diff`) | |
729 function! s:read_lawrencium_diff(repo, path_parts, full_path) abort | |
730 let l:diffargs = [] | |
731 let l:commaidx = stridx(a:path_parts['value'], ',') | |
732 if l:commaidx > 0 | |
733 let l:rev1 = strpart(a:path_parts['value'], 0, l:commaidx) | |
734 let l:rev2 = strpart(a:path_parts['value'], l:commaidx + 1) | |
735 if l:rev1 == '-' | |
736 let l:diffargs = [ '-r', l:rev2 ] | |
737 elseif l:rev2 == '-' | |
738 let l:diffargs = [ '-r', l:rev1 ] | |
739 else | |
740 let l:diffargs = [ '-r', l:rev1, '-r', l:rev2 ] | |
741 endif | |
742 elseif a:path_parts['value'] != '' | |
743 let l:diffargs = [ '-c', a:path_parts['value'] ] | |
744 else | |
745 let l:diffargs = [] | |
746 endif | |
747 if a:path_parts['path'] != '' && a:path_parts['path'] != '.' | |
748 call add(l:diffargs, a:full_path) | |
749 endif | |
750 call a:repo.ReadCommandOutput('diff', l:diffargs) | |
751 setlocal filetype=diff | |
752 setlocal nofoldenable | |
753 endfunction | |
754 | |
755 " Annotate file | |
756 function! s:read_lawrencium_annotate(repo, path_parts, full_path) abort | |
757 let l:cmd_args = ['-c', '-n', '-u', '-d', '-q'] | |
758 if a:path_parts['value'] == 'v=1' | |
759 call insert(l:cmd_args, '-v', 0) | |
760 endif | |
761 call add(l:cmd_args, a:full_path) | |
762 call a:repo.ReadCommandOutput('annotate', l:cmd_args) | |
763 endfunction | |
764 | |
765 " MQ series | |
766 function! s:read_lawrencium_qseries(repo, path_parts, full_path) abort | |
767 let l:names = split(a:repo.RunCommand('qseries'), '\n') | |
768 let l:head = split(a:repo.RunCommand('qapplied', '-s'), '\n') | |
769 let l:tail = split(a:repo.RunCommand('qunapplied', '-s'), '\n') | |
770 | |
771 let l:idx = 0 | |
772 let l:curbuffer = bufname('%') | |
773 for line in l:head | |
774 call setbufvar(l:curbuffer, 'lawrencium_patchname_' . (l:idx + 1), l:names[l:idx]) | |
775 call append(l:idx, "*" . line) | |
776 let l:idx = l:idx + 1 | |
777 endfor | |
778 for line in l:tail | |
779 call setbufvar(l:curbuffer, 'lawrencium_patchname_' . (l:idx + 1), l:names[l:idx]) | |
780 call append(l:idx, line) | |
781 let l:idx = l:idx + 1 | |
782 endfor | |
783 call setbufvar(l:curbuffer, 'lawrencium_patchname_top', l:names[len(l:head) - 1]) | |
784 set filetype=hgqseries | |
785 endfunction | |
786 | |
787 " Generic read | |
788 let s:lawrencium_file_readers = { | |
789 \'rev': function('s:read_lawrencium_rev'), | |
790 \'log': function('s:read_lawrencium_log'), | |
791 \'logpatch': function('s:read_lawrencium_logpatch'), | |
792 \'diff': function('s:read_lawrencium_diff'), | |
793 \'status': function('s:read_lawrencium_status'), | |
794 \'annotate': function('s:read_lawrencium_annotate'), | |
795 \'qseries': function('s:read_lawrencium_qseries') | |
796 \} | |
797 let s:lawrencium_file_customoptions = { | |
798 \'status': 1 | |
799 \} | |
800 | |
801 function! s:ReadLawrenciumFile(path) abort | |
802 call s:trace("Reading Lawrencium file: " . a:path) | |
803 let l:path_parts = s:parse_lawrencium_path(a:path) | |
804 if l:path_parts['root'] == '' | |
805 call s:throw("Can't get repository root from: " . a:path) | |
806 endif | |
807 if !has_key(s:lawrencium_file_readers, l:path_parts['action']) | |
808 call s:throw("No registered reader for action: " . l:path_parts['action']) | |
809 endif | |
810 | |
811 " Call the registered reader. | |
812 let l:repo = s:hg_repo(l:path_parts['root']) | |
813 let l:full_path = l:repo.root_dir . l:path_parts['path'] | |
814 let LawrenciumFileReader = s:lawrencium_file_readers[l:path_parts['action']] | |
815 call LawrenciumFileReader(l:repo, l:path_parts, l:full_path) | |
816 | |
817 " Setup the new buffer. | |
818 if !has_key(s:lawrencium_file_customoptions, l:path_parts['action']) | |
819 setlocal readonly | |
820 setlocal nomodified | |
821 setlocal bufhidden=delete | |
822 setlocal buftype=nofile | |
823 endif | |
824 goto | |
825 | |
826 " Remember the real Lawrencium path, because Vim can fuck up the slashes | |
827 " on Windows. | |
828 let b:lawrencium_path = a:path | |
829 | |
830 " Remember the repo it belongs to and make | |
831 " the Lawrencium commands available. | |
832 let b:mercurial_dir = l:repo.root_dir | |
833 call s:DefineMainCommands() | |
834 | |
835 return '' | |
836 endfunction | |
837 | |
838 function! s:WriteLawrenciumFile(path) abort | |
839 call s:trace("Writing Lawrencium file: " . a:path) | |
840 endfunction | |
841 | |
842 augroup lawrencium_files | 72 augroup lawrencium_files |
843 autocmd! | 73 autocmd! |
844 autocmd BufReadCmd lawrencium://**//**//* exe s:ReadLawrenciumFile(expand('<amatch>')) | 74 autocmd BufReadCmd lawrencium://**//**//* exe lawrencium#read_lawrencium_file(expand('<amatch>')) |
845 autocmd BufWriteCmd lawrencium://**//**//* exe s:WriteLawrenciumFile(expand('<amatch>')) | 75 autocmd BufWriteCmd lawrencium://**//**//* exe lawrencium#write_lawrencium_file(expand('<amatch>')) |
846 augroup END | 76 augroup END |
847 | 77 |
848 " }}} | 78 " }}} |
849 | 79 |
850 " Buffer Commands Management {{{ | |
851 | |
852 " Store the commands for Lawrencium-enabled buffers so that we can add them in | |
853 " batch when we need to. | |
854 let s:main_commands = [] | |
855 | |
856 function! s:AddMainCommand(command) abort | |
857 let s:main_commands += [a:command] | |
858 endfunction | |
859 | |
860 function! s:DefineMainCommands() | |
861 for l:command in s:main_commands | |
862 execute 'command! -buffer ' . l:command | |
863 endfor | |
864 endfunction | |
865 | |
866 augroup lawrencium_main | |
867 autocmd! | |
868 autocmd User Lawrencium call s:DefineMainCommands() | |
869 augroup end | |
870 | |
871 " }}} | |
872 | |
873 " Commands Auto-Complete {{{ | |
874 | |
875 " Auto-complete function for commands that take repo-relative file paths. | |
876 function! s:ListRepoFiles(ArgLead, CmdLine, CursorPos) abort | |
877 let l:matches = s:hg_repo().Glob(a:ArgLead . '*', 1) | |
878 call map(l:matches, 's:normalizepath(v:val)') | |
879 return l:matches | |
880 endfunction | |
881 | |
882 " Auto-complete function for commands that take repo-relative directory paths. | |
883 function! s:ListRepoDirs(ArgLead, CmdLine, CursorPos) abort | |
884 let l:matches = s:hg_repo().Glob(a:ArgLead . '*/') | |
885 call map(l:matches, 's:normalizepath(v:val)') | |
886 return l:matches | |
887 endfunction | |
888 | |
889 " }}} | |
890 | |
891 " Hg {{{ | |
892 | |
893 function! s:Hg(bang, ...) abort | |
894 let l:repo = s:hg_repo() | |
895 if g:lawrencium_auto_cd | |
896 " Temporary set the current directory to the root of the repo | |
897 " to make auto-completed paths work magically. | |
898 execute 'cd! ' . fnameescape(l:repo.root_dir) | |
899 endif | |
900 let l:output = call(l:repo.RunCommandEx, [0] + a:000, l:repo) | |
901 if g:lawrencium_auto_cd | |
902 execute 'cd! -' | |
903 endif | |
904 silent doautocmd User HgCmdPost | |
905 if a:bang | |
906 " Open the output of the command in a temp file. | |
907 let l:temp_file = s:tempname('hg-output-', '.txt') | |
908 split | |
909 execute 'edit ' . fnameescape(l:temp_file) | |
910 call append(0, split(l:output, '\n')) | |
911 call cursor(1, 1) | |
912 | |
913 " Make it a temp buffer | |
914 setlocal bufhidden=delete | |
915 setlocal buftype=nofile | |
916 | |
917 " Try to find a nice syntax to set given the current command. | |
918 let l:command_name = s:GetHgCommandName(a:000) | |
919 if l:command_name != '' && exists('g:lawrencium_hg_commands_file_types') | |
920 let l:file_type = get(g:lawrencium_hg_commands_file_types, l:command_name, '') | |
921 if l:file_type != '' | |
922 execute 'setlocal ft=' . l:file_type | |
923 endif | |
924 endif | |
925 else | |
926 " Just print out the output of the command. | |
927 echo l:output | |
928 endif | |
929 endfunction | |
930 | |
931 " Include the generated HG usage file. | |
932 let s:usage_file = expand("<sfile>:h:h") . "/resources/hg_usage.vim" | |
933 if filereadable(s:usage_file) | |
934 execute "source " . fnameescape(s:usage_file) | |
935 else | |
936 call s:error("Can't find the Mercurial usage file. Auto-completion will be disabled in Lawrencium.") | |
937 endif | |
938 | |
939 " Include the command file type mappings. | |
940 let s:file_type_mappings = expand("<sfile>:h:h") . '/resources/hg_command_file_types.vim' | |
941 if filereadable(s:file_type_mappings) | |
942 execute "source " . fnameescape(s:file_type_mappings) | |
943 endif | |
944 | |
945 function! s:CompleteHg(ArgLead, CmdLine, CursorPos) | |
946 " Don't do anything if the usage file was not sourced. | |
947 if !exists('g:lawrencium_hg_commands') || !exists('g:lawrencium_hg_options') | |
948 return [] | |
949 endif | |
950 | |
951 " a:ArgLead seems to be the number 0 when completing a minus '-'. | |
952 " Gotta find out why... | |
953 let l:arglead = a:ArgLead | |
954 if type(a:ArgLead) == type(0) | |
955 let l:arglead = '-' | |
956 endif | |
957 | |
958 " Try completing a global option, before any command name. | |
959 if a:CmdLine =~# '\v^Hg(\s+\-[a-zA-Z0-9\-_]*)+$' | |
960 return filter(copy(g:lawrencium_hg_options), "v:val[0:strlen(l:arglead)-1] ==# l:arglead") | |
961 endif | |
962 | |
963 " Try completing a command (note that there could be global options before | |
964 " the command name). | |
965 if a:CmdLine =~# '\v^Hg\s+(\-[a-zA-Z0-9\-_]+\s+)*[a-zA-Z]+$' | |
966 return filter(keys(g:lawrencium_hg_commands), "v:val[0:strlen(l:arglead)-1] ==# l:arglead") | |
967 endif | |
968 | |
969 " Try completing a command's options. | |
970 let l:cmd = matchstr(a:CmdLine, '\v(^Hg\s+(\-[a-zA-Z0-9\-_]+\s+)*)@<=[a-zA-Z]+') | |
971 if strlen(l:cmd) > 0 && l:arglead[0] ==# '-' | |
972 if has_key(g:lawrencium_hg_commands, l:cmd) | |
973 " Return both command options and global options together. | |
974 let l:copts = filter(copy(g:lawrencium_hg_commands[l:cmd]), "v:val[0:strlen(l:arglead)-1] ==# l:arglead") | |
975 let l:gopts = filter(copy(g:lawrencium_hg_options), "v:val[0:strlen(l:arglead)-1] ==# l:arglead") | |
976 return l:copts + l:gopts | |
977 endif | |
978 endif | |
979 | |
980 " Just auto-complete with filenames unless it's an option. | |
981 if l:arglead[0] ==# '-' | |
982 return [] | |
983 else | |
984 return s:ListRepoFiles(a:ArgLead, a:CmdLine, a:CursorPos) | |
985 endfunction | |
986 | |
987 function! s:GetHgCommandName(args) abort | |
988 for l:a in a:args | |
989 if stridx(l:a, '-') != 0 | |
990 return l:a | |
991 endif | |
992 endfor | |
993 return '' | |
994 endfunction | |
995 | |
996 call s:AddMainCommand("-bang -complete=customlist,s:CompleteHg -nargs=* Hg :call s:Hg(<bang>0, <f-args>)") | |
997 | |
998 " }}} | |
999 | |
1000 " Hgstatus {{{ | |
1001 | |
1002 function! s:HgStatus() abort | |
1003 " Get the repo and the Lawrencium path for `hg status`. | |
1004 let l:repo = s:hg_repo() | |
1005 let l:status_path = l:repo.GetLawrenciumPath('', 'status', '') | |
1006 | |
1007 " Open the Lawrencium buffer in a new split window of the right size. | |
1008 if g:lawrencium_status_win_split_above | |
1009 execute "keepalt leftabove split " . fnameescape(l:status_path) | |
1010 else | |
1011 execute "keepalt rightbelow split " . fnameescape(l:status_path) | |
1012 endif | |
1013 | |
1014 if (line('$') == 1 && getline(1) == '') | |
1015 " Buffer is empty, which means there are not changes... | |
1016 " Quit and display a message. | |
1017 " TODO: figure out why the first `echom` doesn't show when alone. | |
1018 bdelete | |
1019 echom "Nothing was modified." | |
1020 echom "" | |
1021 return | |
1022 endif | |
1023 | |
1024 execute "setlocal winfixheight" | |
1025 if !g:lawrencium_status_win_split_even | |
1026 execute "setlocal winheight=" . (line('$') + 1) | |
1027 execute "resize " . (line('$') + 1) | |
1028 endif | |
1029 | |
1030 " Add some nice commands. | |
1031 command! -buffer Hgstatusedit :call s:HgStatus_FileEdit(0) | |
1032 command! -buffer Hgstatusdiff :call s:HgStatus_Diff(0) | |
1033 command! -buffer Hgstatusvdiff :call s:HgStatus_Diff(1) | |
1034 command! -buffer Hgstatustabdiff :call s:HgStatus_Diff(2) | |
1035 command! -buffer Hgstatusdiffsum :call s:HgStatus_DiffSummary(1) | |
1036 command! -buffer Hgstatusvdiffsum :call s:HgStatus_DiffSummary(2) | |
1037 command! -buffer Hgstatustabdiffsum :call s:HgStatus_DiffSummary(3) | |
1038 command! -buffer Hgstatusrefresh :call s:HgStatus_Refresh() | |
1039 command! -buffer -range -bang Hgstatusrevert :call s:HgStatus_Revert(<line1>, <line2>, <bang>0) | |
1040 command! -buffer -range Hgstatusaddremove :call s:HgStatus_AddRemove(<line1>, <line2>) | |
1041 command! -buffer -range=% -bang Hgstatuscommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 0) | |
1042 command! -buffer -range=% -bang Hgstatusvcommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 1) | |
1043 command! -buffer -range=% -nargs=+ Hgstatusqnew :call s:HgStatus_QNew(<line1>, <line2>, <f-args>) | |
1044 command! -buffer -range=% Hgstatusqrefresh :call s:HgStatus_QRefresh(<line1>, <line2>) | |
1045 | |
1046 " Add some handy mappings. | |
1047 if g:lawrencium_define_mappings | |
1048 nnoremap <buffer> <silent> <cr> :Hgstatusedit<cr> | |
1049 nnoremap <buffer> <silent> <C-N> :call search('^[MARC\!\?I ]\s.', 'We')<cr> | |
1050 nnoremap <buffer> <silent> <C-P> :call search('^[MARC\!\?I ]\s.', 'Wbe')<cr> | |
1051 nnoremap <buffer> <silent> <C-D> :Hgstatustabdiff<cr> | |
1052 nnoremap <buffer> <silent> <C-V> :Hgstatusvdiff<cr> | |
1053 nnoremap <buffer> <silent> <C-U> :Hgstatusdiffsum<cr> | |
1054 nnoremap <buffer> <silent> <C-H> :Hgstatusvdiffsum<cr> | |
1055 nnoremap <buffer> <silent> <C-A> :Hgstatusaddremove<cr> | |
1056 nnoremap <buffer> <silent> <C-S> :Hgstatuscommit<cr> | |
1057 nnoremap <buffer> <silent> <C-R> :Hgstatusrefresh<cr> | |
1058 nnoremap <buffer> <silent> q :bdelete!<cr> | |
1059 | |
1060 vnoremap <buffer> <silent> <C-A> :Hgstatusaddremove<cr> | |
1061 vnoremap <buffer> <silent> <C-S> :Hgstatuscommit<cr> | |
1062 endif | |
1063 endfunction | |
1064 | |
1065 function! s:HgStatus_Refresh(...) abort | |
1066 if a:0 > 0 | |
1067 let l:win_nr = bufwinnr(a:1) | |
1068 call s:trace("Switching back to status window ".l:win_nr) | |
1069 if l:win_nr < 0 | |
1070 call s:throw("Can't find the status window anymore!") | |
1071 endif | |
1072 execute l:win_nr . 'wincmd w' | |
1073 " Delete everything in the buffer, and re-read the status into it. | |
1074 " TODO: In theory I would only have to do `edit` like below when we're | |
1075 " already in the window, but for some reason Vim just goes bonkers and | |
1076 " weird shit happens. I have no idea why, hence the work-around here | |
1077 " to bypass the whole `BufReadCmd` auto-command altogether, and just | |
1078 " edit the buffer in place. | |
1079 normal! ggVGd | |
1080 call s:ReadLawrenciumFile(b:lawrencium_path) | |
1081 return | |
1082 endif | |
1083 | |
1084 " Just re-edit the buffer, it will reload the contents by calling | |
1085 " the matching Mercurial command. | |
1086 edit | |
1087 endfunction | |
1088 | |
1089 function! s:HgStatus_FileEdit(newtab) abort | |
1090 " Get the path of the file the cursor is on. | |
1091 let l:filename = s:HgStatus_GetSelectedFile() | |
1092 | |
1093 let l:cleanupbufnr = -1 | |
1094 if a:newtab == 0 | |
1095 " If the file is already open in a window, jump to that window. | |
1096 " Otherwise, jump to the previous window and open it there. | |
1097 for nr in range(1, winnr('$')) | |
1098 let l:br = winbufnr(nr) | |
1099 let l:bpath = fnamemodify(bufname(l:br), ':p') | |
1100 if l:bpath ==# l:filename | |
1101 execute nr . 'wincmd w' | |
1102 return | |
1103 endif | |
1104 endfor | |
1105 wincmd p | |
1106 else | |
1107 " Just open a new tab so we can edit the file there. | |
1108 " We don't use `tabedit` because it messes up the current window | |
1109 " if it happens to be the same file. | |
1110 " We'll just have to clean up the default empty buffer created. | |
1111 tabnew | |
1112 let l:cleanupbufnr = bufnr('%') | |
1113 endif | |
1114 execute 'edit ' . fnameescape(l:filename) | |
1115 if l:cleanupbufnr >= 0 | |
1116 execute 'bdelete ' . l:cleanupbufnr | |
1117 endif | |
1118 endfunction | |
1119 | |
1120 function! s:HgStatus_AddRemove(linestart, lineend) abort | |
1121 " Get the selected filenames. | |
1122 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['!', '?']) | |
1123 if len(l:filenames) == 0 | |
1124 call s:error("No files to add or remove in selection or current line.") | |
1125 return | |
1126 endif | |
1127 | |
1128 " Run `addremove` on those paths. | |
1129 let l:repo = s:hg_repo() | |
1130 call l:repo.RunCommand('addremove', l:filenames) | |
1131 | |
1132 " Refresh the status window. | |
1133 call s:HgStatus_Refresh() | |
1134 endfunction | |
1135 | |
1136 function! s:HgStatus_Revert(linestart, lineend, bang) abort | |
1137 " Get the selected filenames. | |
1138 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R']) | |
1139 if len(l:filenames) == 0 | |
1140 call s:error("No files to revert in selection or current line.") | |
1141 return | |
1142 endif | |
1143 | |
1144 " Run `revert` on those paths. | |
1145 " If the bang modifier is specified, revert with no backup. | |
1146 let l:repo = s:hg_repo() | |
1147 if a:bang | |
1148 call insert(l:filenames, '-C', 0) | |
1149 endif | |
1150 call l:repo.RunCommand('revert', l:filenames) | |
1151 | |
1152 " Refresh the status window. | |
1153 call s:HgStatus_Refresh() | |
1154 endfunction | |
1155 | |
1156 function! s:HgStatus_Commit(linestart, lineend, bang, vertical) abort | |
1157 " Get the selected filenames. | |
1158 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R']) | |
1159 if len(l:filenames) == 0 | |
1160 call s:error("No files to commit in selection or file.") | |
1161 return | |
1162 endif | |
1163 | |
1164 " Run `Hgcommit` on those paths. | |
1165 let l:buf_nr = bufnr('%') | |
1166 let l:callback = 'call s:HgStatus_Refresh('.l:buf_nr.')' | |
1167 call s:HgCommit(a:bang, a:vertical, l:callback, l:filenames) | |
1168 endfunction | |
1169 | |
1170 function! s:HgStatus_Diff(split) abort | |
1171 " Open the file and run `Hgdiff` on it. | |
1172 " We also need to translate the split mode for it... if we already | |
1173 " opened the file in a new tab, `HgDiff` only needs to do a vertical | |
1174 " split (i.e. split=1). | |
1175 let l:newtab = 0 | |
1176 let l:hgdiffsplit = a:split | |
1177 if a:split == 2 | |
1178 let l:newtab = 1 | |
1179 let l:hgdiffsplit = 1 | |
1180 endif | |
1181 call s:HgStatus_FileEdit(l:newtab) | |
1182 call s:HgDiff('%:p', l:hgdiffsplit) | |
1183 endfunction | |
1184 | |
1185 function! s:HgStatus_DiffSummary(split) abort | |
1186 " Get the path of the file the cursor is on. | |
1187 let l:path = s:HgStatus_GetSelectedFile() | |
1188 " Reuse the same diff summary window | |
1189 let l:reuse_id = 'lawrencium_diffsum_for_' . bufnr('%') | |
1190 let l:split_prev_win = (a:split < 3) | |
1191 let l:args = {'reuse_id': l:reuse_id, 'use_prev_win': l:split_prev_win, | |
1192 \'avoid_win': winnr(), 'split_mode': a:split} | |
1193 call s:HgDiffSummary(l:path, l:args) | |
1194 endfunction | |
1195 | |
1196 function! s:HgStatus_QNew(linestart, lineend, patchname, ...) abort | |
1197 " Get the selected filenames. | |
1198 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R']) | |
1199 if len(l:filenames) == 0 | |
1200 call s:error("No files in selection or file to create patch.") | |
1201 return | |
1202 endif | |
1203 | |
1204 " Run `Hg qnew` on those paths. | |
1205 let l:repo = s:hg_repo() | |
1206 call insert(l:filenames, a:patchname, 0) | |
1207 if a:0 > 0 | |
1208 call insert(l:filenames, '-m', 0) | |
1209 let l:message = '"' . join(a:000, ' ') . '"' | |
1210 call insert(l:filenames, l:message, 1) | |
1211 endif | |
1212 call l:repo.RunCommand('qnew', l:filenames) | |
1213 | |
1214 " Refresh the status window. | |
1215 call s:HgStatus_Refresh() | |
1216 endfunction | |
1217 | |
1218 function! s:HgStatus_QRefresh(linestart, lineend) abort | |
1219 " Get the selected filenames. | |
1220 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R']) | |
1221 if len(l:filenames) == 0 | |
1222 call s:error("No files in selection or file to refresh the patch.") | |
1223 return | |
1224 endif | |
1225 | |
1226 " Run `Hg qrefresh` on those paths. | |
1227 let l:repo = s:hg_repo() | |
1228 call insert(l:filenames, '-s', 0) | |
1229 call l:repo.RunCommand('qrefresh', l:filenames) | |
1230 | |
1231 " Refresh the status window. | |
1232 call s:HgStatus_Refresh() | |
1233 endfunction | |
1234 | |
1235 | |
1236 function! s:HgStatus_GetSelectedFile() abort | |
1237 let l:filenames = s:HgStatus_GetSelectedFiles() | |
1238 return l:filenames[0] | |
1239 endfunction | |
1240 | |
1241 function! s:HgStatus_GetSelectedFiles(...) abort | |
1242 if a:0 >= 2 | |
1243 let l:lines = getline(a:1, a:2) | |
1244 else | |
1245 let l:lines = [] | |
1246 call add(l:lines, getline('.')) | |
1247 endif | |
1248 let l:filenames = [] | |
1249 let l:repo = s:hg_repo() | |
1250 for line in l:lines | |
1251 if a:0 >= 3 | |
1252 let l:status = s:HgStatus_GetFileStatus(line) | |
1253 if index(a:3, l:status) < 0 | |
1254 continue | |
1255 endif | |
1256 endif | |
1257 " Yay, awesome, Vim's regex syntax is fucked up like shit, especially for | |
1258 " look-aheads and look-behinds. See for yourself: | |
1259 let l:filename = matchstr(l:line, '\v(^[MARC\!\?I ]\s)@<=.*') | |
1260 let l:filename = l:repo.GetFullPath(l:filename) | |
1261 call add(l:filenames, l:filename) | |
1262 endfor | |
1263 return l:filenames | |
1264 endfunction | |
1265 | |
1266 function! s:HgStatus_GetFileStatus(...) abort | |
1267 let l:line = a:0 ? a:1 : getline('.') | |
1268 return matchstr(l:line, '\v^[MARC\!\?I ]') | |
1269 endfunction | |
1270 | |
1271 call s:AddMainCommand("Hgstatus :call s:HgStatus()") | |
1272 | |
1273 " }}} | |
1274 | |
1275 " Hgcd, Hglcd {{{ | |
1276 | |
1277 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hgcd :cd<bang> `=s:hg_repo().GetFullPath(<q-args>)`") | |
1278 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hglcd :lcd<bang> `=s:hg_repo().GetFullPath(<q-args>)`") | |
1279 | |
1280 " }}} | |
1281 | |
1282 " Hgedit {{{ | |
1283 | |
1284 function! s:HgEdit(bang, filename) abort | |
1285 let l:full_path = s:hg_repo().GetFullPath(a:filename) | |
1286 if a:bang | |
1287 execute "edit! " . fnameescape(l:full_path) | |
1288 else | |
1289 execute "edit " . fnameescape(l:full_path) | |
1290 endif | |
1291 endfunction | |
1292 | |
1293 call s:AddMainCommand("-bang -nargs=1 -complete=customlist,s:ListRepoFiles Hgedit :call s:HgEdit(<bang>0, <f-args>)") | |
1294 | |
1295 " }}} | |
1296 | |
1297 " Hgvimgrep {{{ | |
1298 | |
1299 function! s:HgVimGrep(bang, pattern, ...) abort | |
1300 let l:repo = s:hg_repo() | |
1301 let l:file_paths = [] | |
1302 if a:0 > 0 | |
1303 for ff in a:000 | |
1304 let l:full_ff = l:repo.GetFullPath(ff) | |
1305 call add(l:file_paths, l:full_ff) | |
1306 endfor | |
1307 else | |
1308 call add(l:file_paths, l:repo.root_dir . "**") | |
1309 endif | |
1310 if a:bang | |
1311 execute "vimgrep! " . a:pattern . " " . join(l:file_paths, " ") | |
1312 else | |
1313 execute "vimgrep " . a:pattern . " " . join(l:file_paths, " ") | |
1314 endif | |
1315 endfunction | |
1316 | |
1317 call s:AddMainCommand("-bang -nargs=+ -complete=customlist,s:ListRepoFiles Hgvimgrep :call s:HgVimGrep(<bang>0, <f-args>)") | |
1318 | |
1319 " }}} | |
1320 | |
1321 " Hgdiff, Hgvdiff, Hgtabdiff {{{ | |
1322 | |
1323 function! s:HgDiff(filename, split, ...) abort | |
1324 " Default revisions to diff: the working directory (null string) | |
1325 " and the parent of the working directory (using Mercurial's revsets syntax). | |
1326 " Otherwise, use the 1 or 2 revisions specified as extra parameters. | |
1327 let l:rev1 = 'p1()' | |
1328 let l:rev2 = '' | |
1329 if a:0 == 1 | |
1330 if type(a:1) == type([]) | |
1331 if len(a:1) >= 2 | |
1332 let l:rev1 = a:1[0] | |
1333 let l:rev2 = a:1[1] | |
1334 elseif len(a:1) == 1 | |
1335 let l:rev1 = a:1[0] | |
1336 endif | |
1337 else | |
1338 let l:rev1 = a:1 | |
1339 endif | |
1340 elseif a:0 == 2 | |
1341 let l:rev1 = a:1 | |
1342 let l:rev2 = a:2 | |
1343 endif | |
1344 | |
1345 " Get the current repo, and expand the given filename in case it contains | |
1346 " fancy filename modifiers. | |
1347 let l:repo = s:hg_repo() | |
1348 let l:path = expand(a:filename) | |
1349 let l:diff_id = localtime() | |
1350 call s:trace("Diff'ing '".l:rev1."' and '".l:rev2."' on file: ".l:path) | |
1351 | |
1352 " Get the first file and open it. | |
1353 let l:cleanupbufnr = -1 | |
1354 if l:rev1 == '' | |
1355 if a:split == 2 | |
1356 " Don't use `tabedit` here because if `l:path` is the same as | |
1357 " the current path, it will also reload the buffer in the current | |
1358 " tab/window for some reason, which causes all state to be lost | |
1359 " (all folds get collapsed again, cursor is moved to start, etc.) | |
1360 tabnew | |
1361 let l:cleanupbufnr = bufnr('%') | |
1362 execute 'edit ' . fnameescape(l:path) | |
1363 else | |
1364 if bufexists(l:path) | |
1365 execute 'buffer ' . fnameescape(l:path) | |
1366 else | |
1367 execute 'edit ' . fnameescape(l:path) | |
1368 endif | |
1369 endif | |
1370 " Make it part of the diff group. | |
1371 call s:HgDiff_DiffThis(l:diff_id) | |
1372 else | |
1373 let l:rev_path = l:repo.GetLawrenciumPath(l:path, 'rev', l:rev1) | |
1374 if a:split == 2 | |
1375 " See comments above about avoiding `tabedit`. | |
1376 tabnew | |
1377 let l:cleanupbufnr = bufnr('%') | |
1378 endif | |
1379 execute 'edit ' . fnameescape(l:rev_path) | |
1380 " Make it part of the diff group. | |
1381 call s:HgDiff_DiffThis(l:diff_id) | |
1382 endif | |
1383 if l:cleanupbufnr >= 0 && bufloaded(l:cleanupbufnr) | |
1384 execute 'bdelete ' . l:cleanupbufnr | |
1385 endif | |
1386 | |
1387 " Get the second file and open it too. | |
1388 " Don't use `diffsplit` because it will set `&diff` before we get a chance | |
1389 " to save a bunch of local settings that we will want to restore later. | |
1390 let l:diffsplit = 'split' | |
1391 if a:split >= 1 | |
1392 let l:diffsplit = 'vsplit' | |
1393 endif | |
1394 if l:rev2 == '' | |
1395 execute l:diffsplit . ' ' . fnameescape(l:path) | |
1396 else | |
1397 let l:rev_path = l:repo.GetLawrenciumPath(l:path, 'rev', l:rev2) | |
1398 execute l:diffsplit . ' ' . fnameescape(l:rev_path) | |
1399 endif | |
1400 call s:HgDiff_DiffThis(l:diff_id) | |
1401 endfunction | |
1402 | |
1403 function! s:HgDiff_DiffThis(diff_id) abort | |
1404 " Store some commands to run when we exit diff mode. | |
1405 " It's needed because `diffoff` reverts those settings to their default | |
1406 " values, instead of their previous ones. | |
1407 if &diff | |
1408 call s:throw("Calling diffthis too late on a buffer!") | |
1409 return | |
1410 endif | |
1411 call s:trace('Enabling diff mode on ' . bufname('%')) | |
1412 let w:lawrencium_diffoff = {} | |
1413 let w:lawrencium_diffoff['&diff'] = 0 | |
1414 let w:lawrencium_diffoff['&wrap'] = &l:wrap | |
1415 let w:lawrencium_diffoff['&scrollopt'] = &l:scrollopt | |
1416 let w:lawrencium_diffoff['&scrollbind'] = &l:scrollbind | |
1417 let w:lawrencium_diffoff['&cursorbind'] = &l:cursorbind | |
1418 let w:lawrencium_diffoff['&foldmethod'] = &l:foldmethod | |
1419 let w:lawrencium_diffoff['&foldcolumn'] = &l:foldcolumn | |
1420 let w:lawrencium_diffoff['&foldenable'] = &l:foldenable | |
1421 let w:lawrencium_diff_id = a:diff_id | |
1422 diffthis | |
1423 autocmd BufWinLeave <buffer> call s:HgDiff_CleanUp() | |
1424 endfunction | |
1425 | |
1426 function! s:HgDiff_DiffOff(...) abort | |
1427 " Get the window name (given as a paramter, or current window). | |
1428 let l:nr = a:0 ? a:1 : winnr() | |
1429 | |
1430 " Run the commands we saved in `HgDiff_DiffThis`, or just run `diffoff`. | |
1431 let l:backup = getwinvar(l:nr, 'lawrencium_diffoff') | |
1432 if type(l:backup) == type({}) && len(l:backup) > 0 | |
1433 call s:trace('Disabling diff mode on ' . l:nr) | |
1434 for key in keys(l:backup) | |
1435 call setwinvar(l:nr, key, l:backup[key]) | |
1436 endfor | |
1437 call setwinvar(l:nr, 'lawrencium_diffoff', {}) | |
1438 else | |
1439 call s:trace('Disabling diff mode on ' . l:nr . ' (but no true restore)') | |
1440 diffoff | |
1441 endif | |
1442 endfunction | |
1443 | |
1444 function! s:HgDiff_GetDiffWindows(diff_id) abort | |
1445 let l:result = [] | |
1446 for nr in range(1, winnr('$')) | |
1447 if getwinvar(nr, '&diff') && getwinvar(nr, 'lawrencium_diff_id') == a:diff_id | |
1448 call add(l:result, nr) | |
1449 endif | |
1450 endfor | |
1451 return l:result | |
1452 endfunction | |
1453 | |
1454 function! s:HgDiff_CleanUp() abort | |
1455 " If we're not leaving one of our diff window, do nothing. | |
1456 if !&diff || !exists('w:lawrencium_diff_id') | |
1457 return | |
1458 endif | |
1459 | |
1460 " If there will be only one diff window left (plus the one we're leaving), | |
1461 " turn off diff in it and restore its local settings. | |
1462 let l:nrs = s:HgDiff_GetDiffWindows(w:lawrencium_diff_id) | |
1463 if len(l:nrs) <= 2 | |
1464 call s:trace('Disabling diff mode in ' . len(l:nrs) . ' windows.') | |
1465 for nr in l:nrs | |
1466 if getwinvar(nr, '&diff') | |
1467 call s:HgDiff_DiffOff(nr) | |
1468 endif | |
1469 endfor | |
1470 else | |
1471 call s:trace('Still ' . len(l:nrs) . ' diff windows open.') | |
1472 endif | |
1473 endfunction | |
1474 | |
1475 call s:AddMainCommand("-nargs=* Hgdiff :call s:HgDiff('%:p', 0, <f-args>)") | |
1476 call s:AddMainCommand("-nargs=* Hgvdiff :call s:HgDiff('%:p', 1, <f-args>)") | |
1477 call s:AddMainCommand("-nargs=* Hgtabdiff :call s:HgDiff('%:p', 2, <f-args>)") | |
1478 | |
1479 " }}} | |
1480 | |
1481 " Hgdiffsum, Hgdiffsumsplit, Hgvdiffsumsplit, Hgtabdiffsum {{{ | |
1482 | |
1483 function! s:HgDiffSummary(filename, present_args, ...) abort | |
1484 " Default revisions to diff: the working directory (null string) | |
1485 " and the parent of the working directory (using Mercurial's revsets syntax). | |
1486 " Otherwise, use the 1 or 2 revisions specified as extra parameters. | |
1487 let l:revs = '' | |
1488 if a:0 == 1 | |
1489 if type(a:1) == type([]) | |
1490 if len(a:1) >= 2 | |
1491 let l:revs = a:1[0] . ',' . a:1[1] | |
1492 elseif len(a:1) == 1 | |
1493 let l:revs = a:1[0] | |
1494 endif | |
1495 else | |
1496 let l:revs = a:1 | |
1497 endif | |
1498 elseif a:0 >= 2 | |
1499 let l:revs = a:1 . ',' . a:2 | |
1500 endif | |
1501 | |
1502 " Get the current repo, and expand the given filename in case it contains | |
1503 " fancy filename modifiers. | |
1504 let l:repo = s:hg_repo() | |
1505 let l:path = expand(a:filename) | |
1506 call s:trace("Diff'ing revisions: '".l:revs."' on file: ".l:path) | |
1507 let l:special = l:repo.GetLawrenciumPath(l:path, 'diff', l:revs) | |
1508 | |
1509 " Build the correct edit command, and switch to the correct window, based | |
1510 " on the presentation arguments we got. | |
1511 if type(a:present_args) == type(0) | |
1512 " Just got a split mode. | |
1513 let l:valid_args = {'split_mode': a:present_args} | |
1514 else | |
1515 " Got complex args. | |
1516 let l:valid_args = a:present_args | |
1517 endif | |
1518 | |
1519 " First, see if we should reuse an existing window based on some buffer | |
1520 " variable. | |
1521 let l:target_winnr = -1 | |
1522 let l:split = get(l:valid_args, 'split_mode', 0) | |
1523 let l:reuse_id = get(l:valid_args, 'reuse_id', '') | |
1524 let l:avoid_id = get(l:valid_args, 'avoid_win', -1) | |
1525 if l:reuse_id != '' | |
1526 let l:target_winnr = s:find_buffer_window(l:reuse_id, 1) | |
1527 if l:target_winnr > 0 && l:split != 3 | |
1528 " Unless we'll be opening in a new tab, don't split anymore, since | |
1529 " we found the exact window we wanted. | |
1530 let l:split = 0 | |
1531 endif | |
1532 call s:trace("Looking for window with '".l:reuse_id."', found: ".l:target_winnr) | |
1533 endif | |
1534 " If we didn't find anything, see if we should use the current or previous | |
1535 " window. | |
1536 if l:target_winnr <= 0 | |
1537 let l:use_prev_win = get(l:valid_args, 'use_prev_win', 0) | |
1538 if l:use_prev_win | |
1539 let l:target_winnr = winnr('#') | |
1540 call s:trace("Will use previous window: ".l:target_winnr) | |
1541 endif | |
1542 endif | |
1543 " And let's see if we have a window we should actually avoid. | |
1544 if l:avoid_id >= 0 && | |
1545 \(l:target_winnr == l:avoid_id || | |
1546 \(l:target_winnr <= 0 && winnr() == l:avoid_id)) | |
1547 for wnr in range(1, winnr('$')) | |
1548 if wnr != l:avoid_id | |
1549 call s:trace("Avoiding using window ".l:avoid_id. | |
1550 \", now using: ".wnr) | |
1551 let l:target_winnr = wnr | |
1552 break | |
1553 endif | |
1554 endfor | |
1555 endif | |
1556 " Now let's see what kind of split we want to use, if any. | |
1557 let l:cmd = 'edit ' | |
1558 if l:split == 1 | |
1559 let l:cmd = 'rightbelow split ' | |
1560 elseif l:split == 2 | |
1561 let l:cmd = 'rightbelow vsplit ' | |
1562 elseif l:split == 3 | |
1563 let l:cmd = 'tabedit ' | |
1564 endif | |
1565 | |
1566 " All good now, proceed. | |
1567 if l:target_winnr > 0 | |
1568 execute l:target_winnr . "wincmd w" | |
1569 endif | |
1570 execute 'keepalt ' . l:cmd . fnameescape(l:special) | |
1571 | |
1572 " Set the reuse ID if we had one. | |
1573 if l:reuse_id != '' | |
1574 call s:trace("Setting reuse ID '".l:reuse_id."' on buffer: ".bufnr('%')) | |
1575 call setbufvar('%', l:reuse_id, 1) | |
1576 endif | |
1577 endfunction | |
1578 | |
1579 call s:AddMainCommand("-nargs=* Hgdiffsum :call s:HgDiffSummary('%:p', 0, <f-args>)") | |
1580 call s:AddMainCommand("-nargs=* Hgdiffsumsplit :call s:HgDiffSummary('%:p', 1, <f-args>)") | |
1581 call s:AddMainCommand("-nargs=* Hgvdiffsumsplit :call s:HgDiffSummary('%:p', 2, <f-args>)") | |
1582 call s:AddMainCommand("-nargs=* Hgtabdiffsum :call s:HgDiffSummary('%:p', 3, <f-args>)") | |
1583 | |
1584 " }}} | |
1585 | |
1586 " Hgcommit {{{ | |
1587 | |
1588 function! s:HgCommit(bang, vertical, callback, ...) abort | |
1589 " Get the repo we'll be committing into. | |
1590 let l:repo = s:hg_repo() | |
1591 | |
1592 " Get the list of files to commit. | |
1593 " It can either be several files passed as extra parameters, or an | |
1594 " actual list passed as the first extra parameter. | |
1595 let l:filenames = [] | |
1596 if a:0 | |
1597 let l:filenames = a:000 | |
1598 if a:0 == 1 && type(a:1) == type([]) | |
1599 let l:filenames = a:1 | |
1600 endif | |
1601 endif | |
1602 | |
1603 " Open a commit message file. | |
1604 let l:commit_path = s:tempname('hg-editor-', '.txt') | |
1605 let l:split = a:vertical ? 'vsplit' : 'split' | |
1606 execute l:split . ' ' . l:commit_path | |
1607 call append(0, ['', '']) | |
1608 call append(2, split(s:HgCommit_GenerateMessage(l:repo, l:filenames), '\n')) | |
1609 call cursor(1, 1) | |
1610 | |
1611 " Setup the auto-command that will actually commit on write/exit, | |
1612 " and make the buffer delete itself on exit. | |
1613 let b:mercurial_dir = l:repo.root_dir | |
1614 let b:lawrencium_commit_files = l:filenames | |
1615 if type(a:callback) == type([]) | |
1616 let b:lawrencium_commit_pre_callback = a:callback[0] | |
1617 let b:lawrencium_commit_post_callback = a:callback[1] | |
1618 let b:lawrencium_commit_abort_callback = a:callback[2] | |
1619 else | |
1620 let b:lawrencium_commit_pre_callback = 0 | |
1621 let b:lawrencium_commit_post_callback = a:callback | |
1622 let b:lawrencium_commit_abort_callback = 0 | |
1623 endif | |
1624 setlocal bufhidden=delete | |
1625 setlocal filetype=hgcommit | |
1626 if a:bang | |
1627 autocmd BufDelete <buffer> call s:HgCommit_Execute(expand('<afile>:p'), 0) | |
1628 else | |
1629 autocmd BufDelete <buffer> call s:HgCommit_Execute(expand('<afile>:p'), 1) | |
1630 endif | |
1631 " Make commands available. | |
1632 call s:DefineMainCommands() | |
1633 endfunction | |
1634 | |
1635 let s:hg_status_messages = { | |
1636 \'M': 'modified', | |
1637 \'A': 'added', | |
1638 \'R': 'removed', | |
1639 \'C': 'clean', | |
1640 \'!': 'missing', | |
1641 \'?': 'not tracked', | |
1642 \'I': 'ignored', | |
1643 \' ': '', | |
1644 \} | |
1645 | |
1646 function! s:HgCommit_GenerateMessage(repo, filenames) abort | |
1647 let l:msg = "HG: Enter commit message. Lines beginning with 'HG:' are removed.\n" | |
1648 let l:msg .= "HG: Leave message empty to abort commit.\n" | |
1649 let l:msg .= "HG: Write and quit buffer to proceed.\n" | |
1650 let l:msg .= "HG: --\n" | |
1651 let l:msg .= "HG: user: " . split(a:repo.RunCommand('showconfig ui.username'), '\n')[0] . "\n" | |
1652 let l:msg .= "HG: branch '" . split(a:repo.RunCommand('branch'), '\n')[0] . "'\n" | |
1653 | |
1654 execute 'lcd ' . fnameescape(a:repo.root_dir) | |
1655 if len(a:filenames) | |
1656 let l:status_lines = split(a:repo.RunCommand('status', a:filenames), "\n") | |
1657 else | |
1658 let l:status_lines = split(a:repo.RunCommand('status'), "\n") | |
1659 endif | |
1660 for l:line in l:status_lines | |
1661 if l:line ==# '' | |
1662 continue | |
1663 endif | |
1664 let l:type = matchstr(l:line, '\v^[MARC\!\?I ]') | |
1665 let l:path = l:line[2:] | |
1666 let l:msg .= "HG: " . s:hg_status_messages[l:type] . ' ' . l:path . "\n" | |
1667 endfor | |
1668 | |
1669 return l:msg | |
1670 endfunction | |
1671 | |
1672 function! s:HgCommit_Execute(log_file, show_output) abort | |
1673 " Check if the user actually saved a commit message. | |
1674 if !filereadable(a:log_file) | |
1675 call s:error("abort: Commit message not saved") | |
1676 if exists('b:lawrencium_commit_abort_callback') && | |
1677 \type(b:lawrencium_commit_abort_callback) == type("") && | |
1678 \b:lawrencium_commit_abort_callback != '' | |
1679 call s:trace("Executing abort callback: ".b:lawrencium_commit_abort_callback) | |
1680 execute b:lawrencium_commit_abort_callback | |
1681 endif | |
1682 return | |
1683 endif | |
1684 | |
1685 " Execute a pre-callback if there is one. | |
1686 if exists('b:lawrencium_commit_pre_callback') && | |
1687 \type(b:lawrencium_commit_pre_callback) == type("") && | |
1688 \b:lawrencium_commit_pre_callback != '' | |
1689 call s:trace("Executing pre callback: ".b:lawrencium_commit_pre_callback) | |
1690 execute b:lawrencium_commit_pre_callback | |
1691 endif | |
1692 | |
1693 call s:trace("Committing with log file: " . a:log_file) | |
1694 | |
1695 " Clean all the 'HG: ' lines. | |
1696 let l:is_valid = s:clean_commit_file(a:log_file) | |
1697 if !l:is_valid | |
1698 call s:error("abort: Empty commit message") | |
1699 return | |
1700 endif | |
1701 | |
1702 " Get the repo and commit with the given message. | |
1703 let l:repo = s:hg_repo() | |
1704 let l:hg_args = ['-l', a:log_file] | |
1705 call extend(l:hg_args, b:lawrencium_commit_files) | |
1706 let l:output = l:repo.RunCommand('commit', l:hg_args) | |
1707 if a:show_output && l:output !~# '\v%^\s*%$' | |
1708 call s:trace("Output from hg commit:", 1) | |
1709 for l:output_line in split(l:output, '\n') | |
1710 echom l:output_line | |
1711 endfor | |
1712 endif | |
1713 | |
1714 " Execute a post-callback if there is one. | |
1715 if exists('b:lawrencium_commit_post_callback') && | |
1716 \type(b:lawrencium_commit_post_callback) == type("") && | |
1717 \b:lawrencium_commit_post_callback != '' | |
1718 call s:trace("Executing post callback: ".b:lawrencium_commit_post_callback) | |
1719 execute b:lawrencium_commit_post_callback | |
1720 endif | |
1721 endfunction | |
1722 | |
1723 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgcommit :call s:HgCommit(<bang>0, 0, 0, <f-args>)") | |
1724 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgvcommit :call s:HgCommit(<bang>0, 1, 0, <f-args>)") | |
1725 | |
1726 " }}} | |
1727 | |
1728 " Hgrevert {{{ | |
1729 | |
1730 function! s:HgRevert(bang, ...) abort | |
1731 " Get the files to revert. | |
1732 let l:filenames = a:000 | |
1733 if a:0 == 0 | |
1734 let l:filenames = [ expand('%:p') ] | |
1735 endif | |
1736 if a:bang | |
1737 call insert(l:filenames, '--no-backup', 0) | |
1738 endif | |
1739 | |
1740 " Get the repo and run the command. | |
1741 let l:repo = s:hg_repo() | |
1742 call l:repo.RunCommand('revert', l:filenames) | |
1743 | |
1744 " Re-edit the file to see the change. | |
1745 edit | |
1746 endfunction | |
1747 | |
1748 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgrevert :call s:HgRevert(<bang>0, <f-args>)") | |
1749 | |
1750 " }}} | |
1751 | |
1752 " Hgremove {{{ | |
1753 | |
1754 function! s:HgRemove(bang, ...) abort | |
1755 " Get the files to remove. | |
1756 let l:filenames = a:000 | |
1757 if a:0 == 0 | |
1758 let l:filenames = [ expand('%:p') ] | |
1759 endif | |
1760 if a:bang | |
1761 call insert(l:filenames, '--force', 0) | |
1762 endif | |
1763 | |
1764 " Get the repo and run the command. | |
1765 let l:repo = s:hg_repo() | |
1766 call l:repo.RunCommand('rm', l:filenames) | |
1767 | |
1768 " Re-edit the file to see the change. | |
1769 edit | |
1770 endfunction | |
1771 | |
1772 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgremove :call s:HgRemove(<bang>0, <f-args>)") | |
1773 | |
1774 " }}} | |
1775 | |
1776 " Hglog, Hglogthis {{{ | |
1777 | |
1778 function! s:HgLog(vertical, ...) abort | |
1779 " Get the file or directory to get the log from. | |
1780 " (empty string is for the whole repository) | |
1781 let l:repo = s:hg_repo() | |
1782 if a:0 > 0 && matchstr(a:1, '\v-*') == "" | |
1783 let l:path = l:repo.GetRelativePath(expand(a:1)) | |
1784 else | |
1785 let l:path = '' | |
1786 endif | |
1787 | |
1788 " Get the Lawrencium path for this `hg log`, | |
1789 " open it in a preview window and jump to it. | |
1790 if a:0 > 0 && l:path != "" | |
1791 let l:log_opts = join(a:000[1:-1], ',') | |
1792 else | |
1793 let l:log_opts = join(a:000, ',') | |
1794 endif | |
1795 | |
1796 let l:log_path = l:repo.GetLawrenciumPath(l:path, 'log', l:log_opts) | |
1797 if a:vertical | |
1798 execute 'vertical pedit ' . fnameescape(l:log_path) | |
1799 else | |
1800 execute 'pedit ' . fnameescape(l:log_path) | |
1801 endif | |
1802 wincmd P | |
1803 | |
1804 " Add some other nice commands and mappings. | |
1805 let l:is_file = (l:path != '' && filereadable(l:repo.GetFullPath(l:path))) | |
1806 command! -buffer -nargs=* Hglogdiffsum :call s:HgLog_DiffSummary(1, <f-args>) | |
1807 command! -buffer -nargs=* Hglogvdiffsum :call s:HgLog_DiffSummary(2, <f-args>) | |
1808 command! -buffer -nargs=* Hglogtabdiffsum :call s:HgLog_DiffSummary(3, <f-args>) | |
1809 command! -buffer -nargs=+ -complete=file Hglogexport :call s:HgLog_ExportPatch(<f-args>) | |
1810 if l:is_file | |
1811 command! -buffer Hglogrevedit :call s:HgLog_FileRevEdit() | |
1812 command! -buffer -nargs=* Hglogdiff :call s:HgLog_Diff(0, <f-args>) | |
1813 command! -buffer -nargs=* Hglogvdiff :call s:HgLog_Diff(1, <f-args>) | |
1814 command! -buffer -nargs=* Hglogtabdiff :call s:HgLog_Diff(2, <f-args>) | |
1815 endif | |
1816 | |
1817 if g:lawrencium_define_mappings | |
1818 nnoremap <buffer> <silent> <C-U> :Hglogdiffsum<cr> | |
1819 nnoremap <buffer> <silent> <C-H> :Hglogvdiffsum<cr> | |
1820 nnoremap <buffer> <silent> <cr> :Hglogvdiffsum<cr> | |
1821 nnoremap <buffer> <silent> q :bdelete!<cr> | |
1822 if l:is_file | |
1823 nnoremap <buffer> <silent> <C-E> :Hglogrevedit<cr> | |
1824 nnoremap <buffer> <silent> <C-D> :Hglogtabdiff<cr> | |
1825 nnoremap <buffer> <silent> <C-V> :Hglogvdiff<cr> | |
1826 endif | |
1827 endif | |
1828 | |
1829 " Clean up when the log buffer is deleted. | |
1830 let l:bufobj = s:buffer_obj() | |
1831 call l:bufobj.OnDelete('call s:HgLog_Delete(' . l:bufobj.nr . ')') | |
1832 endfunction | |
1833 | |
1834 function! s:HgLog_Delete(bufnr) | |
1835 if g:lawrencium_auto_close_buffers | |
1836 call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr) | |
1837 call s:delete_dependency_buffers('lawrencium_rev_for', a:bufnr) | |
1838 endif | |
1839 endfunction | |
1840 | |
1841 function! s:HgLog_FileRevEdit() | |
1842 let l:repo = s:hg_repo() | |
1843 let l:bufobj = s:buffer_obj() | |
1844 let l:rev = s:HgLog_GetSelectedRev() | |
1845 let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName()) | |
1846 let l:path = l:repo.GetLawrenciumPath(l:log_path['path'], 'rev', l:rev) | |
1847 | |
1848 " Go to the window we were in before going in the log window, | |
1849 " and open the revision there. | |
1850 wincmd p | |
1851 call s:edit_deletable_buffer('lawrencium_rev_for', l:bufobj.nr, l:path) | |
1852 endfunction | |
1853 | |
1854 function! s:HgLog_Diff(split, ...) abort | |
1855 let l:revs = [] | |
1856 if a:0 >= 2 | |
1857 let l:revs = [a:1, a:2] | |
1858 elseif a:0 == 1 | |
1859 let l:revs = ['p1('.a:1.')', a:1] | |
1860 else | |
1861 let l:sel = s:HgLog_GetSelectedRev() | |
1862 let l:revs = ['p1('.l:sel.')', l:sel] | |
1863 endif | |
1864 | |
1865 let l:repo = s:hg_repo() | |
1866 let l:bufobj = s:buffer_obj() | |
1867 let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName()) | |
1868 let l:path = l:repo.GetFullPath(l:log_path['path']) | |
1869 | |
1870 " Go to the window we were in before going to the log window, | |
1871 " and open the split diff there. | |
1872 if a:split < 2 | |
1873 wincmd p | |
1874 endif | |
1875 call s:HgDiff(l:path, a:split, l:revs) | |
1876 endfunction | |
1877 | |
1878 function! s:HgLog_DiffSummary(split, ...) abort | |
1879 let l:revs = [] | |
1880 if a:0 >= 2 | |
1881 let l:revs = [a:1, a:2] | |
1882 elseif a:0 == 1 | |
1883 let l:revs = [a:1] | |
1884 else | |
1885 let l:revs = [s:HgLog_GetSelectedRev()] | |
1886 endif | |
1887 | |
1888 let l:repo = s:hg_repo() | |
1889 let l:bufobj = s:buffer_obj() | |
1890 let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName()) | |
1891 let l:path = l:repo.GetFullPath(l:log_path['path']) | |
1892 | |
1893 " Go to the window we were in before going in the log window, | |
1894 " and split for the diff summary from there. | |
1895 let l:reuse_id = 'lawrencium_diffsum_for_' . bufnr('%') | |
1896 let l:split_prev_win = (a:split < 3) | |
1897 let l:args = {'reuse_id': l:reuse_id, 'use_prev_win': l:split_prev_win, | |
1898 \'split_mode': a:split} | |
1899 call s:HgDiffSummary(l:path, l:args, l:revs) | |
1900 endfunction | |
1901 | |
1902 function! s:HgLog_GetSelectedRev(...) abort | |
1903 if a:0 == 1 | |
1904 let l:line = getline(a:1) | |
1905 else | |
1906 let l:line = getline('.') | |
1907 endif | |
1908 " Behold, Vim's look-ahead regex syntax again! WTF. | |
1909 let l:rev = matchstr(l:line, '\v^(\d+)(\:)@=') | |
1910 if l:rev == '' | |
1911 call s:throw("Can't parse revision number from line: " . l:line) | |
1912 endif | |
1913 return l:rev | |
1914 endfunction | |
1915 | |
1916 function! s:HgLog_ExportPatch(...) abort | |
1917 let l:patch_name = a:1 | |
1918 if !empty($HG_EXPORT_PATCH_DIR) | |
1919 " Use the patch dir only if user has specified a relative path | |
1920 if has('win32') | |
1921 let l:is_patch_relative = (matchstr(l:patch_name, '\v^([a-zA-Z]:)?\\') == "") | |
1922 else | |
1923 let l:is_patch_relative = (matchstr(l:patch_name, '\v^/') == "") | |
1924 endif | |
1925 if l:is_patch_relative | |
1926 let l:patch_name = s:normalizepath( | |
1927 s:stripslash($HG_EXPORT_PATCH_DIR) . "/" . l:patch_name) | |
1928 endif | |
1929 endif | |
1930 | |
1931 if a:0 == 2 | |
1932 let l:rev = a:2 | |
1933 else | |
1934 let l:rev = s:HgLog_GetSelectedRev() | |
1935 endif | |
1936 | |
1937 let l:repo = s:hg_repo() | |
1938 let l:export_args = ['-o', l:patch_name, '-r', l:rev] | |
1939 | |
1940 call l:repo.RunCommand('export', l:export_args) | |
1941 | |
1942 echom "Created patch: " . l:patch_name | |
1943 endfunction | |
1944 | |
1945 call s:AddMainCommand("Hglogthis :call s:HgLog(0, '%:p')") | |
1946 call s:AddMainCommand("Hgvlogthis :call s:HgLog(1, '%:p')") | |
1947 call s:AddMainCommand("-nargs=* -complete=customlist,s:ListRepoFiles Hglog :call s:HgLog(0, <f-args>)") | |
1948 call s:AddMainCommand("-nargs=* -complete=customlist,s:ListRepoFiles Hgvlog :call s:HgLog(1, <f-args>)") | |
1949 | |
1950 " }}} | |
1951 | |
1952 " Hgannotate, Hgwannotate {{{ | |
1953 | |
1954 function! s:HgAnnotate(bang, verbose, ...) abort | |
1955 " Open the file to annotate if needed. | |
1956 if a:0 > 0 | |
1957 call s:HgEdit(a:bang, a:1) | |
1958 endif | |
1959 | |
1960 " Get the Lawrencium path for the annotated file. | |
1961 let l:path = expand('%:p') | |
1962 let l:bufnr = bufnr('%') | |
1963 let l:repo = s:hg_repo() | |
1964 let l:value = a:verbose ? 'v=1' : '' | |
1965 let l:annotation_path = l:repo.GetLawrenciumPath(l:path, 'annotate', l:value) | |
1966 | |
1967 " Check if we're trying to annotate something with local changes. | |
1968 let l:has_local_edits = 0 | |
1969 let l:path_status = l:repo.RunCommand('status', l:path) | |
1970 if l:path_status != '' | |
1971 call s:trace("Found local edits for '" . l:path . "'. Will annotate parent revision.") | |
1972 let l:has_local_edits = 1 | |
1973 endif | |
1974 | |
1975 if l:has_local_edits | |
1976 " Just open the output of the command. | |
1977 echom "Local edits found, will show the annotations for the parent revision." | |
1978 execute 'edit ' . fnameescape(l:annotation_path) | |
1979 setlocal nowrap nofoldenable | |
1980 setlocal filetype=hgannotate | |
1981 else | |
1982 " Store some info about the current buffer. | |
1983 let l:cur_topline = line('w0') + &scrolloff | |
1984 let l:cur_line = line('.') | |
1985 let l:cur_wrap = &wrap | |
1986 let l:cur_foldenable = &foldenable | |
1987 | |
1988 " Open the annotated file in a split buffer on the left, after | |
1989 " having disabled wrapping and folds on the current file. | |
1990 " Make both windows scroll-bound. | |
1991 setlocal scrollbind nowrap nofoldenable | |
1992 execute 'keepalt leftabove vsplit ' . fnameescape(l:annotation_path) | |
1993 setlocal nonumber | |
1994 setlocal scrollbind nowrap nofoldenable foldcolumn=0 | |
1995 setlocal filetype=hgannotate | |
1996 | |
1997 " When the annotated buffer is deleted, restore the settings we | |
1998 " changed on the current buffer, and go back to that buffer. | |
1999 let l:annotate_buffer = s:buffer_obj() | |
2000 call l:annotate_buffer.OnDelete('execute bufwinnr(' . l:bufnr . ') . "wincmd w"') | |
2001 call l:annotate_buffer.OnDelete('setlocal noscrollbind') | |
2002 if l:cur_wrap | |
2003 call l:annotate_buffer.OnDelete('setlocal wrap') | |
2004 endif | |
2005 if l:cur_foldenable | |
2006 call l:annotate_buffer.OnDelete('setlocal foldenable') | |
2007 endif | |
2008 | |
2009 " Go to the line we were at in the source buffer when we | |
2010 " opened the annotation window. | |
2011 execute l:cur_topline | |
2012 normal! zt | |
2013 execute l:cur_line | |
2014 syncbind | |
2015 | |
2016 " Set the correct window width for the annotations. | |
2017 if a:verbose | |
2018 let l:last_token = match(getline('.'), '\v\d{4}:\s') | |
2019 let l:token_end = 5 | |
2020 else | |
2021 let l:last_token = match(getline('.'), '\v\d{2}:\s') | |
2022 let l:token_end = 3 | |
2023 endif | |
2024 if l:last_token < 0 | |
2025 echoerr "Can't find the end of the annotation columns." | |
2026 else | |
2027 let l:column_count = l:last_token + l:token_end + g:lawrencium_annotate_width_offset | |
2028 execute "vertical resize " . l:column_count | |
2029 setlocal winfixwidth | |
2030 endif | |
2031 endif | |
2032 | |
2033 " Make the annotate buffer a Lawrencium buffer. | |
2034 let b:mercurial_dir = l:repo.root_dir | |
2035 let b:lawrencium_annotated_path = l:path | |
2036 let b:lawrencium_annotated_bufnr = l:bufnr | |
2037 call s:DefineMainCommands() | |
2038 | |
2039 " Add some other nice commands and mappings. | |
2040 command! -buffer Hgannotatediffsum :call s:HgAnnotate_DiffSummary() | |
2041 command! -buffer Hgannotatelog :call s:HgAnnotate_DiffSummary(1) | |
2042 if g:lawrencium_define_mappings | |
2043 nnoremap <buffer> <silent> <cr> :Hgannotatediffsum<cr> | |
2044 nnoremap <buffer> <silent> <leader><cr> :Hgannotatelog<cr> | |
2045 endif | |
2046 | |
2047 " Clean up when the annotate buffer is deleted. | |
2048 let l:bufobj = s:buffer_obj() | |
2049 call l:bufobj.OnDelete('call s:HgAnnotate_Delete(' . l:bufobj.nr . ')') | |
2050 endfunction | |
2051 | |
2052 function! s:HgAnnotate_Delete(bufnr) abort | |
2053 if g:lawrencium_auto_close_buffers | |
2054 call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr) | |
2055 endif | |
2056 endfunction | |
2057 | |
2058 function! s:HgAnnotate_DiffSummary(...) abort | |
2059 " Get the path for the diff of the revision specified under the cursor. | |
2060 let l:line = getline('.') | |
2061 let l:rev_hash = matchstr(l:line, '\v[a-f0-9]{12}') | |
2062 let l:log = (a:0 > 0 ? a:1 : 0) | |
2063 | |
2064 " Get the Lawrencium path for the diff, and the buffer object for the | |
2065 " annotation. | |
2066 let l:repo = s:hg_repo() | |
2067 if l:log | |
2068 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'logpatch', l:rev_hash) | |
2069 else | |
2070 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'diff', l:rev_hash) | |
2071 endif | |
2072 let l:annotate_buffer = s:buffer_obj() | |
2073 | |
2074 " Find a window already displaying diffs for this annotation. | |
2075 let l:diff_winnr = s:find_buffer_window('lawrencium_diff_for', l:annotate_buffer.nr) | |
2076 if l:diff_winnr == -1 | |
2077 " Not found... go back to the main source buffer and open a bottom | |
2078 " split with the diff for the specified revision. | |
2079 execute bufwinnr(b:lawrencium_annotated_bufnr) . 'wincmd w' | |
2080 execute 'rightbelow split ' . fnameescape(l:path) | |
2081 let b:lawrencium_diff_for = l:annotate_buffer.nr | |
2082 let b:lawrencium_quit_on_delete = 1 | |
2083 else | |
2084 " Found! Use that window to open the diff. | |
2085 execute l:diff_winnr . 'wincmd w' | |
2086 execute 'edit ' . fnameescape(l:path) | |
2087 let b:lawrencium_diff_for = l:annotate_buffer.nr | |
2088 endif | |
2089 endfunction | |
2090 | |
2091 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgannotate :call s:HgAnnotate(<bang>0, 0, <f-args>)") | |
2092 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgwannotate :call s:HgAnnotate(<bang>0, 1, <f-args>)") | |
2093 | |
2094 " }}} | |
2095 | |
2096 " Hgqseries {{{ | |
2097 | |
2098 function! s:HgQSeries() abort | |
2099 " Open the MQ series in the preview window and jump to it. | |
2100 let l:repo = s:hg_repo() | |
2101 let l:path = l:repo.GetLawrenciumPath('', 'qseries', '') | |
2102 execute 'pedit ' . fnameescape(l:path) | |
2103 wincmd P | |
2104 | |
2105 " Make the series buffer a Lawrencium buffer. | |
2106 let b:mercurial_dir = l:repo.root_dir | |
2107 call s:DefineMainCommands() | |
2108 | |
2109 " Add some commands and mappings. | |
2110 command! -buffer Hgqseriesgoto :call s:HgQSeries_Goto() | |
2111 command! -buffer Hgqserieseditmessage :call s:HgQSeries_EditMessage() | |
2112 command! -buffer -nargs=+ Hgqseriesrename :call s:HgQSeries_Rename(<f-args>) | |
2113 if g:lawrencium_define_mappings | |
2114 nnoremap <buffer> <silent> <C-g> :Hgqseriesgoto<cr> | |
2115 nnoremap <buffer> <silent> <C-e> :Hgqserieseditmessage<cr> | |
2116 nnoremap <buffer> <silent> q :bdelete!<cr> | |
2117 endif | |
2118 endfunction | |
2119 | |
2120 function! s:HgQSeries_GetCurrentPatchName() abort | |
2121 let l:pos = getpos('.') | |
2122 return getbufvar('%', 'lawrencium_patchname_' . l:pos[1]) | |
2123 endfunction | |
2124 | |
2125 function! s:HgQSeries_Goto() abort | |
2126 let l:repo = s:hg_repo() | |
2127 let l:patchname = s:HgQSeries_GetCurrentPatchName() | |
2128 if len(l:patchname) == 0 | |
2129 call s:error("No patch to go to here.") | |
2130 return | |
2131 endif | |
2132 call l:repo.RunCommand('qgoto', l:patchname) | |
2133 edit | |
2134 endfunction | |
2135 | |
2136 function! s:HgQSeries_Rename(...) abort | |
2137 let l:repo = s:hg_repo() | |
2138 let l:current_name = s:HgQSeries_GetCurrentPatchName() | |
2139 if len(l:current_name) == 0 | |
2140 call s:error("No patch to rename here.") | |
2141 return | |
2142 endif | |
2143 let l:new_name = '"' . join(a:000, ' ') . '"' | |
2144 call l:repo.RunCommand('qrename', l:current_name, l:new_name) | |
2145 edit | |
2146 endfunction | |
2147 | |
2148 function! s:HgQSeries_EditMessage() abort | |
2149 let l:repo = s:hg_repo() | |
2150 let l:patchname = getbufvar('%', 'lawrencium_patchname_top') | |
2151 if len(l:patchname) == 0 | |
2152 call s:error("No patch to edit here.") | |
2153 return | |
2154 endif | |
2155 let l:current = split(l:repo.RunCommand('qheader', l:patchname), '\n') | |
2156 | |
2157 " Open a temp file to write the commit message. | |
2158 let l:temp_file = s:tempname('hg-qrefedit-', '.txt') | |
2159 split | |
2160 execute 'edit ' . fnameescape(l:temp_file) | |
2161 call append(0, 'HG: Enter the new commit message for patch "' . l:patchname . '" here.\n') | |
2162 call append(0, '') | |
2163 call append(0, l:current) | |
2164 call cursor(1, 1) | |
2165 | |
2166 " Make it a temp buffer that will actually change the commit message | |
2167 " when it is saved and closed. | |
2168 let b:mercurial_dir = l:repo.root_dir | |
2169 let b:lawrencium_patchname = l:patchname | |
2170 setlocal bufhidden=delete | |
2171 setlocal filetype=hgcommit | |
2172 autocmd BufDelete <buffer> call s:HgQSeries_EditMessage_Execute(expand('<afile>:p')) | |
2173 | |
2174 call s:DefineMainCommands() | |
2175 endfunction | |
2176 | |
2177 function! s:HgQSeries_EditMessage_Execute(log_file) abort | |
2178 if !filereadable(a:log_file) | |
2179 call s:error("abort: Commit message not saved") | |
2180 return | |
2181 endif | |
2182 | |
2183 " Clean all the 'HG:' lines. | |
2184 let l:is_valid = s:clean_commit_file(a:log_file) | |
2185 if !l:is_valid | |
2186 call s:error("abort: Empty commit message") | |
2187 return | |
2188 endif | |
2189 | |
2190 " Get the repo and edit the given patch. | |
2191 let l:repo = s:hg_repo() | |
2192 let l:hg_args = ['-s', '-l', a:log_file] | |
2193 call l:repo.RunCommand('qref', l:hg_args) | |
2194 endfunction | |
2195 | |
2196 call s:AddMainCommand("Hgqseries call s:HgQSeries()") | |
2197 | |
2198 " }}} | |
2199 | |
2200 " Hgrecord {{{ | |
2201 | |
2202 function! s:HgRecord(split) abort | |
2203 let l:repo = s:hg_repo() | |
2204 let l:orig_buf = s:buffer_obj() | |
2205 let l:tmp_path = l:orig_buf.GetName(':p') . '~record' | |
2206 let l:diff_id = localtime() | |
2207 | |
2208 " Start diffing on the current file, enable some commands. | |
2209 call l:orig_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()') | |
2210 call l:orig_buf.DefineCommand('Hgrecordcommit', ':call s:HgRecord_Execute()') | |
2211 call s:HgDiff_DiffThis(l:diff_id) | |
2212 setlocal foldmethod=diff | |
2213 | |
2214 " Split the window and open the parent revision in the right or bottom | |
2215 " window. Keep the current buffer in the left or top window... we're going | |
2216 " to 'move' those changes into the parent revision. | |
2217 let l:cmd = 'keepalt rightbelow split ' | |
2218 if a:split == 1 | |
2219 let l:cmd = 'keepalt rightbelow vsplit ' | |
2220 endif | |
2221 let l:rev_path = l:repo.GetLawrenciumPath(expand('%:p'), 'rev', '') | |
2222 execute l:cmd . fnameescape(l:rev_path) | |
2223 | |
2224 " This new buffer with the parent revision is set as a Lawrencium buffer. | |
2225 " Let's save it to an actual file and reopen it like that (somehow we | |
2226 " could probably do it with `:saveas` instead but we'd need to reset a | |
2227 " bunch of other buffer settings, and Vim weirdly creates another backup | |
2228 " buffer when you do that). | |
2229 execute 'keepalt write! ' . fnameescape(l:tmp_path) | |
2230 execute 'keepalt edit! ' . fnameescape(l:tmp_path) | |
2231 setlocal bufhidden=delete | |
2232 let b:mercurial_dir = l:repo.root_dir | |
2233 let b:lawrencium_record_for = l:orig_buf.GetName(':p') | |
2234 let b:lawrencium_record_other_nr = l:orig_buf.nr | |
2235 let b:lawrencium_record_commit_split = !a:split | |
2236 call setbufvar(l:orig_buf.nr, 'lawrencium_record_for', '%') | |
2237 call setbufvar(l:orig_buf.nr, 'lawrencium_record_other_nr', bufnr('%')) | |
2238 | |
2239 " Hookup the commit and abort commands. | |
2240 let l:rec_buf = s:buffer_obj() | |
2241 call l:rec_buf.OnDelete('call s:HgRecord_Execute()') | |
2242 call l:rec_buf.DefineCommand('Hgrecordcommit', ':quit') | |
2243 call l:rec_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()') | |
2244 call s:DefineMainCommands() | |
2245 | |
2246 " Make it the other part of the diff. | |
2247 call s:HgDiff_DiffThis(l:diff_id) | |
2248 setlocal foldmethod=diff | |
2249 call l:rec_buf.SetVar('&filetype', l:orig_buf.GetVar('&filetype')) | |
2250 call l:rec_buf.SetVar('&fileformat', l:orig_buf.GetVar('&fileformat')) | |
2251 | |
2252 if g:lawrencium_record_start_in_working_buffer | |
2253 wincmd p | |
2254 endif | |
2255 endfunction | |
2256 | |
2257 function! s:HgRecord_Execute() abort | |
2258 if exists('b:lawrencium_record_abort') | |
2259 " Abort flag is set, let's just cleanup. | |
2260 let l:buf_nr = b:lawrencium_record_for == '%' ? bufnr('%') : | |
2261 \b:lawrencium_record_other_nr | |
2262 call s:HgRecord_CleanUp(l:buf_nr) | |
2263 call s:error("abort: User requested aborting the record operation.") | |
2264 return | |
2265 endif | |
2266 | |
2267 if !exists('b:lawrencium_record_for') | |
2268 call s:throw("This doesn't seem like a record buffer, something's wrong!") | |
2269 endif | |
2270 if b:lawrencium_record_for == '%' | |
2271 " Switch to the 'recording' buffer's window. | |
2272 let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr) | |
2273 call l:buf_obj.MoveToFirstWindow() | |
2274 endif | |
2275 | |
2276 " Setup the commit operation. | |
2277 let l:split = b:lawrencium_record_commit_split | |
2278 let l:working_bufnr = b:lawrencium_record_other_nr | |
2279 let l:working_path = fnameescape(b:lawrencium_record_for) | |
2280 let l:record_path = fnameescape(expand('%:p')) | |
2281 let l:callbacks = [ | |
2282 \'call s:HgRecord_PostExecutePre('.l:working_bufnr.', "'. | |
2283 \escape(l:working_path, '\').'", "'. | |
2284 \escape(l:record_path, '\').'")', | |
2285 \'call s:HgRecord_PostExecutePost('.l:working_bufnr.', "'. | |
2286 \escape(l:working_path, '\').'")', | |
2287 \'call s:HgRecord_PostExecuteAbort('.l:working_bufnr.', "'. | |
2288 \escape(l:record_path, '\').'")' | |
2289 \] | |
2290 call s:trace("Starting commit flow with callbacks: ".string(l:callbacks)) | |
2291 call s:HgCommit(0, l:split, l:callbacks, b:lawrencium_record_for) | |
2292 endfunction | |
2293 | |
2294 function! s:HgRecord_PostExecutePre(working_bufnr, working_path, record_path) abort | |
2295 " Just before committing, we switch the original file with the record | |
2296 " file... we'll restore things in the post-callback below. | |
2297 " We also switch on 'autoread' temporarily on the working buffer so that | |
2298 " we don't have an annoying popup in gVim. | |
2299 if has('dialog_gui') | |
2300 call setbufvar(a:working_bufnr, '&autoread', 1) | |
2301 endif | |
2302 call s:trace("Backuping original file: ".a:working_path) | |
2303 silent call rename(a:working_path, a:working_path.'~working') | |
2304 call s:trace("Committing recorded changes using: ".a:record_path) | |
2305 silent call rename(a:record_path, a:working_path) | |
2306 sleep 200m | |
2307 endfunction | |
2308 | |
2309 function! s:HgRecord_PostExecutePost(working_bufnr, working_path) abort | |
2310 " Recover the back-up file from underneath the buffer. | |
2311 call s:trace("Recovering original file: ".a:working_path) | |
2312 silent call rename(a:working_path.'~working', a:working_path) | |
2313 | |
2314 " Clean up! | |
2315 call s:HgRecord_CleanUp(a:working_bufnr) | |
2316 | |
2317 " Restore default 'autoread'. | |
2318 if has('dialog_gui') | |
2319 set autoread< | |
2320 endif | |
2321 endfunction | |
2322 | |
2323 function! s:HgRecord_PostExecuteAbort(working_bufnr, record_path) abort | |
2324 call s:HgRecord_CleanUp(a:working_bufnr) | |
2325 call s:trace("Delete discarded record file: ".a:record_path) | |
2326 silent call delete(a:record_path) | |
2327 endfunction | |
2328 | |
2329 function! s:HgRecord_Abort() abort | |
2330 if b:lawrencium_record_for == '%' | |
2331 " We're in the working directory buffer. Switch to the 'recording' | |
2332 " buffer and quit. | |
2333 let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr) | |
2334 call l:buf_obj.MoveToFirstWindow() | |
2335 endif | |
2336 " We're now in the 'recording' buffer... set the abort flag and quit, | |
2337 " which will run the execution (it will early out and clean things up). | |
2338 let b:lawrencium_record_abort = 1 | |
2339 quit! | |
2340 endfunction | |
2341 | |
2342 function! s:HgRecord_CleanUp(buf_nr) abort | |
2343 " Get in the original buffer and clean the local commands/variables. | |
2344 let l:buf_obj = s:buffer_obj(a:buf_nr) | |
2345 call l:buf_obj.MoveToFirstWindow() | |
2346 if !exists('b:lawrencium_record_for') || b:lawrencium_record_for != '%' | |
2347 call s:throw("Cleaning up something else than the original buffer ". | |
2348 \"for a record operation. That's suspiciously incorrect! ". | |
2349 \"Aborting.") | |
2350 endif | |
2351 call l:buf_obj.DeleteCommand('Hgrecordabort') | |
2352 call l:buf_obj.DeleteCommand('Hgrecordcommit') | |
2353 unlet b:lawrencium_record_for | |
2354 unlet b:lawrencium_record_other_nr | |
2355 endfunction | |
2356 | |
2357 call s:AddMainCommand("Hgrecord call s:HgRecord(0)") | |
2358 call s:AddMainCommand("Hgvrecord call s:HgRecord(1)") | |
2359 | |
2360 " }}} | |
2361 | |
2362 " Autoload Functions {{{ | |
2363 | |
2364 " Prints a summary of the current repo (if any) that's appropriate for | |
2365 " displaying on the status line. | |
2366 function! lawrencium#statusline(...) | |
2367 if !exists('b:mercurial_dir') | |
2368 return '' | |
2369 endif | |
2370 let l:repo = s:hg_repo() | |
2371 let l:prefix = (a:0 > 0 ? a:1 : '') | |
2372 let l:suffix = (a:0 > 1 ? a:2 : '') | |
2373 let l:branch = 'default' | |
2374 let l:branch_file = l:repo.GetFullPath('.hg/branch') | |
2375 if filereadable(l:branch_file) | |
2376 let l:branch = readfile(l:branch_file)[0] | |
2377 endif | |
2378 let l:bookmarks = '' | |
2379 let l:bookmarks_file = l:repo.GetFullPath('.hg/bookmarks.current') | |
2380 if filereadable(l:bookmarks_file) | |
2381 let l:bookmarks = join(readfile(l:bookmarks_file), ', ') | |
2382 endif | |
2383 let l:line = l:prefix . l:branch | |
2384 if strlen(l:bookmarks) > 0 | |
2385 let l:line = l:line . ' - ' . l:bookmarks | |
2386 endif | |
2387 let l:line = l:line . l:suffix | |
2388 return l:line | |
2389 endfunction | |
2390 | |
2391 " Rescans the current buffer for setting up Mercurial commands. | |
2392 " Passing '1' as the parameter enables debug traces temporarily. | |
2393 function! lawrencium#rescan(...) | |
2394 if exists('b:mercurial_dir') | |
2395 unlet b:mercurial_dir | |
2396 endif | |
2397 if a:0 && a:1 | |
2398 let l:trace_backup = g:lawrencium_trace | |
2399 let g:lawrencium_trace = 1 | |
2400 endif | |
2401 call s:setup_buffer_commands() | |
2402 if a:0 && a:1 | |
2403 let g:lawrencium_trace = l:trace_backup | |
2404 endif | |
2405 endfunction | |
2406 | |
2407 " Enables/disables the debug trace. | |
2408 function! lawrencium#debugtrace(...) | |
2409 let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1)) | |
2410 echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.") | |
2411 endfunction | |
2412 | |
2413 " }}} | |
2414 |