omnisharp-vim
omnisharp-vim copied to clipboard
Add OmniSharpBuild commands
During the recent refactorings, we have removed support for the legacy server, OmniSharp-server. As part of this, the :OmniSharpBuild
commands have also been removed - OmniSharp-roslyn does not have /build
endpoints so these leftovers have been confusing to have lying around.
We can also now have multiple servers running simultaneously, and the old :OmniSharpBuild
implementation would not have been able to handle this situation.
Now we need to add some nice build functionality back in. I don't think this needs to involve the server - in my opinion the simplest version of this can be a call to :make
, with a sensible 'makeprg'
and 'errorformat'
, which builds the solution associated with the currently active file: OmniSharp#FindSolution()
It would be nice to detect whether the correct 'makeprg'
should use msbuild
, xbuild
or dotnet build
but I'm not sure if that's something we sensibly can detect, or should just use a g:
/b:
variable.
This should make use of vim8/neovim jobs, with the same dispatch
/vimplug
fallbacks as we use to run the server.
Alternatively, we can simply provide sensible mappings in the README
and :help
, and suggest how users can use ':make' or integrate with e.g. AsyncRun or AsyncDo
Any ideas or PRs welcome!
The build command was added so that I could SSH to machines and build remotely. It was also useful back in the day for working on OSX but building on a Windows VM. I was possibly the only user who ever did this :)
I guess the same thing would also have been possible using msbuild
over SSH.
xbuild
is deprecated now and should never be used. msbuild
is on the $PATH and should be used on linux and OSX since around Mono 5.0
Good to know, thanks! I do think it'll be very useful to have an easy integration with the multiple servers we have now. I suspect I'll start with a personal config and merge it in here when it works well ... unless someone else beats me to it!
I'm still not sure what the best way to incorporate Building into OmniSharp-vim is, but for anyone interested, this is how I currently do it, using msbuild
and AsyncDo
function! MakeSolution() abort
let makeprg = 'msbuild /nologo /v:q /property:GenerateFullPaths=true /clp:ErrorsOnly '
let sln = fnamemodify(OmniSharp#FindSolutionOrDir(), ':.')
echomsg makeprg . sln
call asyncdo#run(1, makeprg . sln)
endfunction
Then in ~/.vim/ftplugin/cs.vim
add something like:
nnoremap <silent> <buffer> <Space>mk :call MakeSolution()<CR>
Edit 2019-01-31: Updated OmniSharp#FindSolution()
to OmniSharp#FindSolutionOrDir()
to reflect recent updates
That is how I did it:
I added ~/.vim/compiler/msbuild.vim
if exists("current_compiler")
finish
endif
let current_compiler = "msbuild"
let $PATH.=';'.shellescape('C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\').';'
setlocal makeprg=msbuild\ /nologo\ /v:q\ /property:GenerateFullPaths=true\ /clp:ErrorsOnly
setlocal errorformat=\ %#%f(%l\\\,%c):\ %m
Then in ~/.vim/ftplugin/cs.vim
compiler msbuild
Then I added this to ~/.vim/vimrc
but this probably also belongs into the cs.vim
file (but I do not yet really understand that ftplugin thing ;-) )
function! MakeSolution() abort
let sln = shellescape(fnamemodify(OmniSharp#FindSolutionOrDir(), ':.'))
call feedkeys(':make' . sln)
endfunction
nnoremap <silent> <buffer> <Space>mk :call MakeSolution()<CR>
" automatically open quickfix window after build is completed
autocmd QuickFixCmdPost [^l]* nested cwindow
autocmd QuickFixCmdPost l* nested lwindow
The neat thing about this is that the quickfix window popups with the builderrors and you can jump to the location using the quickfix list.
You could also define different compilers and switch these simply by using :compiler otherCompiler
I needed a way to build a specific .csproj and not every project in the .sln so I wrote this. I'm no Vim or C# wizard so..., feel free to refine.
You type :Make
and <tab>
and it autocompletes to a .csproj in the search space using the :help wildmenu
feature of Vim. The makeprg is done async using any plugin which defines asyncdo#run().
That's a nice writeup, @rene-descartes2021. If you want to only include projects which are part of the current solution in your tab-completion function, OmniSharp-vim already has a list of them:
let host = OmniSharp#GetHost().job
let projects = OmniSharp#proc#GetJob(host.sln_or_dir).projects
It's actually a good idea to add command completion functions for the running servers, and the projects in those servers, to OmniSharp-vim. That would allow custom commands for building, publishing, nuget restoring, testing etc.
Edit
Oh I just remembered I already added a completion function for tab-completing running servers, used with :OmniSharpStopServer
😅
OmniSharp#CompleteRunningSln
Adding OmniSharp#CompleteRunningProjects
(all projects in all running solutions) and OmniSharp#CompleteRunningProjectsCurrentSln
(only projects from the current solution) should be pretty simple.
If you want to only include projects which are part of the current solution in your tab-completion function, OmniSharp-vim already has a list of them
Ok I revised the writeup to include that. Falling back on a recursive search if OmniSharp-vim server hadn't loaded yet. Probably a better way to do things than I did.
That would allow custom commands for building, publishing, nuget restoring, testing etc.
My attention now is on debugger integration. Vimspector appears to be the best option. It needs a debug configuration, which appears to be a .vimspector.json file in the root of the project. Example for configuration for C# (which points to the built executable e.g. ${workspaceRoot}/bin/Debug/netcoreapp2.2/csharp.dll
). Looks like there could either be a 1:1 relationship between these .vimspector.json
files and built .csproj
files, or perhaps each .csproj
's debug configuration written into a single .vimspector.json
file in the .sln
folder which I think would be better at a glance. At present the .vimspector.json
file is found by recursively searching up the directory hierarchy from the directory of the file open in Vim, on a call to call vimspector#LaunchWithSettings( #{ configuration: 'name here' } )
.
The .vimspector.json
could be populated on build by parsing elements like <OutputPath>
and <OutputType>
from the .csproj
, -o argument from dotnet build
, or maybe query Omnisharp-vim for equivalent info? I don't know what is possible to query from Omnisharp, so that's why I write here.
My interest in this is I'm writing a C# layer for a distributable vim configuration. But I guess this proposed debugger integration could instead go into your nickspoons/vim-sharpenup.
Just trying to wrap my mind around the best way to make things convenient for the end-user.
OmniSharp-vim doesn't cache all of the data it receives from OmniSharp-roslyn. If there are server values we would like to be able to access from OmniSharp-vim, we can add values to that OmniSharp#GetHost().job.projects
dictionary. Currently it only contains this:
{
"name": "ProjectName",
"path": "/full/path/to/Project.csproj"
}
However we could add e.g. 'target', which we do get from OmniSharp-roslyn:
{
"name": "ProjectName",
"path": "/full/path/to/Project/Project.csproj",
"target": "/full/path/to/Project/bin/Debug/net5.0/Project.dll"
}
Then you could use that target to generate a .vimspector.json
. This would just require adding the new value from the TargetPath
server response property to the dictionary here.
To see what we get from the server, configure OmniSharp-vim to use debug-level logging (let g:OmniSharp_loglevel = 'debug'
), and then check the :OmniSharpOpenLog
for JSON server responses.
@rene-descartes2021 I've given your example in the wiki a go, and it works well after tweaking a bit. I won't edit it myself but here's my version of the function:
function! WriteVimspectorConfig() abort
let projects = OmniSharp#GetHost().job.projects
let config = { 'configurations': {} }
for project in projects
let config.configurations[project['name']] = {
\ 'adapter': 'netcoredbg',
\ 'configuration': {
\ 'request': 'launch',
\ 'program': project['target'],
\ 'args': [],
\ 'stopAtEntry': v:true
\ }
\}
endfor
let sln_or_dir = OmniSharp#GetHost().sln_or_dir
let slndir = sln_or_dir =~? '\.sln$' ? fnamemodify(sln_or_dir, ':h') : sln_or_dir
let filename = slndir . s:dir_separator . '.vimspector.json'
call writefile([json_encode(config)], filename)
endfunction
The differences are:
- Your version was creating a list of project dicts (
{'configurations': [{'project1': {...}}, {'project2': {...}}]}
), instead of a single dict with projects as properties ({'configurations': {'project1': {...}, 'project2': {...}}}
) - The
stopAtEntry
value was being converted to a string, instead of a boolean - The
program
is simplified to use the absolute path, which vimspector seems happy with - The
.vimspector.json
file is written to the .sln root location, rather than the working directory (I often work from a higher directory, with multiple solutions open in a session).
It's a really useful little snippet, added to my config, thanks!
Whoops, yes I was hung up on how to get it to complete when called on OmniSharpReady, then got sidetracked. Maybe calling it manually is best. I since found issue #96. Glad things work.
@rene-descartes2021 have a look at #734. @jpfeiffer16 has integrated Vimspector with OmniSharp-vim for debugging tests, it's excellent. And debugging tests is much more complicated than debugging executables, so it should be pretty simple to integrate debugging non-test projects.
I see the new :OmniSharpDebugProject
and :OmniSharpCreateDebugConfig
commands, looks wonderful! That ad-hoc Vimspector config for debugger integration is a great feature.
I managed to get :Make
to autocomplete to the .csproj associated with the .cs file using cached 'project' data from OmniSharp-Rosyln. I updated the wiki with how to do that.
So now I'm looking at updating the C# CompilerSet in Vim. The one presently in Vim/Neovim uses csc
, and it looks to me like that frontend is no longer exposed with modern .NET, so compiler/cs.vim
should use dotnet
as the frontend instead of csc
?
I also noticed the mcs CompilerSet, for Mono C#, which curiously doesn't set the makeprg.
EDIT: I now see that there are msbuild and xbuild CompilerSets. Hmm. I presume those CompilerSets should be removed, the cs CompilerSet should use dotnet
, and the mcs CompilerSet should use logic to wrap xbuild
/msbuild
. Allowing for simpler logic for setting the default compiler in ftplugin/cs.vim? Then again... I suppose those CompilerSets may be fine as-is? No, I think change is needed, as at present there is no CompilerSet which calls dotnet
, which I imagine should be implicit for *.cs files so long as dotnet
is found in $PATH.
EDIT: There is the argument that the cs/mcs CompilerSets should be unchanged in calling csc/mcs to compile a *.cs file directly, for backwards-compatibility and if csc is exposed again. And a dotnet CompilerSet made, which is set implicitly if on $PATH, otherwise xbuild/msbuild if on $PATH. I suppose this makes the most sense all things considered.
The sheerun/vim-polyglot Vim plugin looks like a promising way to distribute the updated C# CompilerSet, EDIT: and implicitly setting the CompilerSet in ftplugin/cs.vim
/EDIT. The vim-polygot plugin being detached from a future specific Vim/Neovim release/version, thus the ability to quickly distribute to any Vim version. The updated C# CompilerSet can be upstreamed from vim-polyglot into Vim/Neovim, but, I don't know the best way to go about it.
What I have for my ~/.vim/compiler/cs.vim
(minus most boilerplate):
let current_compiler = "cs"
CompilerSet makeprg=dotnet\ build\ /v:q\ /property:GenerateFullPaths=true\ /clp:ErrorsOnly
CompilerSet errorformat=\ %#%f(%l\\\,%c):\ %m
I think the errorformat
above could possibly be better. e.g. Trim down the project name in %m from an absolute path to <basename>.csproj
, EDIT: or removing the project name altogether. I'll try to improve this.
It would be nice to detect whether the correct
'makeprg'
should usemsbuild
,xbuild
ordotnet build
but I'm not sure if that's something we sensibly can detect, or should just use ag:
/b:
variable.
xbuild
appears to be deprecated.
msbuild
not on my path (Debian bullseye, dotnet-sdk-6.0 package).
dotnet
is on my path
Thus it should be dotnet build
as the makeprg
, per my limited vantage point. EDIT: The current distribution CompilerSets don't have this.
Alternatively, we can simply provide sensible mappings in the
README
and:help
, and suggest how users can use ':make' or integrate with e.g. AsyncRun or AsyncDo
I have lines for both AsyncRun and AsyncDo in the wiki, using :Make
. With a way to cache the 'project' for the opened cs file. I couldn't figure out a way to do IPC to wait on the Omnisharp-Roslyn response with the 'project' data, so I decided to just cache it for each cs file on open.
Making a couple OmniSharp-vim functions to build a sln or csproj which assume a particular sln or csproj seems do-able. EDIT: Which would invoke the current CompilerSet via AsyncRun/AsyncDo, and a third OmniSharp-vim function which compiles the particular *.cs file, temporarily using the existing cs/msc CompilerSets, (even though the csc frontend might not work at present). /EDIT. More user friendly than the :Make in the wiki, which is a bit cluttered in the autocompletion when there are many possible csproj to build. I'll put something together.
The updated C# CompilerSet can be upstreamed from vim-polyglot into Vim/Neovim, but, I don't know the best way to go about it.
@rene-descartes2021 I actually maintain most of the C# runtime files for vim, over at https://github.com/nickspoons/vim-cs. This is currently just the ftplugin
, indent
and syntax
files, but it would make a lot of sense to add the compiler
there too. You're most welcome to make a PR, we can discuss further there and add some sensible vim defaults, and then I'll send it to Bram when we're happy.
Note that we'll need to try to contact the previous maintainers for their permission to take over maintainership, I don't expect they'll mind though, as the compilers haven't been touched for years.
Of course, all we can do there is set the compilers to dotnet build
/msbuild
, we can't reference OmniSharp.