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
|
|
286 let l:args = []
|
|
287 if !empty(a:target)
|
|
288 call add(l:args, '/t:'.a:target)
|
|
289 endif
|
|
290 call vimcrosoft#run_make(l:args)
|
|
291 endfunction
|
|
292
|
|
293 function! vimcrosoft#build_project(projname, target, only) abort
|
|
294 let l:projname = !empty(a:projname) ? a:projname : g:vimcrosoft_active_project
|
|
295 if empty(l:projname)
|
|
296 call vimcrosoft#error("No project name given, and no active project set.")
|
|
297 return
|
|
298 endif
|
|
299
|
|
300 " Strip trailing spaces in the project name.
|
|
301 let l:projname = substitute(l:projname, '\v\s+$', '', 'g')
|
|
302 let l:target = '/t:'.tr(l:projname, '.', '_')
|
|
303 if !empty(a:target)
|
|
304 let l:target .= ':'.a:target
|
|
305 endif
|
|
306
|
|
307 let l:args = []
|
|
308 call add(l:args, l:target)
|
|
309 if a:only
|
|
310 call add(l:args, '/p:BuildProjectReferences=false')
|
|
311 endif
|
|
312 call vimcrosoft#run_make(l:args)
|
|
313 endfunction
|
|
314
|
|
315 function! vimcrosoft#run_make(customargs) abort
|
|
316 if !vimcrosoft#ensure_msbuild_found()
|
|
317 return
|
|
318 endif
|
|
319
|
|
320 " Add some common arguments for MSBuild.
|
|
321 let l:fullargs = copy(a:customargs)
|
|
322 call add(l:fullargs, '"/p:Configuration='.g:vimcrosoft_current_config.'"')
|
|
323 call add(l:fullargs, '"/p:Platform='.g:vimcrosoft_current_platform.'"')
|
|
324 " Add the solution file itself.
|
|
325 call add(l:fullargs, '"'.g:vimcrosoft_current_sln.'"')
|
|
326
|
|
327 " Setup the backdoor args list for our compiler to pick-up, and run
|
|
328 " the make process.
|
|
329 let g:vimcrosoft_temp_compiler_args__ = l:fullargs
|
|
330 compiler vimcrosoftsln
|
|
331 if !empty(g:vimcrosoft_make_command)
|
|
332 execute g:vimcrosoft_make_command
|
|
333 elseif exists(":Make") " Support for vim-dispatch.
|
|
334 Make
|
|
335 else
|
|
336 make
|
|
337 endif
|
|
338 endfunction
|
|
339
|
|
340 function! vimcrosoft#set_config_platform(configplatform)
|
|
341 let l:bits = split(a:configplatform, '|')
|
|
342 if len(l:bits) != 2
|
|
343 call vimcrosoft#throw("Expected a value of the form: Config|Platform")
|
|
344 endif
|
|
345
|
|
346 let g:vimcrosoft_current_config = l:bits[0]
|
|
347 let g:vimcrosoft_current_platform = l:bits[1]
|
|
348 call vimcrosoft#call_modules('on_config_platform_changed',
|
|
349 \g:vimcrosoft_current_config, g:vimcrosoft_current_platform)
|
|
350
|
|
351 call vimcrosoft#save_config()
|
|
352 endfunction
|
|
353
|
|
354 function! vimcrosoft#get_sln_project_names() abort
|
|
355 if empty(g:vimcrosoft_current_sln)
|
|
356 return []
|
|
357 endif
|
|
358 let l:output = vimcrosoft#exec_script_now("list_sln_projects",
|
|
359 \g:vimcrosoft_current_sln,
|
|
360 \'-c', g:vimcrosoft_current_sln_cache,
|
|
361 \'--full-names')
|
|
362 return split(l:output, "\n")
|
|
363 endfunction
|
|
364
|
|
365 function! vimcrosoft#get_sln_config_platforms() abort
|
|
366 if empty(g:vimcrosoft_current_sln)
|
|
367 return []
|
|
368 endif
|
|
369 let l:output = vimcrosoft#exec_script_now("list_sln_configs",
|
|
370 \g:vimcrosoft_current_sln,
|
|
371 \'-c', g:vimcrosoft_current_sln_cache)
|
|
372 return split(l:output, "\n")
|
|
373 endfunction
|
|
374
|
|
375 " }}}
|
|
376
|
|
377 " {{{ Commands Auto-completion
|
|
378
|
|
379 function! vimcrosoft#complete_current_sln_projects(ArgLead, CmdLine, CursorPos)
|
|
380 let l:proj_names = vimcrosoft#get_sln_project_names()
|
|
381 let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g')
|
|
382 let l:projnames = filter(l:proj_names,
|
|
383 \{idx, val -> val =~? l:argpat})
|
|
384 return l:projnames
|
|
385 endfunction
|
|
386
|
|
387 function! vimcrosoft#complete_current_sln_config_platforms(ArgLead, CmdLine, CursorPos)
|
|
388 let l:cfgplats = vimcrosoft#get_sln_config_platforms()
|
|
389 let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g')
|
|
390 let l:cfgplatnames = filter(l:cfgplats,
|
|
391 \{idx, val -> val =~? l:argpat})
|
|
392 return l:cfgplatnames
|
|
393 endfunction
|
|
394
|
|
395 " }}}
|
|
396
|
|
397 " {{{ Statusline Functions
|
|
398
|
|
399 function! vimcrosoft#statusline(...)
|
|
400 if empty(g:vimcrosoft_current_sln)
|
|
401 return ''
|
|
402 endif
|
|
403
|
|
404 let l:line = fnamemodify(g:vimcrosoft_current_sln, ':t')
|
|
405 if !empty(g:vimcrosoft_active_project)
|
|
406 let l:line .= '('.g:vimcrosoft_active_project.')'
|
|
407 endif
|
|
408 let l:line .= ' ['.
|
|
409 \g:vimcrosoft_current_config.'|'.
|
|
410 \g:vimcrosoft_current_platform.']'
|
|
411 return l:line
|
|
412 endfunction
|
|
413
|
|
414 " }}}
|
|
415
|
|
416 " {{{ Initialization
|
|
417
|
|
418 function! vimcrosoft#init() abort
|
|
419 call vimcrosoft#trace("Loading modules...")
|
|
420 for modpath in s:modules
|
|
421 execute 'source '.fnameescape(modpath)
|
|
422 endfor
|
|
423
|
|
424 call vimcrosoft#call_modules('init')
|
|
425
|
|
426 if g:vimcrosoft_auto_find_sln
|
|
427 call vimcrosoft#auto_find_sln(1)
|
|
428 call vimcrosoft#load_config()
|
|
429 endif
|
|
430 endfunction
|
|
431
|
|
432 " }}}
|