Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OmniSharpBuild commands #386

Open
nickspoons opened this issue Jul 26, 2018 · 14 comments
Open

Add OmniSharpBuild commands #386

nickspoons opened this issue Jul 26, 2018 · 14 comments

Comments

@nickspoons
Copy link
Member

nickspoons commented Jul 26, 2018

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!

@nosami
Copy link
Contributor

nosami commented Jul 26, 2018

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

@nickspoons
Copy link
Member Author

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!

@nickspoons
Copy link
Member Author

nickspoons commented Sep 4, 2018

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

@Confuset
Copy link

Confuset commented Jul 5, 2019

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

@rene-descartes2021
Copy link
Contributor

rene-descartes2021 commented Sep 18, 2021

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().

@nickspoons
Copy link
Member Author

nickspoons commented Sep 18, 2021

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.

@rene-descartes2021
Copy link
Contributor

rene-descartes2021 commented Sep 21, 2021

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.

@nickspoons
Copy link
Member Author

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.

@nickspoons
Copy link
Member Author

@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:

  1. Your version was creating a list of project dicts ({'configurations': [{'project1': {...}}, {'project2': {...}}]}), instead of a single dict with projects as properties ({'configurations': {'project1': {...}, 'project2': {...}}})
  2. The stopAtEntry value was being converted to a string, instead of a boolean
  3. The program is simplified to use the absolute path, which vimspector seems happy with
  4. 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!

@rene-descartes2021
Copy link
Contributor

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.

@nickspoons
Copy link
Member Author

@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.

@rene-descartes2021
Copy link
Contributor

rene-descartes2021 commented May 13, 2022

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.

@rene-descartes2021
Copy link
Contributor

rene-descartes2021 commented May 18, 2022

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 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.

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.

@nickspoons
Copy link
Member Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants