Mercurial > vim-crosoft
annotate autoload/vimcrosoft.vim @ 12:1639be0967d6
Warn if multiple solutions have been found in the project.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 29 Aug 2023 12:50:59 -0700 |
parents | 096e80c13781 |
children |
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]) | |
12
1639be0967d6
Warn if multiple solutions have been found in the project.
Ludovic Chabant <ludovic@chabant.com>
parents:
11
diff
changeset
|
270 if len(l:slnfiles) > 1 && g:vimcrosoft_warn_multiple_slns |
1639be0967d6
Warn if multiple solutions have been found in the project.
Ludovic Chabant <ludovic@chabant.com>
parents:
11
diff
changeset
|
271 let l:warnmsg = "Found multiple solutions files: " |
1639be0967d6
Warn if multiple solutions have been found in the project.
Ludovic Chabant <ludovic@chabant.com>
parents:
11
diff
changeset
|
272 \.join(l:slnfiles, " ; ") |
1639be0967d6
Warn if multiple solutions have been found in the project.
Ludovic Chabant <ludovic@chabant.com>
parents:
11
diff
changeset
|
273 \." Check or change the auto-chosen first one." |
1639be0967d6
Warn if multiple solutions have been found in the project.
Ludovic Chabant <ludovic@chabant.com>
parents:
11
diff
changeset
|
274 call vimcrosoft#warning(l:warnmsg) |
1639be0967d6
Warn if multiple solutions have been found in the project.
Ludovic Chabant <ludovic@chabant.com>
parents:
11
diff
changeset
|
275 endif |
0 | 276 return l:slnfiles[0] |
277 endif | |
278 let l:prev = l:cur | |
279 let l:cur = fnamemodify(l:cur, ':h') | |
280 endwhile | |
281 call vimcrosoft#throw("No solution file found.") | |
282 endfunction | |
283 | |
284 function! vimcrosoft#set_active_project(projname, ...) abort | |
285 " Strip trailing spaces in the project name. | |
286 let l:projname = substitute(a:projname, '\v\s+$', '', 'g') | |
287 | |
288 let g:vimcrosoft_active_project = l:projname | |
289 call vimcrosoft#call_modules('on_active_project_changed', l:projname) | |
290 | |
291 let l:silent = a:0 && a:1 | |
292 if !l:silent | |
293 call vimcrosoft#save_config() | |
294 echom "Active project changed" | |
295 endif | |
296 endfunction | |
297 | |
298 function! vimcrosoft#build_sln(target) abort | |
6
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
299 if g:vimcrosoft_save_all_on_build |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
300 wall |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
301 endif |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
302 |
0 | 303 let l:args = [] |
304 if !empty(a:target) | |
305 call add(l:args, '/t:'.a:target) | |
306 endif | |
307 call vimcrosoft#run_make(l:args) | |
308 endfunction | |
309 | |
310 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
|
311 if g:vimcrosoft_save_all_on_build |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
312 wall |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
313 endif |
376f3371c311
Save all unsaved buffers before building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
314 |
0 | 315 let l:projname = !empty(a:projname) ? a:projname : g:vimcrosoft_active_project |
316 if empty(l:projname) | |
317 call vimcrosoft#error("No project name given, and no active project set.") | |
318 return | |
319 endif | |
320 | |
321 " Strip trailing spaces in the project name. | |
322 let l:projname = substitute(l:projname, '\v\s+$', '', 'g') | |
323 let l:target = '/t:'.tr(l:projname, '.', '_') | |
324 if !empty(a:target) | |
325 let l:target .= ':'.a:target | |
326 endif | |
327 | |
328 let l:args = [] | |
329 call add(l:args, l:target) | |
330 if a:only | |
331 call add(l:args, '/p:BuildProjectReferences=false') | |
332 endif | |
333 call vimcrosoft#run_make(l:args) | |
334 endfunction | |
335 | |
336 function! vimcrosoft#run_make(customargs) abort | |
337 if !vimcrosoft#ensure_msbuild_found() | |
338 return | |
339 endif | |
340 | |
341 " Add some common arguments for MSBuild. | |
342 let l:fullargs = copy(a:customargs) | |
343 call add(l:fullargs, '"/p:Configuration='.g:vimcrosoft_current_config.'"') | |
344 call add(l:fullargs, '"/p:Platform='.g:vimcrosoft_current_platform.'"') | |
345 " Add the solution file itself. | |
346 call add(l:fullargs, '"'.g:vimcrosoft_current_sln.'"') | |
347 | |
348 " Setup the backdoor args list for our compiler to pick-up, and run | |
349 " the make process. | |
350 let g:vimcrosoft_temp_compiler_args__ = l:fullargs | |
351 compiler vimcrosoftsln | |
352 if !empty(g:vimcrosoft_make_command) | |
353 execute g:vimcrosoft_make_command | |
354 elseif exists(":Make") " Support for vim-dispatch. | |
355 Make | |
356 else | |
357 make | |
358 endif | |
359 endfunction | |
360 | |
361 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
|
362 let l:bits = split(substitute(a:configplatform, '\\ ', ' ', 'g'), '|') |
0 | 363 if len(l:bits) != 2 |
364 call vimcrosoft#throw("Expected a value of the form: Config|Platform") | |
365 endif | |
366 | |
367 let g:vimcrosoft_current_config = l:bits[0] | |
368 let g:vimcrosoft_current_platform = l:bits[1] | |
369 call vimcrosoft#call_modules('on_config_platform_changed', | |
370 \g:vimcrosoft_current_config, g:vimcrosoft_current_platform) | |
371 | |
372 call vimcrosoft#save_config() | |
373 endfunction | |
374 | |
375 function! vimcrosoft#get_sln_project_names() abort | |
376 if empty(g:vimcrosoft_current_sln) | |
377 return [] | |
378 endif | |
379 let l:output = vimcrosoft#exec_script_now("list_sln_projects", | |
380 \g:vimcrosoft_current_sln, | |
381 \'-c', g:vimcrosoft_current_sln_cache, | |
382 \'--full-names') | |
383 return split(l:output, "\n") | |
384 endfunction | |
385 | |
386 function! vimcrosoft#get_sln_config_platforms() abort | |
387 if empty(g:vimcrosoft_current_sln) | |
388 return [] | |
389 endif | |
390 let l:output = vimcrosoft#exec_script_now("list_sln_configs", | |
391 \g:vimcrosoft_current_sln, | |
392 \'-c', g:vimcrosoft_current_sln_cache) | |
393 return split(l:output, "\n") | |
394 endfunction | |
395 | |
396 " }}} | |
397 | |
398 " {{{ Commands Auto-completion | |
399 | |
400 function! vimcrosoft#complete_current_sln_projects(ArgLead, CmdLine, CursorPos) | |
401 let l:proj_names = vimcrosoft#get_sln_project_names() | |
402 let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g') | |
403 let l:projnames = filter(l:proj_names, | |
404 \{idx, val -> val =~? l:argpat}) | |
405 return l:projnames | |
406 endfunction | |
407 | |
408 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
|
409 let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g') |
0 | 410 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
|
411 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
|
412 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
|
413 return l:cfgplats_filtered |
0 | 414 endfunction |
415 | |
416 " }}} | |
417 | |
418 " {{{ Statusline Functions | |
419 | |
420 function! vimcrosoft#statusline(...) | |
421 if empty(g:vimcrosoft_current_sln) | |
422 return '' | |
423 endif | |
424 | |
425 let l:line = fnamemodify(g:vimcrosoft_current_sln, ':t') | |
426 if !empty(g:vimcrosoft_active_project) | |
427 let l:line .= '('.g:vimcrosoft_active_project.')' | |
428 endif | |
429 let l:line .= ' ['. | |
430 \g:vimcrosoft_current_config.'|'. | |
431 \g:vimcrosoft_current_platform.']' | |
432 return l:line | |
433 endfunction | |
434 | |
435 " }}} | |
436 | |
437 " {{{ Initialization | |
438 | |
439 function! vimcrosoft#init() abort | |
440 call vimcrosoft#trace("Loading modules...") | |
441 for modpath in s:modules | |
442 execute 'source '.fnameescape(modpath) | |
443 endfor | |
444 | |
445 call vimcrosoft#call_modules('init') | |
446 | |
447 if g:vimcrosoft_auto_find_sln | |
448 call vimcrosoft#auto_find_sln(1) | |
449 call vimcrosoft#load_config() | |
450 endif | |
451 endfunction | |
452 | |
453 " }}} |