Mercurial > vim-lawrencium
comparison autoload/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 | |
children | 4d5f4233b04e |
comparison
equal
deleted
inserted
replaced
138:a2d823c82e5f | 139:065625e1bb31 |
---|---|
1 | |
2 " Path Utility {{{ | |
3 | |
4 " Strips the ending slash in a path. | |
5 function! lawrencium#stripslash(path) | |
6 return fnamemodify(a:path, ':s?[/\\]$??') | |
7 endfunction | |
8 | |
9 " Returns whether a path is absolute. | |
10 function! lawrencium#isabspath(path) | |
11 return a:path =~# '\v^(\w\:)?[/\\]' | |
12 endfunction | |
13 | |
14 " Normalizes the slashes in a path. | |
15 function! lawrencium#normalizepath(path) | |
16 if exists('+shellslash') && &shellslash | |
17 return substitute(a:path, '\v/', '\\', 'g') | |
18 elseif has('win32') | |
19 return substitute(a:path, '\v/', '\\', 'g') | |
20 else | |
21 return a:path | |
22 endif | |
23 endfunction | |
24 | |
25 " Shell-slashes the path (opposite of `normalizepath`). | |
26 function! lawrencium#shellslash(path) | |
27 if exists('+shellslash') && !&shellslash | |
28 return substitute(a:path, '\v\\', '/', 'g') | |
29 else | |
30 return a:path | |
31 endif | |
32 endfunction | |
33 | |
34 " Like tempname() but with some control over the filename. | |
35 function! lawrencium#tempname(name, ...) | |
36 let l:path = tempname() | |
37 let l:result = fnamemodify(l:path, ':h') . '/' . a:name . fnamemodify(l:path, ':t') | |
38 if a:0 > 0 | |
39 let l:result = l:result . a:1 | |
40 endif | |
41 return l:result | |
42 endfunction | |
43 | |
44 " Delete a temporary file if it exists. | |
45 function! lawrencium#clean_tempfile(path) | |
46 if filewritable(a:path) | |
47 call lawrencium#trace("Cleaning up temporary file: " . a:path) | |
48 call delete(a:path) | |
49 endif | |
50 endfunction | |
51 | |
52 " }}} | |
53 | |
54 " Logging {{{ | |
55 | |
56 " Prints a message if debug tracing is enabled. | |
57 function! lawrencium#trace(message, ...) | |
58 if g:lawrencium_trace || (a:0 && a:1) | |
59 let l:message = "lawrencium: " . a:message | |
60 echom l:message | |
61 endif | |
62 endfunction | |
63 | |
64 " Prints an error message with 'lawrencium error' prefixed to it. | |
65 function! lawrencium#error(message) | |
66 echom "lawrencium error: " . a:message | |
67 endfunction | |
68 | |
69 " Throw a Lawrencium exception message. | |
70 function! lawrencium#throw(message) | |
71 let v:errmsg = "lawrencium: " . a:message | |
72 throw v:errmsg | |
73 endfunction | |
74 | |
75 " }}} | |
76 | |
77 " Repositories {{{ | |
78 | |
79 " Finds the repository root given a path inside that repository. | |
80 " Throw an error if not repository is found. | |
81 function! lawrencium#find_repo_root(path) | |
82 let l:path = lawrencium#stripslash(a:path) | |
83 let l:previous_path = "" | |
84 while l:path != l:previous_path | |
85 if isdirectory(l:path . '/.hg') | |
86 return lawrencium#normalizepath(simplify(fnamemodify(l:path, ':p'))) | |
87 endif | |
88 let l:previous_path = l:path | |
89 let l:path = fnamemodify(l:path, ':h') | |
90 endwhile | |
91 call lawrencium#throw("No Mercurial repository found above: " . a:path) | |
92 endfunction | |
93 | |
94 " Given a Lawrencium path (e.g: 'lawrencium:///repo/root_dir//foo/bar/file.py//rev=34'), extract | |
95 " the repository root, relative file path and revision number/changeset ID. | |
96 " | |
97 " If a second argument exists, it must be: | |
98 " - `relative`: to make the file path relative to the repository root. | |
99 " - `absolute`: to make the file path absolute. | |
100 " | |
101 function! lawrencium#parse_lawrencium_path(lawrencium_path, ...) | |
102 let l:repo_path = lawrencium#shellslash(a:lawrencium_path) | |
103 let l:repo_path = substitute(l:repo_path, '\\ ', ' ', 'g') | |
104 if l:repo_path =~? '\v^lawrencium://' | |
105 let l:repo_path = strpart(l:repo_path, strlen('lawrencium://')) | |
106 endif | |
107 | |
108 let l:root_dir = '' | |
109 let l:at_idx = stridx(l:repo_path, '//') | |
110 if l:at_idx >= 0 | |
111 let l:root_dir = strpart(l:repo_path, 0, l:at_idx) | |
112 let l:repo_path = strpart(l:repo_path, l:at_idx + 2) | |
113 endif | |
114 | |
115 let l:value = '' | |
116 let l:action = '' | |
117 let l:actionidx = stridx(l:repo_path, '//') | |
118 if l:actionidx >= 0 | |
119 let l:action = strpart(l:repo_path, l:actionidx + 2) | |
120 let l:repo_path = strpart(l:repo_path, 0, l:actionidx) | |
121 | |
122 let l:equalidx = stridx(l:action, '=') | |
123 if l:equalidx >= 0 | |
124 let l:value = strpart(l:action, l:equalidx + 1) | |
125 let l:action = strpart(l:action, 0, l:equalidx) | |
126 endif | |
127 endif | |
128 | |
129 if a:0 > 0 | |
130 execute 'cd! ' . fnameescape(l:root_dir) | |
131 if a:1 == 'relative' | |
132 let l:repo_path = fnamemodify(l:repo_path, ':.') | |
133 elseif a:1 == 'absolute' | |
134 let l:repo_path = fnamemodify(l:repo_path, ':p') | |
135 endif | |
136 execute 'cd! -' | |
137 endif | |
138 | |
139 let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value } | |
140 return l:result | |
141 endfunction | |
142 | |
143 " Clean up all the 'HG:' lines from a commit message, and see if there's | |
144 " any message left (Mercurial does this automatically, usually, but | |
145 " apparently not when you feed it a log file...). | |
146 function! lawrencium#clean_commit_file(log_file) abort | |
147 let l:lines = readfile(a:log_file) | |
148 call filter(l:lines, "v:val !~# '\\v^HG:'") | |
149 if len(filter(copy(l:lines), "v:val !~# '\\v^\\s*$'")) == 0 | |
150 return 0 | |
151 endif | |
152 call writefile(l:lines, a:log_file) | |
153 return 1 | |
154 endfunction | |
155 | |
156 " }}} | |
157 | |
158 " Vim Utility {{{ | |
159 | |
160 " Finds a window whose displayed buffer has a given variable | |
161 " set to the given value. | |
162 function! lawrencium#find_buffer_window(varname, varvalue) abort | |
163 for wnr in range(1, winnr('$')) | |
164 let l:bnr = winbufnr(wnr) | |
165 if getbufvar(l:bnr, a:varname) == a:varvalue | |
166 return l:wnr | |
167 endif | |
168 endfor | |
169 return -1 | |
170 endfunction | |
171 | |
172 " Opens a buffer in a way that makes it easy to delete it later: | |
173 " - if the about-to-be previous buffer doesn't have a given variable, | |
174 " just open the new buffer. | |
175 " - if the about-to-be previous buffer has a given variable, open the | |
176 " new buffer with the `keepalt` option to make it so that the | |
177 " actual previous buffer (returned by things like `bufname('#')`) | |
178 " is the original buffer that was there before the first deletable | |
179 " buffer was opened. | |
180 function! lawrencium#edit_deletable_buffer(varname, varvalue, path) abort | |
181 let l:edit_cmd = 'edit ' | |
182 if getbufvar('%', a:varname) != '' | |
183 let l:edit_cmd = 'keepalt edit ' | |
184 endif | |
185 execute l:edit_cmd . fnameescape(a:path) | |
186 call setbufvar('%', a:varname, a:varvalue) | |
187 endfunction | |
188 | |
189 " Deletes all buffers that have a given variable set to a given value. | |
190 " For each buffer, if it is not shown in any window, it will be just deleted. | |
191 " If it is shown in a window, that window will be switched to the alternate | |
192 " buffer before the buffer is deleted, unless the `lawrencium_quit_on_delete` | |
193 " variable is set to `1`, in which case the window is closed too. | |
194 function! lawrencium#delete_dependency_buffers(varname, varvalue) abort | |
195 let l:cur_winnr = winnr() | |
196 for bnr in range(1, bufnr('$')) | |
197 if getbufvar(bnr, a:varname) == a:varvalue | |
198 " Delete this buffer if it is not shown in any window. | |
199 " Otherwise, display the alternate buffer before deleting | |
200 " it so the window is not closed. | |
201 let l:bwnr = bufwinnr(bnr) | |
202 if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1 | |
203 if bufloaded(l:bnr) | |
204 call lawrencium#trace("Deleting dependency buffer " . bnr) | |
205 execute "bdelete! " . bnr | |
206 else | |
207 call lawrencium#trace("Dependency buffer " . bnr . " is already unladed.") | |
208 endif | |
209 else | |
210 execute l:bwnr . "wincmd w" | |
211 " TODO: better handle case where there's no previous/alternate buffer? | |
212 let l:prev_bnr = bufnr('#') | |
213 if l:prev_bnr > 0 && bufloaded(l:prev_bnr) | |
214 execute "buffer " . l:prev_bnr | |
215 if bufloaded(l:bnr) | |
216 call lawrencium#trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr) | |
217 execute "bdelete! " . bnr | |
218 else | |
219 call lawrencium#trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr) | |
220 endif | |
221 else | |
222 call lawrencium#trace("Deleting dependency buffer " . bnr . " and window.") | |
223 bdelete! | |
224 endif | |
225 endif | |
226 endif | |
227 endfor | |
228 if l:cur_winnr != winnr() | |
229 call lawrencium#trace("Returning to window " . l:cur_winnr) | |
230 execute l:cur_winnr . "wincmd w" | |
231 endif | |
232 endfunction | |
233 | |
234 " }}} | |
235 | |
236 " Mercurial Repository Object {{{ | |
237 | |
238 " Let's define a Mercurial repo 'class' using prototype-based object-oriented | |
239 " programming. | |
240 " | |
241 " The prototype dictionary. | |
242 let s:HgRepo = {} | |
243 | |
244 " Constructor. | |
245 function! s:HgRepo.New(path) abort | |
246 let l:newRepo = copy(self) | |
247 let l:newRepo.root_dir = lawrencium#find_repo_root(a:path) | |
248 call lawrencium#trace("Built new Mercurial repository object at : " . l:newRepo.root_dir) | |
249 return l:newRepo | |
250 endfunction | |
251 | |
252 " Gets a full path given a repo-relative path. | |
253 function! s:HgRepo.GetFullPath(path) abort | |
254 let l:root_dir = self.root_dir | |
255 if lawrencium#isabspath(a:path) | |
256 call lawrencium#throw("Expected relative path, got absolute path: " . a:path) | |
257 endif | |
258 return lawrencium#normalizepath(l:root_dir . a:path) | |
259 endfunction | |
260 | |
261 " Gets a repo-relative path given any path. | |
262 function! s:HgRepo.GetRelativePath(path) abort | |
263 execute 'lcd! ' . fnameescape(self.root_dir) | |
264 let l:relative_path = fnamemodify(a:path, ':.') | |
265 execute 'lcd! -' | |
266 return l:relative_path | |
267 endfunction | |
268 | |
269 " Gets, and optionally creates, a temp folder for some operation in the `.hg` | |
270 " directory. | |
271 function! s:HgRepo.GetTempDir(path, ...) abort | |
272 let l:tmp_dir = self.GetFullPath('.hg/lawrencium/' . a:path) | |
273 if !isdirectory(l:tmp_dir) | |
274 if a:0 > 0 && !a:1 | |
275 return '' | |
276 endif | |
277 call mkdir(l:tmp_dir, 'p') | |
278 endif | |
279 return l:tmp_dir | |
280 endfunction | |
281 | |
282 " Gets a list of files matching a root-relative pattern. | |
283 " If a flag is passed and is TRUE, a slash will be appended to all | |
284 " directories. | |
285 function! s:HgRepo.Glob(pattern, ...) abort | |
286 let l:root_dir = self.root_dir | |
287 if (a:pattern =~# '\v^[/\\]') | |
288 let l:root_dir = lawrencium#stripslash(l:root_dir) | |
289 endif | |
290 let l:matches = split(glob(l:root_dir . a:pattern), '\n') | |
291 if a:0 && a:1 | |
292 for l:idx in range(len(l:matches)) | |
293 if !filereadable(l:matches[l:idx]) | |
294 let l:matches[l:idx] = l:matches[l:idx] . '/' | |
295 endif | |
296 endfor | |
297 endif | |
298 let l:strip_len = len(l:root_dir) | |
299 call map(l:matches, 'v:val[l:strip_len : -1]') | |
300 return l:matches | |
301 endfunction | |
302 | |
303 " Gets a full Mercurial command. | |
304 function! s:HgRepo.GetCommand(command, ...) abort | |
305 " If there's only one argument, and it's a list, then use that as the | |
306 " argument list. | |
307 let l:arg_list = a:000 | |
308 if a:0 == 1 && type(a:1) == type([]) | |
309 let l:arg_list = a:1 | |
310 endif | |
311 let l:prev_shellslash = &shellslash | |
312 setlocal noshellslash | |
313 let l:hg_command = g:lawrencium_hg_executable . ' --repository ' . shellescape(lawrencium#stripslash(self.root_dir)) | |
314 let l:hg_command = l:hg_command . ' ' . a:command | |
315 for l:arg in l:arg_list | |
316 let l:hg_command = l:hg_command . ' ' . shellescape(l:arg) | |
317 endfor | |
318 if l:prev_shellslash | |
319 setlocal shellslash | |
320 endif | |
321 return l:hg_command | |
322 endfunction | |
323 | |
324 " Runs a Mercurial command in the repo. | |
325 function! s:HgRepo.RunCommand(command, ...) abort | |
326 let l:all_args = [1, a:command] + a:000 | |
327 return call(self['RunCommandEx'], l:all_args, self) | |
328 endfunction | |
329 | |
330 function! s:HgRepo.RunCommandEx(plain_mode, command, ...) abort | |
331 let l:prev_hgplain = $HGPLAIN | |
332 if a:plain_mode | |
333 let $HGPLAIN = 'true' | |
334 endif | |
335 let l:all_args = [a:command] + a:000 | |
336 let l:hg_command = call(self['GetCommand'], l:all_args, self) | |
337 call lawrencium#trace("Running Mercurial command: " . l:hg_command) | |
338 let l:cmd_out = system(l:hg_command) | |
339 if a:plain_mode | |
340 let $HGPLAIN = l:prev_hgplain | |
341 endif | |
342 return l:cmd_out | |
343 endfunction | |
344 | |
345 " Runs a Mercurial command in the repo and reads its output into the current | |
346 " buffer. | |
347 function! s:HgRepo.ReadCommandOutput(command, ...) abort | |
348 function! s:PutOutputIntoBuffer(command_line) | |
349 let l:was_buffer_empty = (line('$') == 1 && getline(1) == '') | |
350 execute '0read!' . escape(a:command_line, '%#\') | |
351 if l:was_buffer_empty " (Always true?) | |
352 " '0read' inserts before the cursor, leaving a blank line which | |
353 " needs to be deleted... but if there are folds in this thing, we | |
354 " must open them all first otherwise we could delete the whole | |
355 " contents of the last fold (since Vim may close them all by | |
356 " default). | |
357 normal! zRG"_dd | |
358 endif | |
359 endfunction | |
360 | |
361 let l:all_args = [a:command] + a:000 | |
362 let l:hg_command = call(self['GetCommand'], l:all_args, self) | |
363 call lawrencium#trace("Running Mercurial command: " . l:hg_command) | |
364 call s:PutOutputIntoBuffer(l:hg_command) | |
365 endfunction | |
366 | |
367 " Build a Lawrencium path for the given file and action. | |
368 " By default, the given path will be made relative to the repository root, | |
369 " unless '0' is passed as the 4th argument. | |
370 function! s:HgRepo.GetLawrenciumPath(path, action, value, ...) abort | |
371 let l:path = a:path | |
372 if a:0 == 0 || !a:1 | |
373 let l:path = self.GetRelativePath(a:path) | |
374 endif | |
375 let l:path = fnameescape(l:path) | |
376 let l:result = 'lawrencium://' . lawrencium#stripslash(self.root_dir) . '//' . l:path | |
377 if a:action !=? '' | |
378 let l:result = l:result . '//' . a:action | |
379 if a:value !=? '' | |
380 let l:result = l:result . '=' . a:value | |
381 endif | |
382 endif | |
383 return l:result | |
384 endfunction | |
385 | |
386 " Repo cache map. | |
387 let s:buffer_repos = {} | |
388 | |
389 " Get a cached repo. | |
390 function! lawrencium#hg_repo(...) abort | |
391 " Use the given path, or the mercurial directory of the current buffer. | |
392 if a:0 == 0 | |
393 if exists('b:mercurial_dir') | |
394 let l:path = b:mercurial_dir | |
395 else | |
396 let l:path = lawrencium#find_repo_root(expand('%:p')) | |
397 endif | |
398 else | |
399 let l:path = a:1 | |
400 endif | |
401 " Find a cache repo instance, or make a new one. | |
402 if has_key(s:buffer_repos, l:path) | |
403 return get(s:buffer_repos, l:path) | |
404 else | |
405 let l:repo = s:HgRepo.New(l:path) | |
406 let s:buffer_repos[l:path] = l:repo | |
407 return l:repo | |
408 endif | |
409 endfunction | |
410 | |
411 " }}} | |
412 | |
413 " Buffer Object {{{ | |
414 | |
415 " The prototype dictionary. | |
416 let s:Buffer = {} | |
417 | |
418 " Constructor. | |
419 function! s:Buffer.New(number) dict abort | |
420 let l:newBuffer = copy(self) | |
421 let l:newBuffer.nr = a:number | |
422 let l:newBuffer.var_backup = {} | |
423 let l:newBuffer.cmd_names = {} | |
424 let l:newBuffer.on_delete = [] | |
425 let l:newBuffer.on_winleave = [] | |
426 let l:newBuffer.on_unload = [] | |
427 execute 'augroup lawrencium_buffer_' . a:number | |
428 execute ' autocmd!' | |
429 execute ' autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')' | |
430 execute 'augroup end' | |
431 call lawrencium#trace("Built new buffer object for buffer: " . a:number) | |
432 return l:newBuffer | |
433 endfunction | |
434 | |
435 function! s:Buffer.GetName(...) dict abort | |
436 let l:name = bufname(self.nr) | |
437 if a:0 > 0 | |
438 let l:name = fnamemodify(l:name, a:1) | |
439 endif | |
440 return l:name | |
441 endfunction | |
442 | |
443 function! s:Buffer.GetVar(var) dict abort | |
444 return getbufvar(self.nr, a:var) | |
445 endfunction | |
446 | |
447 function! s:Buffer.SetVar(var, value) dict abort | |
448 if !has_key(self.var_backup, a:var) | |
449 let self.var_backup[a:var] = getbufvar(self.nr, a:var) | |
450 endif | |
451 return setbufvar(self.nr, a:var, a:value) | |
452 endfunction | |
453 | |
454 function! s:Buffer.RestoreVars() dict abort | |
455 for key in keys(self.var_backup) | |
456 setbufvar(self.nr, key, self.var_backup[key]) | |
457 endfor | |
458 endfunction | |
459 | |
460 function! s:Buffer.DefineCommand(name, ...) dict abort | |
461 if a:0 == 0 | |
462 call lawrencium#throw("Not enough parameters for s:Buffer.DefineCommands()") | |
463 endif | |
464 if a:0 == 1 | |
465 let l:flags = '' | |
466 let l:cmd = a:1 | |
467 else | |
468 let l:flags = a:1 | |
469 let l:cmd = a:2 | |
470 endif | |
471 if has_key(self.cmd_names, a:name) | |
472 call lawrencium#throw("Command '".a:name."' is already defined in buffer ".self.nr) | |
473 endif | |
474 if bufnr('%') != self.nr | |
475 call lawrencium#throw("You must move to buffer ".self.nr."first before defining local commands") | |
476 endif | |
477 let self.cmd_names[a:name] = 1 | |
478 let l:real_flags = '' | |
479 if type(l:flags) == type('') | |
480 let l:real_flags = l:flags | |
481 endif | |
482 execute 'command -buffer '.l:real_flags.' '.a:name.' '.l:cmd | |
483 endfunction | |
484 | |
485 function! s:Buffer.DeleteCommand(name) dict abort | |
486 if !has_key(self.cmd_names, a:name) | |
487 call lawrencium#throw("Command '".a:name."' has not been defined in buffer ".self.nr) | |
488 endif | |
489 if bufnr('%') != self.nr | |
490 call lawrencium#throw("You must move to buffer ".self.nr."first before deleting local commands") | |
491 endif | |
492 execute 'delcommand '.a:name | |
493 call remove(self.cmd_names, a:name) | |
494 endfunction | |
495 | |
496 function! s:Buffer.DeleteCommands() dict abort | |
497 if bufnr('%') != self.nr | |
498 call lawrencium#throw("You must move to buffer ".self.nr."first before deleting local commands") | |
499 endif | |
500 for name in keys(self.cmd_names) | |
501 execute 'delcommand '.name | |
502 endfor | |
503 let self.cmd_names = {} | |
504 endfunction | |
505 | |
506 function! s:Buffer.MoveToFirstWindow() dict abort | |
507 let l:win_nr = bufwinnr(self.nr) | |
508 if l:win_nr < 0 | |
509 if a:0 > 0 && a:1 == 0 | |
510 return 0 | |
511 endif | |
512 call lawrencium#throw("No windows currently showing buffer ".self.nr) | |
513 endif | |
514 execute l:win_nr.'wincmd w' | |
515 return 1 | |
516 endfunction | |
517 | |
518 function! s:Buffer.OnDelete(cmd) dict abort | |
519 call lawrencium#trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd) | |
520 call add(self.on_delete, a:cmd) | |
521 endfunction | |
522 | |
523 function! s:Buffer.OnWinLeave(cmd) dict abort | |
524 if len(self.on_winleave) == 0 | |
525 call lawrencium#trace("Adding BufWinLeave auto-command on buffer " . self.nr) | |
526 execute 'augroup lawrencium_buffer_' . self.nr . '_winleave' | |
527 execute ' autocmd!' | |
528 execute ' autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')' | |
529 execute 'augroup end' | |
530 endif | |
531 call lawrencium#trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd) | |
532 call add(self.on_winleave, a:cmd) | |
533 endfunction | |
534 | |
535 function! s:Buffer.OnUnload(cmd) dict abort | |
536 if len(self.on_unload) == 0 | |
537 call lawrencium#trace("Adding BufUnload auto-command on buffer " . self.nr) | |
538 execute 'augroup lawrencium_buffer_' . self.nr . '_unload' | |
539 execute ' autocmd!' | |
540 execute ' autocmd BufUnload <buffer=' . self.nr . '> call s:buffer_on_unload(' . self.nr . ')' | |
541 execute 'augroup end' | |
542 endif | |
543 call lawrencium#trace("Adding BufUnload callback for buffer " . self.nr . ": " . a:cmd) | |
544 call add(self.on_unload, a:cmd) | |
545 endfunction | |
546 | |
547 let s:buffer_objects = {} | |
548 | |
549 " Get a buffer instance for the specified buffer number, or the | |
550 " current buffer if nothing is specified. | |
551 function! lawrencium#buffer_obj(...) abort | |
552 let l:bufnr = a:0 ? a:1 : bufnr('%') | |
553 if !has_key(s:buffer_objects, l:bufnr) | |
554 let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr) | |
555 endif | |
556 return s:buffer_objects[l:bufnr] | |
557 endfunction | |
558 | |
559 " Execute all the "on delete" callbacks. | |
560 function! s:buffer_on_delete(number) abort | |
561 let l:bufobj = s:buffer_objects[a:number] | |
562 call lawrencium#trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr) | |
563 for cmd in l:bufobj.on_delete | |
564 call lawrencium#trace(" [" . cmd . "]") | |
565 execute cmd | |
566 endfor | |
567 call lawrencium#trace("Deleted buffer object " . l:bufobj.nr) | |
568 call remove(s:buffer_objects, l:bufobj.nr) | |
569 execute 'augroup lawrencium_buffer_' . l:bufobj.nr | |
570 execute ' autocmd!' | |
571 execute 'augroup end' | |
572 endfunction | |
573 | |
574 " Execute all the "on winleave" callbacks. | |
575 function! s:buffer_on_winleave(number) abort | |
576 let l:bufobj = s:buffer_objects[a:number] | |
577 call lawrencium#trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr) | |
578 for cmd in l:bufobj.on_winleave | |
579 call lawrencium#trace(" [" . cmd . "]") | |
580 execute cmd | |
581 endfor | |
582 execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_winleave' | |
583 execute ' autocmd!' | |
584 execute 'augroup end' | |
585 endfunction | |
586 | |
587 " Execute all the "on unload" callbacks. | |
588 function! s:buffer_on_unload(number) abort | |
589 let l:bufobj = s:buffer_objects[a:number] | |
590 call lawrencium#trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr) | |
591 for cmd in l:bufobj.on_unload | |
592 call lawrencium#trace(" [" . cmd . "]") | |
593 execute cmd | |
594 endfor | |
595 execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload' | |
596 execute ' autocmd!' | |
597 execute 'augroup end' | |
598 endfunction | |
599 | |
600 " }}} | |
601 | |
602 " Buffer Commands Management {{{ | |
603 | |
604 " Store the commands for Lawrencium-enabled buffers so that we can add them in | |
605 " batch when we need to. | |
606 let s:main_commands = [] | |
607 | |
608 function! lawrencium#add_command(command) abort | |
609 let s:main_commands += [a:command] | |
610 endfunction | |
611 | |
612 function! lawrencium#define_commands() | |
613 for l:command in s:main_commands | |
614 execute 'command! -buffer ' . l:command | |
615 endfor | |
616 endfunction | |
617 | |
618 augroup lawrencium_main | |
619 autocmd! | |
620 autocmd User Lawrencium call lawrencium#define_commands() | |
621 augroup end | |
622 | |
623 " Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo. | |
624 " If the file is not in a Mercurial repo, just exit silently. | |
625 function! lawrencium#setup_buffer_commands() abort | |
626 call lawrencium#trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...") | |
627 let l:do_setup = 1 | |
628 if exists('b:mercurial_dir') | |
629 if b:mercurial_dir =~# '\v^\s*$' | |
630 unlet b:mercurial_dir | |
631 else | |
632 let l:do_setup = 0 | |
633 endif | |
634 endif | |
635 try | |
636 let l:repo = lawrencium#hg_repo() | |
637 catch /^lawrencium\:/ | |
638 return | |
639 endtry | |
640 let b:mercurial_dir = l:repo.root_dir | |
641 if exists('b:mercurial_dir') && l:do_setup | |
642 call lawrencium#trace("Setting Mercurial commands for buffer '" . bufname('%')) | |
643 call lawrencium#trace(" with repo : " . expand(b:mercurial_dir)) | |
644 silent doautocmd User Lawrencium | |
645 endif | |
646 endfunction | |
647 | |
648 " }}} | |
649 | |
650 " Commands Auto-Complete {{{ | |
651 | |
652 " Auto-complete function for commands that take repo-relative file paths. | |
653 function! lawrencium#list_repo_files(ArgLead, CmdLine, CursorPos) abort | |
654 let l:matches = lawrencium#hg_repo().Glob(a:ArgLead . '*', 1) | |
655 call map(l:matches, 'lawrencium#normalizepath(v:val)') | |
656 return l:matches | |
657 endfunction | |
658 | |
659 " Auto-complete function for commands that take repo-relative directory paths. | |
660 function! lawrencium#list_repo_dirs(ArgLead, CmdLine, CursorPos) abort | |
661 let l:matches = lawrencium#hg_repo().Glob(a:ArgLead . '*/') | |
662 call map(l:matches, 'lawrencium#normalizepath(v:val)') | |
663 return l:matches | |
664 endfunction | |
665 | |
666 " }}} | |
667 | |
668 " Lawrencium Files {{{ | |
669 | |
670 " Generic read | |
671 let s:lawrencium_file_readers = {} | |
672 let s:lawrencium_file_customoptions = {} | |
673 | |
674 function! lawrencium#add_reader(action, callback, ...) abort | |
675 if has_key(s:lawrencium_file_readers, a:action) | |
676 call lawrencium#throw("Lawrencium file '".a:action."' has alredy been registered.") | |
677 endif | |
678 let s:lawrencium_file_readers[a:action] = function(a:callback) | |
679 if a:0 && a:1 | |
680 let s:lawrencium_file_customoptions[a:action] = 1 | |
681 endif | |
682 endfunction | |
683 | |
684 function! lawrencium#read_lawrencium_file(path) abort | |
685 call lawrencium#trace("Reading Lawrencium file: " . a:path) | |
686 let l:path_parts = lawrencium#parse_lawrencium_path(a:path) | |
687 if l:path_parts['root'] == '' | |
688 call lawrencium#throw("Can't get repository root from: " . a:path) | |
689 endif | |
690 if !has_key(s:lawrencium_file_readers, l:path_parts['action']) | |
691 call lawrencium#throw("No registered reader for action: " . l:path_parts['action']) | |
692 endif | |
693 | |
694 " Call the registered reader. | |
695 let l:repo = lawrencium#hg_repo(l:path_parts['root']) | |
696 let l:full_path = l:repo.root_dir . l:path_parts['path'] | |
697 let LawrenciumFileReader = s:lawrencium_file_readers[l:path_parts['action']] | |
698 call LawrenciumFileReader(l:repo, l:path_parts, l:full_path) | |
699 | |
700 " Setup the new buffer. | |
701 if !has_key(s:lawrencium_file_customoptions, l:path_parts['action']) | |
702 setlocal readonly | |
703 setlocal nomodified | |
704 setlocal bufhidden=delete | |
705 setlocal buftype=nofile | |
706 endif | |
707 goto | |
708 | |
709 " Remember the real Lawrencium path, because Vim can fuck up the slashes | |
710 " on Windows. | |
711 let b:lawrencium_path = a:path | |
712 | |
713 " Remember the repo it belongs to and make | |
714 " the Lawrencium commands available. | |
715 let b:mercurial_dir = l:repo.root_dir | |
716 call lawrencium#define_commands() | |
717 | |
718 return '' | |
719 endfunction | |
720 | |
721 function! lawrencium#write_lawrencium_file(path) abort | |
722 call lawrencium#trace("Writing Lawrencium file: " . a:path) | |
723 endfunction | |
724 | |
725 " }}} | |
726 | |
727 " Statusline {{{ | |
728 | |
729 " Prints a summary of the current repo (if any) that's appropriate for | |
730 " displaying on the status line. | |
731 function! lawrencium#statusline(...) | |
732 if !exists('b:mercurial_dir') | |
733 return '' | |
734 endif | |
735 let l:repo = lawrencium#hg_repo() | |
736 let l:prefix = (a:0 > 0 ? a:1 : '') | |
737 let l:suffix = (a:0 > 1 ? a:2 : '') | |
738 let l:branch = 'default' | |
739 let l:branch_file = l:repo.GetFullPath('.hg/branch') | |
740 if filereadable(l:branch_file) | |
741 let l:branch = readfile(l:branch_file)[0] | |
742 endif | |
743 let l:bookmarks = '' | |
744 let l:bookmarks_file = l:repo.GetFullPath('.hg/bookmarks.current') | |
745 if filereadable(l:bookmarks_file) | |
746 let l:bookmarks = join(readfile(l:bookmarks_file), ', ') | |
747 endif | |
748 let l:line = l:prefix . l:branch | |
749 if strlen(l:bookmarks) > 0 | |
750 let l:line = l:line . ' - ' . l:bookmarks | |
751 endif | |
752 let l:line = l:line . l:suffix | |
753 return l:line | |
754 endfunction | |
755 | |
756 " }}} | |
757 | |
758 " Miscellaneous User Functions {{{ | |
759 | |
760 " Rescans the current buffer for setting up Mercurial commands. | |
761 " Passing '1' as the parameter enables debug traces temporarily. | |
762 function! lawrencium#rescan(...) | |
763 if exists('b:mercurial_dir') | |
764 unlet b:mercurial_dir | |
765 endif | |
766 if a:0 && a:1 | |
767 let l:trace_backup = g:lawrencium_trace | |
768 let g:lawrencium_trace = 1 | |
769 endif | |
770 call lawrencium#setup_buffer_commands() | |
771 if a:0 && a:1 | |
772 let g:lawrencium_trace = l:trace_backup | |
773 endif | |
774 endfunction | |
775 | |
776 " Enables/disables the debug trace. | |
777 function! lawrencium#debugtrace(...) | |
778 let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1)) | |
779 echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.") | |
780 endfunction | |
781 | |
782 " }}} | |
783 | |
784 " Setup {{{ | |
785 | |
786 function! lawrencium#init() abort | |
787 let s:builtin_exts = [ | |
788 \'lawrencium#addremove', | |
789 \'lawrencium#annotate', | |
790 \'lawrencium#cat', | |
791 \'lawrencium#commit', | |
792 \'lawrencium#diff', | |
793 \'lawrencium#hg', | |
794 \'lawrencium#log', | |
795 \'lawrencium#mq', | |
796 \'lawrencium#record', | |
797 \'lawrencium#revert', | |
798 \'lawrencium#status', | |
799 \'lawrencium#vimutils' | |
800 \] | |
801 let s:user_exts = copy(g:lawrencium_extensions) | |
802 let s:exts = s:builtin_exts + s:user_exts | |
803 for ext in s:exts | |
804 call lawrencium#trace("Initializing Lawrencium extension " . ext) | |
805 execute ('call ' . ext . '#init()') | |
806 endfor | |
807 endfunction | |
808 | |
809 " }}} | |
810 |