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