diff 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
line wrap: on
line diff
--- /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
+
+" }}}