Mercurial > vim-crosoft
changeset 0:5d2c0db51914
Initial commit
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 17 Sep 2019 13:24:24 -0700 |
parents | |
children | 426cb9c33353 |
files | README.md autoload/vimcrosoft.vim autoload/vimcrosoft/fzf.vim autoload/vimcrosoft/youcompleteme.vim compiler/vimcrosoftsln.vim doc/vimcrosoft.vim plugin/vimcrosoft.vim scripts/__init__.py scripts/build_sln_cache.py scripts/get_proj_config.py scripts/list_sln_configs.py scripts/list_sln_files.py scripts/list_sln_projects.py scripts/logutil.py scripts/vimutil.py scripts/vsutil.py scripts/ycm_extra_conf.py |
diffstat | 16 files changed, 1803 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,10 @@ + +# Vim-crosoft + +Vimcrosoft is a Vim plugin for working with Microsoft Visual Studio solutions. +It makes it possible to build solutions and projects from Vim, and integrates +with various other plugins, such as letting you list solution files in FZF, or +get code completion with YouCompleteMe. + +It's currently a work in progress in alpha state. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/vimcrosoft.vim Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,432 @@ +" vimcrosoft.vim - A wrapper for Visual Studio solutions + +" Utilities {{{ + +let s:basedir = expand('<sfile>:p:h:h') + +function! vimcrosoft#throw(message) + throw "vimcrosoft: ".a:message +endfunction + +function! vimcrosoft#error(message) + let v:errmsg = "vimcrosoft: ".a:message + echoerr v:errmsg +endfunction + +function! vimcrosoft#warning(message) + echohl WarningMsg + echom "vimcrosoft: ".a:message + echohl None +endfunction + +function! vimcrosoft#trace(message) + if g:vimcrosoft_trace + echom "vimcrosoft: ".a:message + endif +endfunction + +function! vimcrosoft#ensure_msbuild_found() abort + if !empty(g:vimcrosoft_msbuild_path) + return 1 + endif + + let l:programfilesdir = get(environ(), 'ProgramFiles(x86)') + let l:vswhere = l:programfilesdir.'\Microsoft Visual Studio\Installer\vswhere.exe' + if !executable(l:vswhere) + call vimcrosoft#error("Can't find `vswhere` -- you must set `g:vimcrosoft_msbuild_path` yourself.") + return 0 + endif + + let l:vswhere_cmdline = '"'.l:vswhere.'" '. + \'-prerelease -latest -products * '. + \'-requires Microsoft.Component.MSBuild '. + \'-property installationPath' + call vimcrosoft#trace("Trying to find MSBuild, running: ".l:vswhere_cmdline) + let l:installdirs = split(system(l:vswhere_cmdline), '\n') + call vimcrosoft#trace("Got: ".string(l:installdirs)) + for installdir in l:installdirs + let l:msbuild = installdir.'\MSBuild\Current\Bin\MSBuild.exe' + if executable(l:msbuild) + let g:vimcrosoft_msbuild_path = l:msbuild + return 1 + endif + let l:msbuild = installdir.'\MSBuild\15.0\Bin\MSBuild.exe' + if executable(l:msbuild) + let g:vimcrosoft_msbuild_path = l:msbuild + return 1 + endif + endfor + + call vimcrosoft#error("Couldn't find MSBuild anywhere in:\n". + \string(l:installdirs)) + return 0 +endfunction + +" }}} + +" Cache Files {{{ + +function! vimcrosoft#get_sln_cache_dir(...) abort + if empty(g:vimcrosoft_current_sln) + if a:0 && a:1 + call vimcrosoft#throw("No solution is currently set.") + endif + return '' + endif + let l:cache_dir = fnamemodify(g:vimcrosoft_current_sln, ':h') + let l:cache_dir .= '\.vimcrosoft' + return l:cache_dir +endfunction + +function! vimcrosoft#get_sln_cache_file(filename) abort + let l:cache_dir = vimcrosoft#get_sln_cache_dir() + if empty(l:cache_dir) + return '' + else + return l:cache_dir.'\'.a:filename + endif +endfunction + +" }}} + +" Configuration Files {{{ + +let s:config_format = 2 + +function! vimcrosoft#save_config() abort + if empty(g:vimcrosoft_current_sln) + return + endif + + call vimcrosoft#trace("Saving config for: ".g:vimcrosoft_current_sln) + let l:lines = [ + \'format='.string(s:config_format), + \g:vimcrosoft_current_sln, + \g:vimcrosoft_current_config, + \g:vimcrosoft_current_platform, + \g:vimcrosoft_active_project] + let l:configfile = vimcrosoft#get_sln_cache_file('config.txt') + call writefile(l:lines, l:configfile) +endfunction + +function! vimcrosoft#load_config() abort + if empty(g:vimcrosoft_current_sln) + return + endif + + let l:configfile = vimcrosoft#get_sln_cache_file('config.txt') + if !filereadable(l:configfile) + return + endif + + let l:lines = readfile(l:configfile) + let l:format_line = l:lines[0] + if l:format_line == 'format='.string(s:config_format) + let g:vimcrosoft_current_sln = l:lines[1] + let g:vimcrosoft_current_config = l:lines[2] + let g:vimcrosoft_current_platform = l:lines[3] + let g:vimcrosoft_active_project = l:lines[4] + else + call vimcrosoft#warning("Solution configuration format has changed ". + \"since you last opened this solution in Vim. ". + \"You previous configuration/platform has NOT been ". + \"restored.") + endif +endfunction + +" }}} + +" {{{ Scripts + +let s:scriptsdir = s:basedir.'\scripts' + +function! vimcrosoft#get_script_path(scriptname) abort + return s:scriptsdir.'\'.a:scriptname +endfunction + +function! vimcrosoft#exec_script_job(scriptname, ...) abort + let l:scriptpath = vimcrosoft#get_script_path(a:scriptname) + let l:cmd = ['python', l:scriptpath] + a:000 + return job_start(l:cmd) +endfunction + +let s:scriptsdir_added_to_sys = 0 +let s:scripts_imported = [] + +function! s:install_scriptsdir() abort + if !s:scriptsdir_added_to_sys + execute 'python3 import sys' + execute 'python3 sys.path.append("'.escape(s:scriptsdir, "\\").'")' + execute 'python3 import vimutil' + let s:scriptsdir_added_to_sys = 1 + endif +endfunction + +function! vimcrosoft#exec_script_now(scriptname, ...) abort + if g:vimcrosoft_use_external_python + let l:cmd = 'python '.shellescape(vimcrosoft#get_script_path(a:scriptname.'.py')) + " TODO: shellescape arguments? + let l:cmd .= ' '.join(a:000, " ") + let l:output = system(l:cmd) + else + call s:install_scriptsdir() + if index(s:scripts_imported, a:scriptname) < 0 + execute 'python3 import '.a:scriptname + call add(s:scripts_imported, a:scriptname) + endif + let l:line = 'vimutil.runscript('.a:scriptname.'.main' + if a:0 > 0 + let l:args = copy(a:000) + call map(l:args, {idx, val -> escape(val, '\"')}) + let l:line .= ', "'.join(l:args, '", "').'"' + endif + let l:line .= ')' + call vimcrosoft#trace("Executing: ".l:line) + let l:output = py3eval(l:line) + endif + return l:output +endfunction + +" }}} + +" Module Management {{{ + +let s:modulesdir = s:basedir.'\autoload\vimcrosoft' +let s:modules = glob(s:modulesdir.'\*.vim', 0, 1) + +function! vimcrosoft#call_modules(funcname, ...) abort + for modpath in s:modules + let l:modname = fnamemodify(modpath, ':t:r') + let l:fullfuncname = 'vimcrosoft#'.l:modname.'#'.a:funcname + if exists("*".l:fullfuncname) + call vimcrosoft#trace("Module ".l:modname.": calling ".a:funcname) + call call(l:fullfuncname, a:000) + else + call vimcrosoft#trace("Skipping ".l:fullfuncname.": doesn't exist.") + endif + endfor +endfunction + +" }}} + +" Solution Management {{{ + +function! vimcrosoft#set_sln(slnpath, ...) abort + let g:vimcrosoft_current_sln = a:slnpath + + let l:sln_was_set = !empty(a:slnpath) + if l:sln_was_set + let g:vimcrosoft_current_sln_cache = vimcrosoft#get_sln_cache_file("slncache.bin") + call vimcrosoft#call_modules('on_sln_changed', a:slnpath) + else + let g:vimcrosoft_current_sln_cache = '' + call vimcrosoft#call_modules('on_sln_cleared') + endif + + let l:silent = a:0 && a:1 + if !l:silent + call vimcrosoft#save_config() + + if l:sln_was_set + echom "Current solution: ".a:slnpath + else + echom "No current solution anymore" + endif + endif +endfunction + +function! vimcrosoft#auto_find_sln(...) abort + let l:path = getcwd() + try + let l:slnpath = vimcrosoft#find_sln(l:path) + catch /^vimcrosoft:/ + let l:slnpath = '' + endtry + let l:silent = a:0 && a:1 + call vimcrosoft#set_sln(l:slnpath, l:silent) +endfunction + +function! vimcrosoft#find_sln(curpath) abort + if g:vimcrosoft_sln_finder != '' + return call(g:vimcrosoft_sln_finder, [a:curpath]) + endif + return vimcrosoft#default_sln_finder(a:curpath) +endfunction + +function! vimcrosoft#default_sln_finder(path) abort + let l:cur = a:path + let l:prev = "" + while l:cur != l:prev + let l:slnfiles = globpath(l:cur, '*.sln', 0, 1) + if !empty(l:slnfiles) + call vimcrosoft#trace("Found solution file: ".l:slnfiles[0]) + return l:slnfiles[0] + endif + let l:prev = l:cur + let l:cur = fnamemodify(l:cur, ':h') + endwhile + call vimcrosoft#throw("No solution file found.") +endfunction + +function! vimcrosoft#set_active_project(projname, ...) abort + " Strip trailing spaces in the project name. + let l:projname = substitute(a:projname, '\v\s+$', '', 'g') + + let g:vimcrosoft_active_project = l:projname + call vimcrosoft#call_modules('on_active_project_changed', l:projname) + + let l:silent = a:0 && a:1 + if !l:silent + call vimcrosoft#save_config() + echom "Active project changed" + endif +endfunction + +function! vimcrosoft#build_sln(target) abort + let l:args = [] + if !empty(a:target) + call add(l:args, '/t:'.a:target) + endif + call vimcrosoft#run_make(l:args) +endfunction + +function! vimcrosoft#build_project(projname, target, only) abort + let l:projname = !empty(a:projname) ? a:projname : g:vimcrosoft_active_project + if empty(l:projname) + call vimcrosoft#error("No project name given, and no active project set.") + return + endif + + " Strip trailing spaces in the project name. + let l:projname = substitute(l:projname, '\v\s+$', '', 'g') + let l:target = '/t:'.tr(l:projname, '.', '_') + if !empty(a:target) + let l:target .= ':'.a:target + endif + + let l:args = [] + call add(l:args, l:target) + if a:only + call add(l:args, '/p:BuildProjectReferences=false') + endif + call vimcrosoft#run_make(l:args) +endfunction + +function! vimcrosoft#run_make(customargs) abort + if !vimcrosoft#ensure_msbuild_found() + return + endif + + " Add some common arguments for MSBuild. + let l:fullargs = copy(a:customargs) + call add(l:fullargs, '"/p:Configuration='.g:vimcrosoft_current_config.'"') + call add(l:fullargs, '"/p:Platform='.g:vimcrosoft_current_platform.'"') + " Add the solution file itself. + call add(l:fullargs, '"'.g:vimcrosoft_current_sln.'"') + + " Setup the backdoor args list for our compiler to pick-up, and run + " the make process. + let g:vimcrosoft_temp_compiler_args__ = l:fullargs + compiler vimcrosoftsln + if !empty(g:vimcrosoft_make_command) + execute g:vimcrosoft_make_command + elseif exists(":Make") " Support for vim-dispatch. + Make + else + make + endif +endfunction + +function! vimcrosoft#set_config_platform(configplatform) + let l:bits = split(a:configplatform, '|') + if len(l:bits) != 2 + call vimcrosoft#throw("Expected a value of the form: Config|Platform") + endif + + let g:vimcrosoft_current_config = l:bits[0] + let g:vimcrosoft_current_platform = l:bits[1] + call vimcrosoft#call_modules('on_config_platform_changed', + \g:vimcrosoft_current_config, g:vimcrosoft_current_platform) + + call vimcrosoft#save_config() +endfunction + +function! vimcrosoft#get_sln_project_names() abort + if empty(g:vimcrosoft_current_sln) + return [] + endif + let l:output = vimcrosoft#exec_script_now("list_sln_projects", + \g:vimcrosoft_current_sln, + \'-c', g:vimcrosoft_current_sln_cache, + \'--full-names') + return split(l:output, "\n") +endfunction + +function! vimcrosoft#get_sln_config_platforms() abort + if empty(g:vimcrosoft_current_sln) + return [] + endif + let l:output = vimcrosoft#exec_script_now("list_sln_configs", + \g:vimcrosoft_current_sln, + \'-c', g:vimcrosoft_current_sln_cache) + return split(l:output, "\n") +endfunction + +" }}} + +" {{{ Commands Auto-completion + +function! vimcrosoft#complete_current_sln_projects(ArgLead, CmdLine, CursorPos) + let l:proj_names = vimcrosoft#get_sln_project_names() + let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g') + let l:projnames = filter(l:proj_names, + \{idx, val -> val =~? l:argpat}) + return l:projnames +endfunction + +function! vimcrosoft#complete_current_sln_config_platforms(ArgLead, CmdLine, CursorPos) + let l:cfgplats = vimcrosoft#get_sln_config_platforms() + let l:argpat = '^'.substitute(a:ArgLead, '\', '', 'g') + let l:cfgplatnames = filter(l:cfgplats, + \{idx, val -> val =~? l:argpat}) + return l:cfgplatnames +endfunction + +" }}} + +" {{{ Statusline Functions + +function! vimcrosoft#statusline(...) + if empty(g:vimcrosoft_current_sln) + return '' + endif + + let l:line = fnamemodify(g:vimcrosoft_current_sln, ':t') + if !empty(g:vimcrosoft_active_project) + let l:line .= '('.g:vimcrosoft_active_project.')' + endif + let l:line .= ' ['. + \g:vimcrosoft_current_config.'|'. + \g:vimcrosoft_current_platform.']' + return l:line +endfunction + +" }}} + +" {{{ Initialization + +function! vimcrosoft#init() abort + call vimcrosoft#trace("Loading modules...") + for modpath in s:modules + execute 'source '.fnameescape(modpath) + endfor + + call vimcrosoft#call_modules('init') + + if g:vimcrosoft_auto_find_sln + call vimcrosoft#auto_find_sln(1) + call vimcrosoft#load_config() + endif +endfunction + +" }}}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/vimcrosoft/fzf.vim Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,19 @@ + +function! vimcrosoft#fzf#init() abort +endfunction + +function! vimcrosoft#fzf#on_sln_changed(slnpath) abort + let $FZF_DEFAULT_COMMAND = s:build_file_list_command(a:slnpath) +endfunction + +function! vimcrosoft#fzf#on_sln_cleared() abort + unlet $FZF_DEFAULT_COMMAND +endfunction + +function! s:build_file_list_command(slnpath) abort + let l:scriptpath = vimcrosoft#get_script_path('list_sln_files.py') + return 'python '.shellescape(l:scriptpath). + \' '.shellescape(a:slnpath) + \.' --cache '.shellescape(g:vimcrosoft_current_sln_cache) +endfunction +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/vimcrosoft/youcompleteme.vim Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,24 @@ + +function! vimcrosoft#youcompleteme#init() abort +endfunction + +function! vimcrosoft#youcompleteme#on_sln_changed(slnpath) abort + let g:ycm_global_ycm_extra_conf = vimcrosoft#get_script_path('ycm_extra_conf.py') + let g:ycm_extra_conf_vim_data = [ + \'g:vimcrosoft_current_sln', + \'g:vimcrosoft_current_sln_cache', + \'g:vimcrosoft_current_config', + \'g:vimcrosoft_current_platform' + \] +endfunction + +function! vimcrosoft#youcompleteme#on_sln_cleared() abort + let g:ycm_global_ycm_extra_conf = '' + let g:ycm_extra_conf_vim_data = [] +endfunction + +function! vimcrosoft#youcompleteme#on_config_platform_changed(config, platform) abort + if exists(":YcmCompleter") + YcmCompleter ClearCompilationFlagCache + endif +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/compiler/vimcrosoftsln.vim Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,28 @@ +" Compiler file for Visual Studio +" Compiler: Visual Studio +" Maintainer: Ludovic Chabant <https://ludovic.chabant.com> + +if exists("current_compiler") + finish +endif +let current_compiler = "vimcrosoftsln" + +let s:keepcpo = &cpo + +let s:prgargs = '' +if !empty(g:vimcrosoft_temp_compiler_args__) + let s:tmpargs = map( + \copy(g:vimcrosoft_temp_compiler_args__), + \{idx, val -> escape(val, ' \"')}) + let s:prgargs = '\ '.join(s:tmpargs, '\ ') +endif + +let s:prgcmdline = fnameescape('"'.g:vimcrosoft_msbuild_path.'"').s:prgargs +call vimcrosoft#trace("Setting makeprg to: ".s:prgcmdline) +execute "CompilerSet makeprg=".s:prgcmdline + +CompilerSet errorformat& + +let &cpo = s:keepcpo +unlet s:keepcpo +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/vimcrosoft.vim Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,169 @@ +*vimcrosoft.txt* Work with Visual Studio solutions in Vim + + +$$\ $$\$$$$$$\$$\ $$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\$$$$$$$$\ +$$ | $$ \_$$ _$$$\ $$$ $$ __$$\$$ __$$\$$ __$$\$$ __$$\$$ __$$\$$ _____\__$$ __| +$$ | $$ | $$ | $$$$\ $$$$ $$ / \__$$ | $$ $$ / $$ $$ / \__$$ / $$ $$ | $$ | +\$$\ $$ | $$ | $$\$$\$$ $$ $$ | $$$$$$$ $$ | $$ \$$$$$$\ $$ | $$ $$$$$\ $$ | + \$$\$$ / $$ | $$ \$$$ $$ $$ | $$ __$$<$$ | $$ |\____$$\$$ | $$ $$ __| $$ | + \$$$ / $$ | $$ |\$ /$$ $$ | $$\$$ | $$ $$ | $$ $$\ $$ $$ | $$ $$ | $$ | + \$ / $$$$$$\$$ | \_/ $$ \$$$$$$ $$ | $$ |$$$$$$ \$$$$$$ |$$$$$$ $$ | $$ | + \_/ \______\__| \__|\______/\__| \__|\______/ \______/ \______/\__| \__| + + + VIM-CROSOFT + + *vimcrosoft* + +============================================================================== +Configuration *vimcrosoft-configuration* + + *g:vimcrosoft_trace* +g:vimcrosoft_trace + Enables debugging information. + Default: `0` + + *g:vimcrosoft_auto_find_sln* +g:vimcrosoft_auto_find_sln + Try and find a solution for the current working + directory on Vim startup. This effectively executes + |:VimcrosoftAutoFindSln| upon startup. + Default: `0` + + *g:vimcrosoft_sln_finder* +g:vimcrosoft_sln_finder + The name of a function to call to find a solution file + from a given current path. If not set, Vimcrosoft will + use its own default finder, which just walks up the + directory tree until it finds any `*.sln` files. + Default: `""` + + *g:vimcrosoft_msbuild_path* +g:vimcrosoft_msbuild_path + By default, Vimcrosoft automatically finds where + MSBuild is installed. If that fails, you can specify + the path to the MSBuild executable directly. + Default: `""` + + *g:vimcrosoft_make_command* +g:vimcrosoft_make_command + The command to run when starting builds. If empty, + Vimcrosoft will use |:make|, unless the vim-dispatch + plugin is detected, in which case it will use |:Make|. + If the option is not empty, it will use whatever you + specified. + Default: `""` + +============================================================================== +Commands *vimcrosoft-commands* + + *vimcrosoft-solution-commands* +Here's a list of solution-related commands: + + *:VimcrosoftAutoFindSln* +:VimcrosoftAutoFindSln + Finds a solution file (`*.sln`) in the current working + directory, or any of its parent directories, and sets + it as the current solution file (see + |:VimcrosoftSetSln|). If any solution files are found, + the first one is used. + + *:VimcrosoftSetSln* +:VimcrosoftSetSln <file> + Sets the currently active solution file. All + vim-crosoft commands will relate to this solution. + + *:VimcrosoftUnsetSln* +:VimcrosoftUnsetSln + Unsets the currently active solution file. + + *:VimcrosoftSetConfigPlatform* +:VimcrosoftSetConfigPlatform <configplatform> + Sets the currently active configuration and platform + for the active solution. The argument is a combo of + configuration and platform, in the form of + `Configuration|Platform`. + + *:VimcrosoftBuildSln* +:VimcrosoftBuildSln + Starts a build on the current solution, using the + current configuration and platform. + + *:VimcrosoftRebuildSln* +:VimcrosoftRebuildSln + Rebuilds the current solution, using the current + configuration and platform. + + *:VimcrosoftCleanSln* +:VimcrosoftCleanSln + Cleans the current solution for the current + configuration and platform. + + + *vimcrosoft-project-commands* +Here are some project-related commands: + + *:VimcrosoftBuildProject* +:VimcrosoftBuildProject + Builds the active project for the current + configuration and platform. MSBuild will typically + build all its dependencies first. + + *:VimcrosoftBuildProjectOnly* +:VimcrosoftBuildProjectOnly + Builds the active project for the current + configuration and platform, but skips building any + dependencies. + + *:VimcrosoftRebuildProject* +:VimcrosoftRebuildProject + Rebuilds the active project for the current + configuration and platform. + + *:VimcrosoftCleanProject* +:VimcrosoftCleanProject + Cleans the active project for the current + configuration and platform. + + + *vimcrosoft-active-project-commands* +Vimcrosoft lets you specify an "active project" that makes it quicker to +build/clean/etc. + + *:VimcrosoftSetActiveProject* +:VimcrosoftSetActiveProject + Sets the active project for the current solution. This + enables a few "shortcut" commands that operate on it + directly. + + *:VimcrosoftBuildActiveProject* +:VimcrosoftBuildActiveProject + Builds the active project for the current + configuration and platform. MSBuild will typically + build all its dependencies first. + + *:VimcrosoftBuildActiveProjectOnly* +:VimcrosoftBuildActiveProjectOnly + Builds the active project for the current + configuration and platform, but skips building any + dependencies. + + *:VimcrosoftRebuildActiveProject* +:VimcrosoftRebuildActiveProject + Rebuilds the active project for the current + configuration and platform. + + *:VimcrosoftCleanActiveProject* +:VimcrosoftCleanActiveProject + Cleans the active project for the current + configuration and platform. + + +============================================================================== +Statusline *vimcrosoft-statusline* + +You can show some vimcrosoft-related information in your 'statusline' by +calling the `vimcrosoft#statusline()` function. + + +" vim:tw=78:et:ft=help:norl:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/vimcrosoft.vim Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,74 @@ +" vimcrosoft.vim - A wrapper for Visual Studio solutions +" Maintainer: Ludovic Chabant <https://ludovic.chabant.com> + +" Globals {{{ + +if (&cp || get(g:, 'vimcrosoft_dont_load', 0)) + finish +endif + +let g:vimcrosoft_trace = get(g:, 'vimcrosoft_trace', 0) + +let g:vimcrosoft_current_sln = get(g:, 'vimcrosoft_current_sln', '') +let g:vimcrosoft_current_config = get(g:, 'vimcrosoft_current_config', '') +let g:vimcrosoft_current_platform = get(g:, 'vimcrosoft_current_platform', '') +let g:vimcrosoft_active_project = get(g:, 'vimcrosoft_active_project', '') + +let g:vimcrosoft_auto_find_sln = get(g:, 'vimcrosoft_auto_find_sln', 1) +let g:vimcrosoft_sln_finder = get(g:, 'vimcrosoft_sln_finder', '') + +let g:vimcrosoft_current_sln_cache = '' + +let g:vimcrosoft_msbuild_path = get(g:, 'vimcrosoft_msbuild_path', '') +let g:vimcrosoft_use_external_python = get(g:, 'vimcrosoft_use_external_python', 0) +let g:vimcrosoft_make_command = get(g:, 'vimcrosoft_make_command', '') + +" }}} + +" Commands {{{ + +command! VimcrosoftAutoFindSln :call vimcrosoft#auto_find_sln() +command! -nargs=1 -complete=file VimcrosoftSetSln :call vimcrosoft#set_sln(<f-args>) +command! VimcrosoftUnsetSln :call Vimcrosoft#set_sln("") + +command! -nargs=1 + \ -complete=customlist,vimcrosoft#complete_current_sln_config_platforms + \ VimcrosoftSetConfigPlatform + \ :call vimcrosoft#set_config_platform(<f-args>) + +command! VimcrosoftBuildSln :call vimcrosoft#build_sln('Build') +command! VimcrosoftRebuildSln :call vimcrosoft#build_sln('Rebuild') +command! VimcrosoftCleanSln :call vimcrosoft#build_sln('Clean') + +command! -nargs=1 + \ -complete=customlist,vimcrosoft#complete_current_sln_projects + \ VimcrosoftSetActiveProject + \ :call vimcrosoft#set_active_project(<f-args>) +command! VimcrosoftBuildActiveProject :call vimcrosoft#build_project('', '', 0) +command! VimcrosoftBuildActiveProjectOnly :call vimcrosoft#build_project('', '', 1) +command! VimcrosoftRebuildActiveProject :call vimcrosoft#build_project('', 'Rebuild', 0) +command! VimcrosoftCleanActiveProject :call vimcrosoft#build_project('', 'Clean', 0) + +command! -nargs=1 + \ -complete=customlist,vimcrosoft#complete_current_sln_projects + \ VimcrosoftBuildProject + \ :call vimcrosoft#build_project(<f-args>, '', 0) +command! -nargs=1 + \ -complete=customlist,vimcrosoft#complete_current_sln_projects + \ VimcrosoftBuildProjectOnly + \ :call vimcrosoft#build_project(<f-args>, '', 1) +command! -nargs=1 + \ -complete=customlist,vimcrosoft#complete_current_sln_projects + \ VimcrosoftRebuildProject + \ :call vimcrosoft#build_project(<f-args>, 'Rebuild', 1) +command! -nargs=1 -complete=customlist,vimcrosoft#complete_current_sln_projects + \ VimcrosoftCleanProject + \ :call vimcrosoft#build_project(<f-args>, 'Clean', 1) + +" }}} + +" Initialization {{{ + +call vimcrosoft#init() + +" }}}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/build_sln_cache.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,30 @@ +import os.path +import logging +import argparse +from vsutil import SolutionCache + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('solution', + help="The path to the solution file") + parser.add_argument('cache', + help="The path to the cache file") + parser.add_argument('-v', '--verbose', + action='store_true') + args = parser.parse_args() + + loglevel = logging.INFO + if args.verbose: + loglevel = logging.DEBUG + logging.basicConfig(level=loglevel) + logger = logging.getLogger() + + cache, loaded = SolutionCache.load_or_rebuild(args.solution, args.cache) + if not loaded: + total_items = sum([len(i) for i in cache.index.values()]) + logger.debug(f"Built cache with {total_items} items.") + + +if __name__ == '__main__': + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/get_proj_config.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,52 @@ +import argparse +import logging +import re +from vsutil import SolutionCache + + +re = _re_proj_cfg_suffix = re.compile(r'\.(ActiveCfg|Build\.0)$') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('solution', + help="The path to the Visual Studio solution file.") + parser.add_argument('cache', + help="The path to the solution cache.") + parser.add_argument('project', + help="The name of the project to check for.") + parser.add_argument('slnconfig', + help="The solution configuration.") + parser.add_argument('-v', '--verbose', + action='store_true', + help="Show verbose information.") + args = parser.parse_args() + + loglevel = logging.INFO + if args.verbose: + loglevel = logging.DEBUG + logging.basicConfig(level=loglevel) + logger = logging.getLogger() + + cache, loaded = SolutionCache.load_or_rebuild(args.solution, args.cache) + logger.debug("Cache was %s" % ("valid" if loaded else "not valid")) + + proj = cache.slnobj.find_project_by_name(args.project) + if proj is None: + raise Exception("No such project: %s" % args.project) + projguid = '{%s}' % proj.guid + + slnconfig = args.slnconfig.replace(' ', '_') + sec = cache.slnobj.globalsection('ProjectConfigurationPlatforms') + for e in sec.entries: + if e.name.startswith(projguid): + if slnconfig == e.value.replace(' ', '_'): + projcfg = e.name[len(projguid) + 1:] + projcfg = _re_proj_cfg_suffix.sub('', projcfg) + print(projcfg) + break + + +if __name__ == '__main__': + main() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/list_sln_configs.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,31 @@ +import argparse +import logging +from logutil import setup_logging +from vsutil import SolutionCache + + +logger = logging.getLogger(__name__) + + +def main(args=None): + parser = argparse.ArgumentParser() + parser.add_argument('solution', + help="The path to the Visual Studio solution file.") + parser.add_argument('-c', '--cache', + help="The path to the solution cache.") + parser.add_argument('-v', '--verbose', + action='store_true', + help="Show verbose information.") + args = parser.parse_args(args) + setup_logging(args.verbose) + + cache, _ = SolutionCache.load_or_rebuild(args.solution, args.cache) + sec = cache.slnobj.globalsection('SolutionConfigurationPlatforms') + for e in sec.entries: + config, platform = e.name.split('|') + if config != "Invalid" and platform != "Invalid": + print(e.name) + + +if __name__ == '__main__': + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/list_sln_files.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,41 @@ +import argparse +import logging +import os.path +from logutil import setup_logging +from vsutil import SolutionCache + + +logger = logging.getLogger(__name__) + + +def main(args=None): + parser = argparse.ArgumentParser() + parser.add_argument('solution', + help="The path to the Visual Studio solution file.") + parser.add_argument('-p', '--project', + help="Only list files in the named project.") + parser.add_argument('-c', '--cache', + help="Use a cache file to store the file list.") + parser.add_argument('-v', '--verbose', + action='store_true', + help="Show verbose information.") + args = parser.parse_args(args) + setup_logging(args.verbose) + + cache, _ = SolutionCache.load_or_rebuild(args.solution, args.cache) + slnobj = cache.slnobj + + projs = slnobj.projects + if args.project: + projs = [slnobj.find_project_by_name(args.project)] + projs = list(filter(lambda p: not p.is_folder, projs)) + + for p in projs: + ig = p.defaultitemgroup() + for i in ig.get_source_items(): + file_path = os.path.abspath(os.path.join(p.absdirpath, i.include)) + print(file_path) + + +if __name__ == '__main__': + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/list_sln_projects.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,58 @@ +import argparse +import logging +from logutil import setup_logging +from vsutil import SolutionCache + + +logger = logging.getLogger(__name__) + + +def main(args=None): + parser = argparse.ArgumentParser() + parser.add_argument('solution', + help="The path to the Visual Studio solution file.") + parser.add_argument('-c', '--cache', + help="The path to the solution cache.") + parser.add_argument('-f', '--full-names', + action='store_true', + help="Print full names for nested projects.") + parser.add_argument('-v', '--verbose', + action='store_true', + help="Show verbose information.") + args = parser.parse_args(args) + setup_logging(args.verbose) + + cache, _ = SolutionCache.load_or_rebuild(args.solution, args.cache) + slnobj = cache.slnobj + logger.debug("Found {0} projects:".format(len(slnobj.projects))) + + non_folder_projs = list(filter(lambda p: not p.is_folder, + slnobj.projects)) + if args.full_names: + parenting = {} + nesting_sec = slnobj.globalsection("NestedProjects") + if nesting_sec: + for entry in nesting_sec.entries: + child_guid, parent_guid = (entry.name.strip('{}'), + entry.value.strip('{}')) + child = slnobj.find_project_by_guid(child_guid, False) + parent = slnobj.find_project_by_guid(parent_guid, False) + parenting[child] = parent + + for p in non_folder_projs: + full_name = p.name + cur_p = p + while True: + cur_p = parenting.get(cur_p) + if not cur_p: + break + full_name = cur_p.name + "\\" + full_name + print(full_name) + else: + for p in non_folder_projs: + print(p.name) + + +if __name__ == '__main__': + main() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/logutil.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,9 @@ +import logging + + +def setup_logging(verbose): + loglevel = logging.INFO + if verbose: + loglevel = logging.DEBUG + logging.basicConfig(level=loglevel) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/vimutil.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,19 @@ +import io +import sys + + +def runscript(scriptfunc, *args): + """ Executes a given function with the given args. This is because + the convention is that all python scripts here should have a `main` + function that takes a custom list of args to override the default + behaviour of using `sys.args`. + """ + prevout = sys.stdout + captured = io.StringIO() + sys.stdout = captured + try: + scriptfunc(args) + finally: + sys.stdout = prevout + return captured.getvalue() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/vsutil.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,592 @@ +import copy +import logging +import os.path +import pickle +import re +import xml.etree.ElementTree as etree + + +# Known VS project types. +PROJ_TYPE_FOLDER = '2150E333-8FDC-42A3-9474-1A3956D46DE8' +PROJ_TYPE_NMAKE = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942' +PROJ_TYPE_CSHARP = 'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC' + +PROJ_TYPE_NAMES = { + PROJ_TYPE_FOLDER: 'folder', + PROJ_TYPE_NMAKE: 'nmake', + PROJ_TYPE_CSHARP: 'csharp' +} + +# Known VS item types. +ITEM_TYPE_CPP_SRC = 'ClCompile' +ITEM_TYPE_CPP_HDR = 'ClInclude' + +ITEM_TYPE_CS_REF = 'Reference' +ITEM_TYPE_CS_PROJREF = 'ProjectReference' +ITEM_TYPE_CS_SRC = 'Compile' + +ITEM_TYPE_NONE = 'None' + +ITEM_TYPE_SOURCE_FILES = (ITEM_TYPE_CPP_SRC, ITEM_TYPE_CPP_HDR, + ITEM_TYPE_CS_SRC) + + +# Known VS properties. +PROP_CONFIGURATION_TYPE = 'ConfigurationType' +PROP_NMAKE_PREPROCESSOR_DEFINITIONS = 'NMakePreprocessorDefinitions' +PROP_NMAKE_INCLUDE_SEARCH_PATH = 'NMakeIncludeSearchPath' + + +logger = logging.getLogger(__name__) + + +def _strip_ns(tag): + """ Remove the XML namespace from a tag name. """ + if tag[0] == '{': + i = tag.index('}') + return tag[i+1:] + return tag + + +re_msbuild_var = re.compile(r'\$\((?P<var>[\w\d_]+)\)') + + +def _resolve_value(val, env): + """ Expands MSBuild property values given a build environment. """ + def _repl_vars(m): + varname = m.group('var') + varval = env.get(varname, '') + return varval + + if not val: + return val + return re_msbuild_var.sub(_repl_vars, val) + + +def _evaluate_condition(cond, env): + """ Expands MSBuild property values in a condition and evaluates it. """ + left, right = _resolve_value(cond, env).split('==') + return left == right + + +class VSBaseGroup: + """ Base class for VS project stuff that has conditional stuff inside. + + For instance, a property group called 'Blah' might have some common + (always valid) stuff, but a bunch of other stuff that should only + be considered when the solution configuration is Debug, Release, or + whatever else. In that case, each 'conditional' (i.e. values for Debug, + values for Release, etc.) is listed and tracked separately until + we are asked to 'resolve' ourselves based on a given build environment. + """ + def __init__(self, label): + self.label = label + self.conditionals = {} + + def get_conditional(self, condition): + """ Adds a conditional sub-group. """ + return self.conditionals.get(condition) + + def get_or_create_conditional(self, condition): + """ Gets or creates a new conditional sub-group. """ + c = self.get_conditional(condition) + if not c: + c = self.__class__(self.label) + self.conditionals[condition] = c + return c + + def resolve(self, env): + """ Resolves this group by evaluating each conditional sub-group + based on the given build environment. Returns a 'flattened' + version of ourselves. + """ + c = self.__class__(self.label) + c._collapse_child(self, env) + + for cond, child in self.conditionals.items(): + if _evaluate_condition(cond, env): + c._collapse_child(child, env) + + return c + + +class VSProjectItem: + """ A VS project item, like a source code file. """ + def __init__(self, include, itemtype=None): + self.include = include + self.itemtype = itemtype + self.metadata = {} + + def resolve(self, env): + c = VSProjectItem(_resolve_value(self.include), self.itemtype) + c.metadata = {k: _resolve_value(v, env) + for k, v in self.metadata.items()} + return c + + def __str__(self): + return "(%s)%s" % (self.itemtype, self.include) + + +class VSProjectItemGroup(VSBaseGroup): + """ A VS project item group, like a list of source code files, + or a list of resources. + """ + def __init__(self, label): + super().__init__(label) + self.items = [] + + def get_source_items(self): + for i in self.items: + if i.itemtype in ITEM_TYPE_SOURCE_FILES: + yield i + + def _collapse_child(self, child, env): + self.items += [i.resolve(env) for i in child.items] + + +class VSProjectProperty: + """ A VS project property, like an include path or compiler flag. """ + def __init__(self, name, value): + self.name = name + self.value = value + + def resolve(self, env): + c = VSProjectProperty(self.name, _resolve_value(self.value, env)) + return c + + def __str__(self): + return "%s=%s" % (self.name, self.value) + + +class VSProjectPropertyGroup(VSBaseGroup): + """ A VS project property group, such as compiler macros or flags. """ + def __init__(self, label): + super().__init__(label) + self.properties = [] + + def get(self, propname): + try: + return self[propname] + except IndexError: + return None + + def __getitem__(self, propname): + for p in self.properties: + if p.name == propname: + return p.value + raise IndexError() + + def _collapse_child(self, child, env): + self.properties += [p.resolve(env) for p in child.properties] + + +class VSProject: + """ A VS project. """ + def __init__(self, projtype, name, path, guid): + self.type = projtype + self.name = name + self.path = path + self.guid = guid + self._itemgroups = None + self._propgroups = None + self._sln = None + + @property + def is_folder(self): + """ Returns whether this project is actually just a solution + folder, used as a container for other projects. + """ + return self.type == PROJ_TYPE_FOLDER + + @property + def abspath(self): + abspath = self.path + if self._sln and self._sln.path: + abspath = os.path.join(self._sln.dirpath, self.path) + return abspath + + @property + def absdirpath(self): + return os.path.dirname(self.abspath) + + @property + def itemgroups(self): + self._ensure_loaded() + return self._itemgroups.values() + + @property + def propertygroups(self): + self._ensure_loaded() + return self._propgroups.values() + + def itemgroup(self, label, resolved_with=None): + self._ensure_loaded() + ig = self._itemgroups.get(label) + if resolved_with is not None and ig is not None: + logger.debug("Resolving item group '%s'." % ig.label) + ig = ig.resolve(resolved_with) + return ig + + def defaultitemgroup(self, resolved_with=None): + return self.itemgroup(None, resolved_with=resolved_with) + + def propertygroup(self, label, resolved_with=None): + self._ensure_loaded() + pg = self._propgroups.get(label) + if resolved_with is not None and pg is not None: + logger.debug("Resolving property group '%s'." % pg.label) + pg = pg.resolve(resolved_with) + return pg + + def defaultpropertygroup(self, resolved_with=None): + return self.propertygroup(None, resolved_with=resolved_with) + + def get_abs_item_include(self, item): + return os.path.abspath(os.path.join(self.absdirpath, item.include)) + + def resolve(self, env): + self._ensure_loaded() + + propgroups = list(self._propgroups) + itemgroups = list(self._itemgroups) + self._propgroups[:] = [] + self._itemgroups[:] = [] + + for pg in propgroups: + rpg = pg.resolve(env) + self._propgroups.append(rpg) + + for ig in itemgroups: + rig = ig.resolve(env) + self._itemgroups.append(rig) + + def _ensure_loaded(self): + if self._itemgroups is None or self._propgroups is None: + self._load() + + def _load(self): + if not self.path: + raise Exception("The current project has no path.") + if self.is_folder: + logger.debug(f"Skipping folder project {self.name}") + self._itemgroups = {} + self._propgroups = {} + return + + ns = {'ms': 'http://schemas.microsoft.com/developer/msbuild/2003'} + + abspath = self.abspath + logger.debug(f"Loading project {self.name} ({self.path}) from: {abspath}") + tree = etree.parse(abspath) + root = tree.getroot() + if _strip_ns(root.tag) != 'Project': + raise Exception(f"Expected root node 'Project', got '{root.tag}'") + + self._itemgroups = {} + for itemgroupnode in root.iterfind('ms:ItemGroup', ns): + label = itemgroupnode.attrib.get('Label') + itemgroup = self._itemgroups.get(label) + if not itemgroup: + itemgroup = VSProjectItemGroup(label) + self._itemgroups[label] = itemgroup + logger.debug(f"Adding itemgroup '{label}'") + + condition = itemgroupnode.attrib.get('Condition') + if condition: + itemgroup = itemgroup.get_or_create_conditional(condition) + + for itemnode in itemgroupnode: + incval = itemnode.attrib.get('Include') + item = VSProjectItem(incval, _strip_ns(itemnode.tag)) + itemgroup.items.append(item) + for metanode in itemnode: + item.metadata[_strip_ns(metanode.tag)] = metanode.text + + self._propgroups = {} + for propgroupnode in root.iterfind('ms:PropertyGroup', ns): + label = propgroupnode.attrib.get('Label') + propgroup = self._propgroups.get(label) + if not propgroup: + propgroup = VSProjectPropertyGroup(label) + self._propgroups[label] = propgroup + logger.debug(f"Adding propertygroup '{label}'") + + condition = propgroupnode.attrib.get('Condition') + if condition: + propgroup = propgroup.get_or_create_conditional(condition) + + for propnode in propgroupnode: + propgroup.properties.append(VSProjectProperty( + _strip_ns(propnode.tag), + propnode.text)) + + +class MissingVSProjectError(Exception): + pass + + +class VSGlobalSectionEntry: + """ An entry in a VS solution's global section. """ + def __init__(self, name, value): + self.name = name + self.value = value + + +class VSGlobalSection: + """ A global section in a VS solution. """ + def __init__(self, name): + self.name = name + self.entries = [] + + +class VSSolution: + """ A VS solution. """ + def __init__(self, path=None): + self.path = path + self.projects = [] + self.sections = [] + + @property + def dirpath(self): + return os.path.dirname(self.path) + + def find_project_by_name(self, name, missing_ok=True): + for p in self.projects: + if p.name == name: + return p + if missing_ok: + return None + return MissingVSProjectError(f"Can't find project with name: {name}") + + def find_project_by_path(self, path, missing_ok=True): + for p in self.projects: + if p.abspath == path: + return p + if missing_ok: + return None + raise MissingVSProjectError(f"Can't find project with path: {path}") + + def find_project_by_guid(self, guid, missing_ok=True): + for p in self.projects: + if p.guid == guid: + return p + if missing_ok: + return None + raise MissingVSProjectError(f"Can't find project for guid: {guid}") + + def globalsection(self, name): + for sec in self.sections: + if sec.name == name: + return sec + return None + + def find_project_configuration(self, proj_guid, sln_config): + configs = self.globalsection('ProjectConfigurationPlatforms') + if not configs: + return None + + entry_name = '{%s}.%s.Build.0' % (proj_guid, sln_config) + for entry in configs.entries: + if entry.name == entry_name: + return entry.value + return None + + +_re_sln_project_decl_start = re.compile( + r'^Project\("\{(?P<type>[A-Z0-9\-]+)\}"\) \= ' + r'"(?P<name>[^"]+)", "(?P<path>[^"]+)", "\{(?P<guid>[A-Z0-9\-]+)\}"$') +_re_sln_project_decl_end = re.compile( + r'^EndProject$') + +_re_sln_global_start = re.compile(r'^Global$') +_re_sln_global_end = re.compile(r'^EndGlobal$') +_re_sln_global_section_start = re.compile( + r'^\s*GlobalSection\((?P<name>\w+)\) \= (?P<step>\w+)$') +_re_sln_global_section_end = re.compile(r'^\s*EndGlobalSection$') + + +def parse_sln_file(slnpath): + """ Parses a solution file, returns a solution object. + The projects are not loaded (they will be lazily loaded upon + first access to their items/properties/etc.). + """ + logging.debug(f"Reading {slnpath}") + slnobj = VSSolution(slnpath) + with open(slnpath, 'r') as fp: + lines = fp.readlines() + _parse_sln_file_text(slnobj, lines) + return slnobj + + +def _parse_sln_file_text(slnobj, lines): + until = None + in_global = False + in_global_section = None + + for i, line in enumerate(lines): + if until: + # We need to parse something until a given token, so let's + # do that and ignore everything else. + m = until.search(line) + if m: + until = None + continue + + if in_global: + # We're in the 'global' part of the solution. It should contain + # a bunch of 'global sections' that we need to parse individually. + if in_global_section: + # Keep parsing the current section until we reach the end. + m = _re_sln_global_section_end.search(line) + if m: + in_global_section = None + continue + + ename, evalue = line.strip().split('=') + in_global_section.entries.append(VSGlobalSectionEntry( + ename.strip(), + evalue.strip())) + continue + + m = _re_sln_global_section_start.search(line) + if m: + # Found the start of a new section. + in_global_section = VSGlobalSection(m.group('name')) + logging.debug(f" Adding global section {in_global_section.name}") + slnobj.sections.append(in_global_section) + continue + + m = _re_sln_global_end.search(line) + if m: + # Found the end of the 'global' part. + in_global = False + continue + + # We're not in a specific part of the solution, so do high-level + # parsing. First, ignore root-level comments. + if not line or line[0] == '#': + continue + + m = _re_sln_project_decl_start.search(line) + if m: + # Found the start of a project declaration. + try: + p = VSProject( + m.group('type'), m.group('name'), m.group('path'), + m.group('guid')) + except: + raise Exception(f"Error line {i}: unexpected project syntax.") + logging.debug(f" Adding project {p.name}") + slnobj.projects.append(p) + p._sln = slnobj + + until = _re_sln_project_decl_end + continue + + m = _re_sln_global_start.search(line) + if m: + # Reached the start of the 'global' part, where global sections + # are defined. + in_global = True + continue + + # Ignore the rest (like visual studio version flags). + continue + + +class SolutionCache: + """ A class that contains a VS solution object, along with pre-indexed + lists of items. It's meant to be saved on disk. + """ + VERSION = 3 + + def __init__(self, slnobj): + self.slnobj = slnobj + self.index = None + + def build_cache(self): + self.index = {} + for proj in self.slnobj.projects: + if proj.is_folder: + continue + itemgroup = proj.defaultitemgroup() + if not itemgroup: + continue + + item_cache = set() + self.index[proj.abspath] = item_cache + + for item in itemgroup.get_source_items(): + item_path = proj.get_abs_item_include(item).lower() + item_cache.add(item_path) + + def save(self, path): + pathdir = os.path.dirname(path) + if not os.path.exists(pathdir): + os.makedirs(pathdir) + with open(path, 'wb') as fp: + pickle.dump(self, fp) + + @staticmethod + def load_or_rebuild(slnpath, cachepath): + if cachepath: + res = _try_load_from_cache(slnpath, cachepath) + if res is not None: + return res + + slnobj = parse_sln_file(slnpath) + cache = SolutionCache(slnobj) + + if cachepath: + logger.debug(f"Regenerating cache: {cachepath}") + cache.build_cache() + cache.save(cachepath) + + return (cache, False) + + +def _try_load_from_cache(slnpath, cachepath): + try: + sln_dt = os.path.getmtime(slnpath) + cache_dt = os.path.getmtime(cachepath) + except OSError: + logger.debug("Can't read solution or cache files.") + return None + + # If the solution file is newer, bail out. + if sln_dt >= cache_dt: + logger.debug("Solution is newer than cache.") + return None + + # Our cache is at least valid for the solution stuff. Some of our + # projects might be out of date, but at least there can't be any + # added or removed projects from the solution (otherwise the solution + # file would have been touched). Let's load the cache. + with open(cachepath, 'rb') as fp: + cache = pickle.load(fp) + + # Check that the cache version is up-to-date with this code. + loaded_ver = getattr(cache, 'VERSION') + if loaded_ver != SolutionCache.VERSION: + logger.debug(f"Cache was saved with older format: {cachepath}") + return None + + slnobj = cache.slnobj + + # Check that none of the project files in the solution are newer + # than this cache. + proj_dts = [] + for p in slnobj.projects: + if not p.is_folder: + try: + proj_dts.append(os.path.getmtime(p.abspath)) + except OSError: + logger.debug(f"Found missing project: {p.abspath}") + return None + + if all([cache_dt > pdt for pdt in proj_dts]): + logger.debug(f"Cache is up to date: {cachepath}") + return (cache, True) + + logger.debug("Cache has outdated projects.") + return None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/ycm_extra_conf.py Tue Sep 17 13:24:24 2019 -0700 @@ -0,0 +1,215 @@ +import argparse +import logging +import os.path +import sys + + +if True: # 'vim' in sys.modules: + sys.path.append(os.path.dirname(__file__)) + + +from logutil import setup_logging +from vsutil import SolutionCache + + +logger = logging.getLogger(__name__) + + +def _build_cflags(filename, solution, buildenv=None, slncache=None): + # Load the solution. + if not solution: + raise Exception( + "No solution path was provided in the client data!") + + cache, loaded = SolutionCache.load_or_rebuild(solution, slncache) + if not loaded: + cache.build_cache() + + # Find the current file in the solution. + filename_lower = filename.lower() + projpath = None + for pp, pi in cache.index.items(): + if filename_lower in pi: + projpath = pp + break + else: + raise Exception("File doesn't belong to the solution: %s" % filename) + + # Find the project that our file belongs to. + proj = cache.slnobj.find_project_by_path(projpath) + if not proj: + raise Exception("Can't find project in solution: %s" % projpath) + logger.debug("Found project %s: %s" % (proj.name, proj.abspath)) + + # Get the provided config/platform combo, which represent a solution + # configuration, and find the corresponding project configuration. + # For instance, a solution configuration of "Debug|Win64" could map + # to a "MyDebug|AnyCPU" configuration on a specific project. + sln_config_platform = '%s|%s' % (buildenv['Configuration'], + buildenv['Platform']) + proj_config_platform = cache.slnobj.find_project_configuration( + proj.guid, sln_config_platform) + if not proj_config_platform: + raise Exception("Can't find project configuration and platform for " + "solution configuration and platform: %s" % + sln_config_platform) + + # Make a build environment for the project, and figure out what + # kind of project it is. + proj_config, proj_platform = proj_config_platform.split('|') + + proj_buildenv = buildenv.copy() + proj_buildenv['Configuration'] = proj_config + proj_buildenv['Platform'] = proj_platform + + cfggroup = proj.propertygroup('Configuration', proj_buildenv) + cfgtype = cfggroup.get('ConfigurationType') + if not cfgtype: + raise Exception("Can't find configuration type. Did you specify a " + "configuration name and platform? Got: %s" % + proj_buildenv) + logger.debug("Found configuration type: %s" % cfgtype) + + # Let's prepare a list of standard stuff for C++. + preproc = [] + incpaths = [] + projdir = os.path.dirname(proj.abspath) + + if cfgtype == 'Makefile': + # It's a 'Makefile' project, which means we know as little about + # compiler flags as whatever information was given to VS. As + # such, if the solution setup doesn't give enough info, VS + # intellisense won't work, and neither will YouCompleteMe. + defaultpropgroup = proj.defaultpropertygroup(proj_buildenv) + + nmake_preproc = defaultpropgroup.get('NMakePreprocessorDefinitions') + preproc += nmake_preproc.strip(';').split(';') + + nmake_incpaths = defaultpropgroup.get('NMakeIncludeSearchPath') + incpaths += [os.path.abspath(os.path.join(projdir, p)) + for p in nmake_incpaths.strip(';').split(';')] + + else: + # We should definitely support standard VC++ projects here but + # I don't need it yet :) + raise Exception("Don't know how to handle configuration type: %s" % + cfgtype) + + # Build the clang/YCM flags with what we found. + flags = ['-x', 'c++'] # TODO: check language type from project file. + + for symbol in preproc: + flags.append('-D%s' % symbol) + for path in incpaths: + if path.startswith("C:\\Program Files"): + flags.append('-isystem') + else: + flags.append('-I') + flags.append(path) + + return {'flags': flags} + + +def _build_env_from_vim(client_data): + buildenv = {} + buildenv['Configuration'] = client_data.get('g:vimcrosoft_current_config', '') + buildenv['Platform'] = client_data.get('g:vimcrosoft_current_platform', '') + return buildenv + + +def Settings(**kwargs): + language = kwargs.get('language') + filename = kwargs.get('filename') + + client_data = kwargs.get('client_data', {}) + from_cli = kwargs.get('from_cli', False) + if from_cli: + solution = client_data.get('solution') + slncache = client_data.get('slncache') + buildenv = client_data.get('env', {}) + else: + solution = client_data.get('g:vimcrosoft_current_sln') + slncache = client_data.get('g:vimcrosoft_current_sln_cache') + buildenv = _build_env_from_vim(client_data) + + flags = None + + if language == 'cfamily': + try: + flags = _build_cflags(filename, solution, + buildenv=buildenv, slncache=slncache) + except Exception as exc: + if from_cli: + raise + flags = {'error': str(exc)} + else: + flags = {'error': f"Unknown language: {language}"} + + with open("D:\\P4\\DevEditor\\debug.txt", 'w') as fp: + fp.write("kwargs:") + fp.write(str(kwargs)) + fp.write("client_data:") + fp.write(str(list(kwargs['client_data'].items()))) + fp.write("flags:") + fp.write(str(flags)) + return flags + + +languages = { + 'cfamily': ['h', 'c', 'hpp', 'cpp', 'inl'] +} + + +def _get_language(filename): + _, ext = os.path.splitext(filename) + ext = ext.lstrip('.') + for lang, exts in languages.items(): + if ext in exts: + return lang + return None + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('solution', + help="The solution file") + parser.add_argument('filename', + help="The filename for which to get flags") + parser.add_argument('-p', '--property', + action="append", + help="Specifies a build property") + parser.add_argument('-c', '--cache', + help="The solution cache to use") + parser.add_argument('-v', '--verbose', + action='store_true', + help="Show debugging information") + args = parser.parse_args() + setup_logging(args.verbose) + + lang = _get_language(args.filename) + logger.debug(f"Got language {lang} for {args.filename}") + + build_env = {} + if args.property: + for p in args.property: + pname, pval = p.split('=', 1) + build_env[pname] = pval + logger.debug(f"Got build environment: {build_env}") + client_data = {'solution': args.solution, + 'slncache': args.cache, + 'env': build_env} + + params = {'from_cli': True, + 'language': lang, + 'filename': args.filename, + 'client_data': client_data + } + flags = Settings(**params) + logger.info("Flags:") + import pprint + pp = pprint.PrettyPrinter(indent=2) + pp.pprint(flags) + + +if __name__ == '__main__': + main()