comparison autoload/vimcrosoft.vim @ 0:5d2c0db51914

Initial commit
author Ludovic Chabant <ludovic@chabant.com>
date Tue, 17 Sep 2019 13:24:24 -0700
parents
children 376f3371c311
comparison
equal deleted inserted replaced
-1:000000000000 0:5d2c0db51914
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 " }}}