0
|
1 " lawrencium.vim - A Mercurial wrapper
|
|
2 " Maintainer: Ludovic Chabant <http://ludovic.chabant.com>
|
|
3 " Version: 0.1
|
|
4
|
|
5 " Globals {{{
|
|
6
|
|
7 if !exists('g:lawrencium_debug')
|
|
8 let g:lawrencium_debug = 0
|
|
9 endif
|
|
10
|
|
11 if (exists('g:loaded_lawrencium') || &cp) && !g:lawrencium_debug
|
|
12 finish
|
|
13 endif
|
|
14 let g:loaded_lawrencium = 1
|
|
15
|
|
16 if !exists('g:lawrencium_hg_executable')
|
|
17 let g:lawrencium_hg_executable = 'hg'
|
|
18 endif
|
|
19
|
|
20 if !exists('g:lawrencium_trace')
|
|
21 let g:lawrencium_trace = 0
|
|
22 endif
|
|
23
|
|
24 if g:lawrencium_debug
|
|
25 echom "Loaded Lawrencium."
|
|
26 endif
|
|
27
|
|
28 " }}}
|
|
29
|
|
30 " Utility {{{
|
|
31
|
|
32 " Strips the ending slash in a path.
|
|
33 function! s:stripslash(path)
|
|
34 return fnamemodify(a:path, ':s?[/\\]$??')
|
|
35 endfunction
|
|
36
|
|
37 " Normalizes the slashes in a path.
|
|
38 function! s:normalizepath(path)
|
|
39 if exists('+shellslash') && &shellslash
|
|
40 return substitute(a:path, '\\', '/', '')
|
|
41 elseif has('win32')
|
|
42 return substitute(a:path, '/', '\\', '')
|
|
43 else
|
|
44 return a:path
|
|
45 endif
|
|
46 endfunction
|
|
47
|
|
48 " Prints a message if debug tracing is enabled.
|
|
49 function! s:trace(message)
|
|
50 if g:lawrencium_trace
|
|
51 let l:message = "lawrencium: " . a:message
|
|
52 echom l:message
|
|
53 endif
|
|
54 endfunction
|
|
55
|
|
56 " Throw a Lawrencium exception message.
|
|
57 function! s:throw(message)
|
|
58 let v:errmsg = "lawrencium: " . a:message
|
|
59 throw v:errmsg
|
|
60 endfunction
|
|
61
|
|
62 " Finds the repository root given a path inside that repository.
|
|
63 " Throw an error if not repository is found.
|
|
64 function! s:find_repo_root(path)
|
|
65 let l:path = s:stripslash(a:path)
|
|
66 let l:previous_path = ""
|
|
67 while l:path != l:previous_path
|
|
68 if isdirectory(l:path . '/.hg/store')
|
|
69 return simplify(fnamemodify(l:path, ':p'))
|
|
70 endif
|
|
71 let l:previous_path = l:path
|
|
72 let l:path = fnamemodify(l:path, ':h')
|
|
73 endwhile
|
|
74 call s:throw("No Mercurial repository found above: " . a:path)
|
|
75 endfunction
|
|
76
|
|
77 " }}}
|
|
78
|
|
79 " Mercurial Repository {{{
|
|
80
|
|
81 " Let's define a Mercurial repo 'class' using prototype-based object-oriented
|
|
82 " programming.
|
|
83 "
|
|
84 " The prototype dictionary.
|
|
85 let s:HgRepo = {}
|
|
86
|
|
87 " Constructor
|
|
88 function! s:HgRepo.New(path) abort
|
|
89 let l:newRepo = copy(self)
|
|
90 let l:newRepo.root_dir = s:find_repo_root(a:path)
|
|
91 call s:trace("Built new Mercurial repository object at : " . l:newRepo.root_dir)
|
|
92 return l:newRepo
|
|
93 endfunction
|
|
94
|
|
95 " Gets a full path given a repo-relative path
|
|
96 function! s:HgRepo.GetFullPath(path) abort
|
|
97 let l:root_dir = self.root_dir
|
|
98 if a:path =~# '^[/\\]'
|
|
99 let l:root_dir = s:stripslash(l:root_dir)
|
|
100 endif
|
|
101 return l:root_dir . a:path
|
|
102 endfunction
|
|
103
|
|
104 " Gets a list of files matching a root-relative pattern.
|
|
105 " If a flag is passed and is TRUE, a slash will be appended to all
|
|
106 " directories.
|
|
107 function! s:HgRepo.Glob(pattern, ...) abort
|
|
108 let l:root_dir = self.root_dir
|
|
109 if (a:pattern =~# '^[/\\]')
|
|
110 let l:root_dir = s:stripslash(l:root_dir)
|
|
111 endif
|
|
112 let l:matches = split(glob(l:root_dir . a:pattern), '\n')
|
|
113 if a:0 && a:1
|
|
114 for l:idx in range(len(l:matches))
|
|
115 if !filereadable(l:matches[l:idx])
|
|
116 let l:matches[l:idx] = l:matches[l:idx] . '/'
|
|
117 endif
|
|
118 endfor
|
|
119 endif
|
|
120 let l:strip_len = len(l:root_dir)
|
|
121 call map(l:matches, 'v:val[l:strip_len : -1]')
|
|
122 return l:matches
|
|
123 endfunction
|
|
124
|
|
125 " Runs a Mercurial command in the repo
|
|
126 function! s:HgRepo.RunCommand(command, ...) abort
|
|
127 let l:hg_command = g:lawrencium_hg_executable . ' --repository ' . shellescape(s:stripslash(self.root_dir))
|
|
128 let l:hg_command = l:hg_command . ' ' . a:command . ' ' . join(a:000, ' ')
|
|
129 call s:trace("Running Mercurial command: " . l:hg_command)
|
|
130 return system(l:hg_command)
|
|
131 endfunction
|
|
132
|
|
133 " Repo cache map
|
|
134 let s:buffer_repos = {}
|
|
135
|
|
136 " Get a cached repo
|
|
137 function! s:hg_repo(...) abort
|
|
138 " Use the given path, or the mercurial directory of the current buffer.
|
|
139 if a:0 == 0
|
|
140 if exists('b:mercurial_dir')
|
|
141 let l:path = b:mercurial_dir
|
|
142 else
|
|
143 let l:path = s:find_repo_root(expand('%:p'))
|
|
144 endif
|
|
145 else
|
|
146 let l:path = a:1
|
|
147 endif
|
|
148 " Find a cache repo instance, or make a new one.
|
|
149 if has_key(s:buffer_repos, l:path)
|
|
150 return get(s:buffer_repos, l:path)
|
|
151 else
|
|
152 let l:repo = s:HgRepo.New(l:path)
|
|
153 let s:buffer_repos[l:path] = l:repo
|
|
154 return l:repo
|
|
155 endif
|
|
156 endfunction
|
|
157
|
|
158 " Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo.
|
|
159 " If the file is not in a Mercurial repo, just exit silently.
|
|
160 function! s:setup_buffer_commands() abort
|
|
161 call s:trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...")
|
|
162 let l:do_setup = 1
|
|
163 if exists('b:mercurial_dir')
|
|
164 if b:mercurial_dir =~# '/^\s*$/'
|
|
165 unlet b:mercurial_dir
|
|
166 else
|
|
167 let l:do_setup = 0
|
|
168 endif
|
|
169 endif
|
|
170 try
|
|
171 let l:repo = s:hg_repo()
|
|
172 catch /^lawrencium\:/
|
|
173 return
|
|
174 endtry
|
|
175 let b:mercurial_dir = l:repo.root_dir
|
|
176 if exists('b:mercurial_dir') && l:do_setup
|
|
177 call s:trace("Setting Mercurial commands for buffer '" . bufname('%'))
|
|
178 call s:trace(" with repo : " . expand(b:mercurial_dir))
|
|
179 silent doautocmd User Lawrencium
|
|
180 endif
|
|
181 endfunction
|
|
182
|
|
183 augroup lawrencium_detect
|
|
184 autocmd!
|
|
185 autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands()
|
|
186 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif
|
|
187 augroup end
|
|
188
|
|
189 " }}}
|
|
190
|
|
191 " Commands {{{
|
|
192
|
|
193 let s:main_commands = []
|
|
194
|
|
195 function! s:AddMainCommand(command) abort
|
|
196 let s:main_commands += [a:command]
|
|
197 endfunction
|
|
198
|
|
199 function! s:DefineMainCommands()
|
|
200 for l:command in s:main_commands
|
|
201 execute 'command! -buffer ' . l:command
|
|
202 endfor
|
|
203 endfunction
|
|
204
|
|
205 augroup lawrencium_main
|
|
206 autocmd!
|
|
207 autocmd User Lawrencium call s:DefineMainCommands()
|
|
208 augroup end
|
|
209
|
|
210 " }}}
|
|
211
|
|
212 " HgExecute {{{
|
|
213
|
|
214 function! s:HgExecute(...) abort
|
|
215 let l:repo = s:hg_repo()
|
|
216 echo call(l:repo.RunCommand, a:000, l:repo)
|
|
217 endfunction
|
|
218
|
|
219 call s:AddMainCommand("-nargs=* Hg :execute s:HgExecute(<f-args>)")
|
|
220
|
|
221 " }}}
|
|
222
|
|
223 " HgStatus {{{
|
|
224
|
|
225 function! s:HgStatus() abort
|
|
226 echo s:hg_repo().RunCommand('status')
|
|
227 endfunction
|
|
228
|
|
229 call s:AddMainCommand("HgStatus :execute s:HgStatus()")
|
|
230
|
|
231 " }}}
|
|
232
|
|
233 " Hgcd, Hglcd {{{
|
|
234
|
|
235 function! s:ListRepoDirs(ArgLead, CmdLine, CursorPos) abort
|
|
236 let l:matches = s:hg_repo().Glob(a:ArgLead . '*/')
|
|
237 call map(l:matches, 's:normalizepath(v:val)')
|
|
238 return l:matches
|
|
239 endfunction
|
|
240
|
|
241 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hgcd :cd<bang> `=s:hg_repo().GetFullPath(<q-args>)`")
|
|
242 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hglcd :lcd<bang> `=s:hg_repo().GetFullPath(<q-args>)`")
|
|
243
|
|
244 " }}}
|
|
245
|
|
246 " Hgedit {{{
|
|
247
|
|
248 function! s:ListRepoFiles(ArgLead, CmdLine, CursorPos) abort
|
|
249 let l:matches = s:hg_repo().Glob(a:ArgLead . '*', 1)
|
|
250 call map(l:matches, 's:normalizepath(v:val)')
|
|
251 return l:matches
|
|
252 endfunction
|
|
253
|
|
254 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgedit :edit<bang> `=s:hg_repo().GetFullPath(<q-args>)`")
|
|
255
|
|
256 " }}}
|
|
257
|
|
258 " Autoload Functions {{{
|
|
259
|
|
260 " Prints a summary of the current repo (if any) that's appropriate for
|
|
261 " displaying on the status line.
|
|
262 function! lawrencium#statusline(...)
|
|
263 if !exists('b:mercurial_dir')
|
|
264 return ''
|
|
265 endif
|
|
266 let l:summary = s:hg_repo().RunCommand('summary')
|
|
267 let l:parent_rev = matchstr(l:summary, 'parent\: \d+\:[0-9a-f]+')
|
|
268 let l:branch = matchstr(l:summary, 'branch\: [\d\w\-_\.]+')
|
|
269 return l:branch . ', ' . l:parent_rev
|
|
270 endfunction
|
|
271
|
|
272 " Rescans the current buffer for setting up Mercurial commands.
|
|
273 " Passing '1' as the parameter enables debug traces temporarily.
|
|
274 function! lawrencium#rescan(...)
|
|
275 if exists('b:mercurial_dir')
|
|
276 unlet b:mercurial_dir
|
|
277 endif
|
|
278 if a:0 && a:1
|
|
279 let l:trace_backup = g:lawrencium_trace
|
|
280 let g:lawrencium_trace = 1
|
|
281 endif
|
|
282 call s:setup_buffer_commands()
|
|
283 if a:0 && a:1
|
|
284 let g:lawrencium_trace = l:trace_backup
|
|
285 endif
|
|
286 endfunction
|
|
287
|
|
288 " Enables/disables the debug trace.
|
|
289 function! lawrencium#debugtrace(...)
|
|
290 let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1))
|
|
291 echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.")
|
|
292 endfunction
|
|
293
|
|
294 " }}}
|
|
295
|