Mercurial > vim-crosoft
annotate autoload/vimcrosoft.vim @ 11:096e80c13781
Use the correct documentation extension.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 22 Jan 2021 16:42:25 -0800 |
parents | ff4590b2503a |
children | 1639be0967d6 |
rev | line source |
---|---|
0 | 1 " vimcrosoft.vim - A wrapper for Visual Studio solutions |
2 | |
3 " Utilities {{{ | |
4 | |
5 let s:basedir = expand('<sfile>:p:h:h') | |
6 | |
7 function! vimcrosoft#throw(message) | |
8 throw "vimcrosoft: ".a:message | |
9 endfunction | |
10 | |
11 function! vimcrosoft#error(message) | |
12 let v:errmsg = "vimcrosoft: ".a:message | |
13 echoerr v:errmsg | |
14 endfunction | |
15 | |
16 function! vimcrosoft#warning(message) | |
17 echohl WarningMsg | |
18 echom "vimcrosoft: ".a:message | |
19 echohl None | |
20 endfunction | |
21 | |
22 function! vimcrosoft#trace(message) | |
23 if g:vimcrosoft_trace | |
24 echom "vimcrosoft: ".a:message | |
25 endif | |
26 endfunction | |
27 | |
28 function! vimcrosoft#ensure_msbuild_found() abort | |
29 if !empty(g:vimcrosoft_msbuild_path) | |
30 return 1 | |
31 endif | |
32 | |
33 let l:programfilesdir = get(environ(), 'ProgramFiles(x86)') | |
34 let l:vswhere = l:programfilesdir.'\Microsoft Visual Studio\Installer\vswhere.exe' | |
35 if !executable(l:vswhere) | |
36 call vimcrosoft#error("Can't find `vswhere` -- you must set `g:vimcrosoft_msbuild_path` yourself.") | |
37 return 0 | |
38 endif | |
39 | |
40 let l:vswhere_cmdline = '"'.l:vswhere.'" '. | |
41 \'-prerelease -latest -products * '. | |
42 \'-requires Microsoft.Component.MSBuild '. | |
43 \'-property installationPath' | |
44 call vimcrosoft#trace("Trying to find MSBuild, running: ".l:vswhere_cmdline) | |
45 let l:installdirs = split(system(l:vswhere_cmdline), '\n') | |
46 call vimcrosoft#trace("Got: ".string(l:installdirs)) | |
47 for installdir in l:installdirs | |
48 let l:msbuild = installdir.'\MSBuild\Current\Bin\MSBuild.exe' | |
49 if executable(l:msbuild) | |
50 let g:vimcrosoft_msbuild_path = l:msbuild | |
51 return 1 | |
52 endif | |
53 let l:msbuild = installdir.'\MSBuild\15.0\Bin\MSBuild.exe' | |
54 if executable(l:msbuild) | |
55 let g:vimcrosoft_msbuild_path = l:msbuild | |
56 return 1 | |
57 endif | |
58 endfor | |
59 | |
60 call vimcrosoft#error("Couldn't find MSBuild anywhere in:\n". | |
61 \string(l:installdirs)) | |
62 return 0 | |
63 endfunction | |
64 | |
11
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
65 function! vimcrosoft#get_msbuild_errorformat() abort |
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
66 " MSBuild error formats look like this: |
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
67 " Path\To\Source\Filename.cpp|53| error C2065: 'MyClass': undeclared identifier [Path\To\MyProject.vcxproj] |
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
68 return '%*[^\ ]\ %f\|%l\|\ %trror\ \D%n:\ %m\ [%o]' |
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
69 " \'\|\|\ %f\|%l\|\ %tarning\ \D%n:\ %m\ [%o]' |
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
70 endfunction |
096e80c13781
Use the correct documentation extension.
Ludovic Chabant <ludovic@chabant.com>
parents:
7
diff
changeset
|
71 |
0 | 72 " }}} |
73 | |
74 " Cache Files {{{ | |
75 | |
76 function! vimcrosoft#get_sln_cache_dir(...) abort | |
77 if empty(g:vimcrosoft_current_sln) | |
78 if a:0 && a:1 | |
79 call vimcrosoft#throw("No solution is currently set.") | |
80 endif | |
81 return '' | |
82 endif | |
83 let l:cache_dir = fnamemodify(g:vimcrosoft_current_sln, ':h') | |
84 let l:cache_dir .= '\.vimcrosoft' | |
85 return l:cache_dir | |
86 endfunction | |
87 | |
88 function! vimcrosoft#get_sln_cache_file(filename) abort | |
89 let l:cache_dir = vimcrosoft#get_sln_cache_dir() | |
90 if empty(l:cache_dir) | |
91 return '' | |
92 else | |
93 return l:cache_dir.'\'.a:filename | |
94 endif | |
95 endfunction | |
96 | |
97 " }}} | |
98 | |
99 " Configuration Files {{{ | |
100 | |
101 let s:config_format = 2 | |
102 | |
103 function! vimcrosoft#save_config() abort | |
104 if empty(g:vimcrosoft_current_sln) | |
105 return | |
106 endif | |
107 | |
108 call vimcrosoft#trace("Saving config for: ".g:vimcrosoft_current_sln) | |
109 let l:lines = [ | |
110 \'format='.string(s:config_format), | |
111 \g:vimcrosoft_current_sln, | |
112 \g:vimcrosoft_current_config, | |
113 \g:vimcrosoft_current_platform, | |
114 \g:vimcrosoft_active_project] | |
115 let l:configfile = vimcrosoft#get_sln_cache_file('config.txt') | |
116 call writefile(l:lines, l:configfile) | |
117 endfunction | |
118 | |
119 function! vimcrosoft#load_config() abort | |
120 if empty(g:vimcrosoft_current_sln) | |
121 return | |
122 endif | |
123 | |
124 let l:configfile = vimcrosoft#get_sln_cache_file('config.txt') | |
125 if !filereadable(l:configfile) | |
126 return | |
127 endif | |
128 | |
129 let l:lines = readfile(l:configfile) | |
130 let l:format_line = l:lines[0] | |
131 if l:format_line == 'format='.string(s:config_format) | |
132 let g:vimcrosoft_current_sln = l:lines[1] | |
133 let g:vimcrosoft_current_config = l:lines[2] | |
134 let g:vimcrosoft_current_platform = l:lines[3] | |
135 let g:vimcrosoft_active_project = l:lines[4] | |
136 else | |
137 call vimcrosoft#warning("Solution configuration format has changed ". | |
138 \"since you last opened this solution in Vim. ". | |
139 \"You previous configuration/platform has NOT been ". | |
140 \"restored.") | |
141 endif | |
142 endfunction | |
143 | |
144 " }}} | |
145 | |
146 " {{{ Scripts | |
147 | |
148 let s:scriptsdir = s:basedir.'\scripts' | |
149 | |
150 function! vimcrosoft#get_script_path(scriptname) abort | |
151 return s:scriptsdir.'\'.a:scriptname | |
152 endfunction | |
153 | |
154 function! vimcrosoft#exec_script_job(scriptname, ...) abort | |
155 let l:scriptpath = vimcrosoft#get_script_path(a:scriptname) | |
156 let l:cmd = ['python', l:scriptpath] + a:000 | |
157 return job_start(l:cmd) | |
158 endfunction | |
159 | |
160 let s:scriptsdir_added_to_sys = 0 | |
161 let s:scripts_imported = [] | |
162 | |
163 function! s:install_scriptsdir() abort | |
164 if !s:scriptsdir_added_to_sys | |
165 execute 'python3 import sys' | |
166 execute 'python3 sys.path.append("'.escape(s:scriptsdir, "\\").'")' | |
167 execute 'python3 import vimutil' | |
168 let s:scriptsdir_added_to_sys = 1 | |
169 endif | |
170 endfunction | |
171 | |
172 function! vimcrosoft#exec_script_now(scriptname, ...) abort | |
173 if g:vimcrosoft_use_external_python | |
174 let l:cmd = 'python '.shellescape(vimcrosoft#get_script_path(a:scriptname.'.py')) | |
175 " TODO: shellescape arguments? | |
176 let l:cmd .= ' '.join(a:000, " ") | |
177 let l:output = system(l:cmd) | |
178 else | |
179 call s:install_scriptsdir() | |
180 if index(s:scripts_imported, a:scriptname) < 0 | |
181 execute 'python3 import '.a:scriptname | |
182 call add(s:scripts_imported, a:scriptname) | |
183 endif | |
184 let l:line = 'vimutil.runscript('.a:scriptname.'.main' | |
185 if a:0 > 0 | |
186 let l:args = copy(a:000) | |
187 call map(l:args, {idx, val -> escape(val, '\"')}) | |
188 let l:line .= ', "'.join(l:args, '", "').'"' | |
189 endif | |
190 let l:line .= ')' | |
191 call vimcrosoft#trace("Executing: ".l:line) | |
192 let l:output = py3eval(l:line) | |
193 endif | |
194 return l:output | |
195 endfunction | |
196 | |
197 " }}} | |
198 | |
199 " Module Management {{{ | |
200 | |
201 let s:modulesdir = s:basedir.'\autoload\vimcrosoft' | |
202 let s:modules = glob(s:modulesdir.'\*.vim', 0, 1) | |
203 | |
204 function! vimcrosoft#call_modules(funcname, ...) abort | |
205 for modpath in s:modules | |
206 let l:modname = fnamemodify(modpath, ':t:r') | |
207 let l:fullfuncname = 'vimcrosoft#'.l:modname.'#'.a:funcname | |
208 if exists("*".l:fullfuncname) | |
209 call vimcrosoft#trace("Module ".l:modname.": calling ".a:funcname) | |
210 call call(l:fullfuncname, a:000) | |
211 else | |
212 call vimcrosoft#trace("Skipping ".l:fullfuncname.": doesn't exist.") | |
213 endif | |
214 endfor | |
215 endfunction | |
216 | |
217 " }}} | |
218 | |
219 " Solution Management {{{ | |
220 | |
221 function! vimcrosoft#set_sln(slnpath, ...) abort | |
222 let g:vimcrosoft_current_sln = a:slnpath | |
223 | |
224 let l:sln_was_set = !empty(a:slnpath) | |
225 if l:sln_was_set | |
226 let g:vimcrosoft_current_sln_cache = vimcrosoft#get_sln_cache_file("slncache.bin") | |
227 call vimcrosoft#call_modules('on_sln_changed', a:slnpath) | |
228 else | |
229 let g:vimcrosoft_current_sln_cache = '' | |
230 call vimcrosoft#call_modules('on_sln_cleared') | |
231 endif | |
232 | |
233 let l:silent = a:0 && a:1 | |
234 if !l:silent | |
235 call vimcrosoft#save_config() | |
236 | |
237 if l:sln_was_set | |
238 echom "Current solution: ".a:slnpath | |
239 else | |
240 echom "No current solution anymore" | |
241 endif | |
242 endif | |
243 endfunction | |
244 | |
245 function! vimcrosoft#auto_find_sln(...) abort | |
246 let l:path = getcwd() | |
247 try | |
248 let l:slnpath = vimcrosoft#find_sln(l:path) | |
249 catch /^vimcrosoft:/ | |
250 let l:slnpath = '' | |
251 endtry | |
252 let l:silent = a:0 && a:1 | |
253 call vimcrosoft#set_sln(l:slnpath, l:silent) | |
254 endfunction | |
255 | |
256 function! vimcrosoft#find_sln(curpath) abort | |
257 if g:vimcrosoft_sln_finder != '' | |
258 return call(g:vimcrosoft_sln_finder, [a:curpath]) | |
259 endif | |
260 return vimcrosoft#default_sln_finder(a:curpath) | |
261 endfunction | |
262 | |
263 function! vimcrosoft#default_sln_finder(path) abort | |
264 let l:cur = a:path | |
265 let l:prev = "" | |
266 while l:cur != l:prev | |
267 let l:slnfiles = globpath(l:cur, '*.sln', 0, 1) | |
268 if !empty(l:slnfiles) | |
269 call vimcrosoft#trace("Found solution file: ".l:slnfiles[0]) | |
270 return l:slnfiles[0] | |
271 endif | |
272 let l:prev = l:cur | |
273 let l:cur = fnamemodify(l:cur, ':h') | |
274 endwhile | |
275 call vimcrosoft#throw("No solution file found.") | |
276 endfunction | |
277 | |
278 function! vimcrosoft#set_active_project(projname, ...) abort | |
279 " Strip trailing spaces in the project name. | |
280 let l:projname = substitute(a:projname, '\v\s+$', '', 'g') | |
281 | |
282 let g:vimcrosoft_active_project = l:projname | |
283 call vimcrosoft#call_modules('on_active_project_changed', l:projname) | |
284 | |
285 let l:silent = a:0 && a:1 | |
286 if !l:silent | |
287 call vimcrosoft#save_config() | |
288 echom "Active project changed" | |
289 endif | |
290 endfunction | |
291 | |
292 function! vimcrosoft#build_sln(target) abort | |
6
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
293 if g:vimcrosoft_save_all_on_build |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
294 wall |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
295 endif |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
296 |
0 | 297 let l:args = [] |
298 if !empty(a:target) | |
299 call add(l:args, '/t:'.a:target) | |
300 endif | |
301 call vimcrosoft#run_make(l:args) | |
302 endfunction | |
303 | |
304 function! vimcrosoft#build_project(projname, target, only) abort | |
6
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
305 if g:vimcrosoft_save_all_on_build |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
306 wall |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
307 endif |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
308 |
0 | 309 let l:projname = !empty(a:projname) ? a:projname : g:vimcrosoft_active_project |
310 if empty(l:projname) | |
311 call vimcrosoft#error("No project name given, and no active project set.") | |
312 return | |
313 endif | |
314 | |
315 " Strip trailing spaces in the project name. | |
316 let l:projname = substitute(l:projname, '\v\s+$', '', 'g') | |
317 let l:target = '/t:'.tr(l:projname, '.', '_') | |
318 if !empty(a:target) | |
319 let l:target .= ':'.a:target | |
320 endif | |
321 | |
322 let l:args = [] | |
323 call add(l:args, l:target) | |
324 if a:only | |
325 call add(l:args, '/p:BuildProjectReferences=false') | |
326 endif | |
327 call vimcrosoft#run_make(l:args) | |
328 endfunction | |
329 | |
330 function! vimcrosoft#run_make(customargs) abort | |
331 if !vimcrosoft#ensure_msbuild_found() | |
332 return | |
333 endif | |
334 | |
335 " Add some common arguments for MSBuild. | |
336 let l:fullargs = copy(a:customargs) | |
337 call add(l:fullargs, '"/p:Configuration='.g:vimcrosoft_current_config.'"') | |
338 call add(l:fullargs, '"/p:Platform='.g:vimcrosoft_current_platform.'"') | |
339 " Add the solution file itself. | |
340 call add(l:fullargs, '"'.g:vimcrosoft_current_sln.'"') | |
341 | |
342 " Setup the backdoor args list for our compiler to pick-up, and run | |
343 " the make process. | |
344 let g:vimcrosoft_temp_compiler_args__ = l:fullargs | |
345 compiler vimcrosoftsln | |
346 if !empty(g:vimcrosoft_make_command) | |
347 execute g:vimcrosoft_make_command | |
348 elseif exists(":Make") " Support for vim-dispatch. | |
349 Make | |
350 else | |
351 make | |
352 endif | |
353 endfunction | |
354 | |
355 function! vimcrosoft#set_config_platform(configplatform) | |
7
ff4590b2503a
Fix handling of spaces in configs/platforms for autocomplete.
Ludovic Chabant <ludovic@chabant.com>
parents:
6
diff
changeset
|
356 let l:bits = split(substitute(a:configplatform, '\\ ', ' ', 'g'), '|') |
0 | 357 if len(l:bits) != 2 |
358 call vimcrosoft#throw("Expected a value of the form: Config|Platform") | |
359 endif | |
360 | |
361 let g:vimcrosoft_current_config = l:bits[0] | |
362 let g:vimcrosoft_current_platform = l:bits[1] | |
363 call vimcrosoft#call_modules('on_config_platform_changed', | |
364 \g:vimcrosoft_current_config, g:vimcrosoft_current_platform) | |
365 | |
366 call vimcrosoft#save_config() | |
367 endfunction | |
368 | |
369 function! vimcrosoft#get_sln_project_names() abort | |
370 if empty(g:vimcrosoft_current_sln) | |
371 return [] | |
372 endif | |
373 let l:output = vimcrosoft#exec_script_now("list_sln_projects", | |
374 \g:vimcrosoft_current_sln, | |
375 \'-c', g:vimcrosoft_current_sln_cache, | |
376 \'--full-names') | |
377 return split(l:output, "\n") | |
378 endfunction | |
379 | |
380 function! vimcrosoft#get_sln_config_platforms() abort | |
381 if empty(g:vimcrosoft_current_sln) | |
382 return [] | |
383 endif | |
384 let l:output = vimcrosoft#exec_script_now("list_sln_configs", | |
385 \g:vimcrosoft_current_sln, | |
386 \'-c', g:vimcrosoft_current_sln_cache) | |
387 return split(l:output, "\n") | |
388 endfunction | |
389 | |
390 " }}} | |
391 | |
392 " {{{ Commands Auto-completion | |
393 | |
394 function! vimcrosoft#complete_current_sln_projects(ArgLead, CmdLine, CursorPos) | |
395 let l:proj_names = vimcrosoft#get_sln_project_names() | |
396 let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g') | |
397 let l:projnames = filter(l:proj_names, | |
398 \{idx, val -> val =~? l:argpat}) | |
399 return l:projnames | |
400 endfunction | |
401 | |
402 function! vimcrosoft#complete_current_sln_config_platforms(ArgLead, CmdLine, CursorPos) | |
7
ff4590b2503a
Fix handling of spaces in configs/platforms for autocomplete.
Ludovic Chabant <ludovic@chabant.com>
parents:
6
diff
changeset
|
403 let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g') |
0 | 404 let l:cfgplats = vimcrosoft#get_sln_config_platforms() |
7
ff4590b2503a
Fix handling of spaces in configs/platforms for autocomplete.
Ludovic Chabant <ludovic@chabant.com>
parents:
6
diff
changeset
|
405 let l:cfgplats_filtered = filter(l:cfgplats, {idx, val -> val =~? l:argpat}) |
ff4590b2503a
Fix handling of spaces in configs/platforms for autocomplete.
Ludovic Chabant <ludovic@chabant.com>
parents:
6
diff
changeset
|
406 call map(l:cfgplats_filtered, {idx, val -> escape(val, ' ')}) |
ff4590b2503a
Fix handling of spaces in configs/platforms for autocomplete.
Ludovic Chabant <ludovic@chabant.com>
parents:
6
diff
changeset
|
407 return l:cfgplats_filtered |
0 | 408 endfunction |
409 | |
410 " }}} | |
411 | |
412 " {{{ Statusline Functions | |
413 | |
414 function! vimcrosoft#statusline(...) | |
415 if empty(g:vimcrosoft_current_sln) | |
416 return '' | |
417 endif | |
418 | |
419 let l:line = fnamemodify(g:vimcrosoft_current_sln, ':t') | |
420 if !empty(g:vimcrosoft_active_project) | |
421 let l:line .= '('.g:vimcrosoft_active_project.')' | |
422 endif | |
423 let l:line .= ' ['. | |
424 \g:vimcrosoft_current_config.'|'. | |
425 \g:vimcrosoft_current_platform.']' | |
426 return l:line | |
427 endfunction | |
428 | |
429 " }}} | |
430 | |
431 " {{{ Initialization | |
432 | |
433 function! vimcrosoft#init() abort | |
434 call vimcrosoft#trace("Loading modules...") | |
435 for modpath in s:modules | |
436 execute 'source '.fnameescape(modpath) | |
437 endfor | |
438 | |
439 call vimcrosoft#call_modules('init') | |
440 | |
441 if g:vimcrosoft_auto_find_sln | |
442 call vimcrosoft#auto_find_sln(1) | |
443 call vimcrosoft#load_config() | |
444 endif | |
445 endfunction | |
446 | |
447 " }}} |