joaotavora / eglot

A client for Language Server Protocol servers
GNU General Public License v3.0
2.26k stars 200 forks source link

Support for Omnisharp (C#) #241

Closed sebasmonia closed 5 years ago

sebasmonia commented 5 years ago

There's already Omnisharp Emacs. The server used by it also supports LSP-mode, if Eglot supported that server I would consolidate all my LSP needs into this package :)

Also, awesome job. I really like Eglot's minimalism.

joaotavora commented 5 years ago

Eglot already "supports" that server if you can get running by launching a command inside a project that you wish to work on. Is it the case with Omnisharp? If so, it's very easy to set it up in eglot-server-programs. Otherwise it's a bit harder. So if you can get me that command-line I can teach you how to set it up.

Also, awesome job. I really like Eglot's minimalism.

Thanks @sebasmonia, let's see if I can find a bit more time to work on it.

sebasmonia commented 5 years ago
C:\Home\omnisharp_64
λ OmniSharp.exe -?
Usage:  [options]

Options:
  -? | -h | --help           Show help information
  -s | --source              Solution or directory for OmniSharp to point at (defaults to current directory).
  -l | --loglevel            Level of logging (defaults to 'Information').
  -v | --verbose             Explicitly set 'Debug' log level.
  -hpid | --hostPID          Host process ID.
  -z | --zero-based-indices  Use zero based indices in request/responses (defaults to 'false').
  -pl | --plugin             Plugin name(s).
  -d | --debug               Wait for debugger to attach
  -stdio | --stdio           Use STDIO over HTTP as OmniSharp communication protocol.
  -lsp | --languageserver    Use Language Server Protocol.
  -e | --encoding            Input / output encoding for STDIO protocol.

So it would seem that we need to find the solution root before we can launch the server. Then pass -lsp and we should be set?

(add-to-list 'eglot-server-programs
             `(csharp-mode . ("c:/path/to/OmniSharp.exe" "-stdio" "-lsp")))
joaotavora commented 5 years ago

So it would seem that we need to find the solution root before we can launch the server.

Why? It says the "directory for Omnisharp to point at" defaults to the current directory, which is just what we want.

(add-to-list 'eglot-server-programs `(csharp-mode . ("c:/path/to/OmniSharp.exe" "-stdio" "-lsp")))

Looks good, did you try it?

sebasmonia commented 5 years ago

Will test it in a few, thanks for validating it.

Usually with .cs files you want a root directory that contains a solution/csproj file. Maybe rather than hooking to csharp-mode I should M-x eglot while in the root folder?

sebasmonia commented 5 years ago

If I call it manually with the path of the solution file it starts the server but eventually get this:

[jsonrpc] Server exited with status 9
Error running timer: (error "[eglot] Timed out")
sebasmonia commented 5 years ago

I tried the LSP Python server from Microsoft and got something very similar. I will test both using http, that might do the trick since its async by nature, we might avoid the timeout.

sebasmonia commented 5 years ago

No dice. I noticed the omnisharp server uses ^M in each line terminator. Could that cause problems?

joaotavora commented 5 years ago

@sebasmonia, I don't think that's the problem. BUt I don't have time to investigate right now. You should post the full contents of the relevant eglot *events* buffer in the meanwhile.

joranvar commented 5 years ago

Might this issue be related? In the *output* buffer, OmniSharp logs that it sends a "started" event, but it's not shown in the *events* buffer. I'm not sure how to inspect the raw output from the server to verify this.

joaotavora commented 5 years ago

@joranvar, seems likely. To inspect the "raw" output from the server, you have to trace functions in Eglot's core, but to inspect its stderr, you can switch to a buffer named *EGLOT (<project-name>/<mode>) stderr*.

The function you have to trace for stdout output is

(defun jsonrpc--process-filter (proc string)
  "Called when new data STRING has arrived for PROC."
...

You can trace it with M-x trace-function RET jsonrpc--process-filter RET. Ouput will go to the *trace-output* buffer.

joranvar commented 5 years ago

Thanks! I didn't know about trace-function, could have used that before :smile:

The output from omnisharp-roslyn (version 1.32.8, which I'm using) contains nothing like that from (e.g.) haskell-ide-engine, no "method" keys or "jsonrpc". Seems like the output is far from LSP-compliant. But I see there's another omnisharp, so the aforementioned issue might not be the one that @sebasmonia has.

sebasmonia commented 5 years ago

Did you start it with the - lsp flag?

joranvar commented 5 years ago

Yes, but that does seem to have no effect.

joranvar commented 5 years ago

But it does have effect in omnisharp-roslyn version 1.32.11, where "protocolVersion" has been corrected to "jsonrpc" as well.

joranvar commented 5 years ago

In that version, there is a difference between "-stdio" "-lsp" and "-lsp" "-stdio". The latter outputs in jsonrpc format. Maybe that helps you, @sebasmonia. It still throws an exception in the OmniSharp.Extensions.LanguageServer.Server.ClientCapabilityProvider.GetStaticOptions somehow, but you seem to be using Windows, whereas I'm on GNU/Linux (NixOS), so ymmv.

sebasmonia commented 5 years ago

Great find @joranvar !

This time I got (:message "Connection state changed" :change "unknown signal\n") in the events buffer. Will test some more later if I can.

sebasmonia commented 5 years ago

Full events:

client-request (id:1) Fri Mar 22 10:48:08 2019: (:jsonrpc "2.0" :id 1 :method "initialize" :params (:processId 128336 :rootPath "c:/repos/playready/" :rootUri "file:///c:/repos/playready/" :initializationOptions nil :capabilities (:workspace (:applyEdit t :executeCommand (:dynamicRegistration :json-false) :workspaceEdit (:documentChanges :json-false) :didChangeWatchedFiles (:dynamicRegistration t) :symbol (:dynamicRegistration :json-false)) :textDocument (:synchronization (:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t) :completion (:dynamicRegistration :json-false :completionItem (:snippetSupport :json-false) :contextSupport t) :hover (:dynamicRegistration :json-false) :signatureHelp (:dynamicRegistration :json-false :signatureInformation (:parameterInformation (:labelOffsetSupport t))) :references (:dynamicRegistration :json-false) :definition (:dynamicRegistration :json-false) :documentSymbol (:dynamicRegistration :json-false :symbolKind (:valueSet [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26])) :documentHighlight (:dynamicRegistration :json-false) :codeAction (:dynamicRegistration :json-false :codeActionLiteralSupport (:codeActionKind (:valueSet ["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))) :formatting (:dynamicRegistration :json-false) :rangeFormatting (:dynamicRegistration :json-false) :rename (:dynamicRegistration :json-false) :publishDiagnostics (:relatedInformation :json-false)) :experimental nil)))

internal Fri Mar 22 10:48:38 2019: (:message "Connection state changed" :change "unknown signal\n")

----------b---y---e---b---y---e----------

joranvar commented 5 years ago

@sebasmonia Can you see what happens in the trace, before that message? It might be something that omnisharp sends.

sebasmonia commented 5 years ago

I just tested again, nothing happened at all, and got again a timeout message. Thanks for keeping the help coming :)

ilohmar commented 5 years ago

This is the current problem, as far as I can tell (just tested): https://github.com/OmniSharp/omnisharp-roslyn/issues/1403

Some capabilities seem to not be recognized by the server, triggering an exception. Seems due to the server side, not to eglot.

joranvar commented 5 years ago

I can confirm this. If I change the (cl-defgeneric eglot-client-capabilities (server) to comment out the :synchronization element, the connection to omnisharp succeeds.

sebasmonia commented 5 years ago

Is there any adverse effect of removing :synchronization ?

Kethku commented 5 years ago

I'm running into this as well with a custom language server implementation. Any hints as to what is going on?

Edit: I figured out my particular issue was on the side of my language server implementation. So ignore this.

tom-bowles commented 5 years ago

This commit, in a nuget dependency of omnisharp-roslyn, appears to solve this: https://github.com/OmniSharp/csharp-language-server-protocol/commit/21907ad42794282d9794881bce2f93aae7e96442.

sebasmonia commented 5 years ago

Tested with today's code which fixes the bug mentioned above, no luck =/

sebasmonia commented 5 years ago

~Tried again, still not working. It seems a lot of people trying to use LSP are having issues. There's one more C# LSP server but hasn't had updates in a while, might try it later:~

~https://github.com/CXuesong/LanguageServer.NET~

I got the latest Omnisharp server working, with the following changes:

(add-to-list 'eglot-server-programs
             `(csharp-mode . ("c:/path/to/OmniSharp.exe" "-lsp")))

No -stdio, and no -s path/to/solution

Then sometimes I get an error in eglot--format-markup, in some calls the server returns null which breaks the function. I hid the problem by adding setting the function's parameter to an empty string when nil.

sebasmonia commented 5 years ago

For reference, my current config:

(use-package eglot
  :commands (eglot eglot-ensure)
  :hook ((python-mode . eglot-ensure)
         (csharp-mode . eglot-ensure))
  :config
  (progn
    (define-key eglot-mode-map (kbd "C-c e r") 'eglot-rename)
    (define-key eglot-mode-map (kbd "C-c e f") 'eglot-format)
    (define-key eglot-mode-map (kbd "C-c e h") 'eglot-help-at-point)
    (add-to-list 'eglot-server-programs
                 `(csharp-mode . ("C:/Home/omnisharp_64/OmniSharp.exe" "-lsp")))))

thank you everyone who helped me get this working!

vermiculus commented 1 year ago

I'm seeing the same issue today (timeout after 30s), but on macOS. Would you prefer a new issue or do you think eglot is stable enough that the problem is most likely with omnisharp?

joaotavora commented 1 year ago

Make a GitHub issue if -- and only if -- you can provide all the elements for said issue, as requested in the template, else don't, and start a discussion instead. See here for what to include in the report. You can also send issue reports to bug-gnu-emacs@gnu.org.

vermiculus commented 1 year ago

Ah, thank you! I was able to get this stack trace for myself. I'll investigate more and hopefully get this resolved on my end without further help. (I'll try to edit resources into this response when I have them for future readers.) Thanks again!!

Internal Error - System.TypeLoadException: Could not load type of field 'OmniSharp.MSBuild.Notification.ProjectLoadedEventArgs:<ProjectInstance>k__BackingField' (2) due to: Could not load file or assembly 'Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.
  at (wrapper managed-to-native) System.RuntimeType.GetPropertiesByName_native(System.RuntimeType,intptr,System.Reflection.BindingFlags,System.RuntimeType/MemberListType)
  at System.RuntimeType.GetPropertiesByName (System.String name, System.Reflection.BindingFlags bindingAttr, System.RuntimeType+MemberListType listType, System.RuntimeType reflectedType) [0x0001b] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.RuntimeType.GetPropertyCandidates (System.String name, System.Reflection.BindingFlags bindingAttr, System.Type[] types, System.Boolean allowPrefixLookup) [0x00010] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.RuntimeType.GetProperties (System.Reflection.BindingFlags bindingAttr) [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.Reflection.RuntimeReflectionExtensions.GetRuntimeProperties (System.Type type) [0x00014] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.Composition.TypedParts.Discovery.TypeInspector+<DiscoverPropertyExports>d__7.MoveNext () [0x00030] in <9aac21a4a3f24063b2b8b59143acddfc>:0 
  at System.Composition.TypedParts.Discovery.TypeInspector+<DiscoverExports>d__5.MoveNext () [0x000dc] in <9aac21a4a3f24063b2b8b59143acddfc>:0 
  at System.Composition.TypedParts.Discovery.TypeInspector.InspectTypeForPart (System.Reflection.TypeInfo type, System.Composition.TypedParts.Discovery.DiscoveredPart& part) [0x00061] in <9aac21a4a3f24063b2b8b59143acddfc>:0 
  at System.Composition.TypedParts.TypedPartExportDescriptorProvider..ctor (System.Collections.Generic.IEnumerable`1[T] types, System.Composition.Convention.AttributedModelProvider attributeContext) [0x00037] in <9aac21a4a3f24063b2b8b59143acddfc>:0 
  at System.Composition.Hosting.ContainerConfiguration.CreateContainer () [0x00042] in <9aac21a4a3f24063b2b8b59143acddfc>:0 
  at OmniSharp.CompositionHostBuilder.Build () [0x00243] in <0d216eab8720499ba5d3870bfa4b8299>:0 
  at OmniSharp.LanguageServerProtocol.LanguageServerHost.CreateCompositionHost (OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeParams initializeParams) [0x00124] in <55c25305b0e8407384c9efef4eb09cc1>:0 
  at OmniSharp.LanguageServerProtocol.LanguageServerHost.Initialize (OmniSharp.Extensions.LanguageServer.Server.ILanguageServer server, OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeParams initializeParams) [0x00000] in <55c25305b0e8407384c9efef4eb09cc1>:0 
  at OmniSharp.Extensions.LanguageServer.Server.LanguageServer+<>c__DisplayClass55_0.<MediatR.IRequestHandler<OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeParams,OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeResult>.Handle>b__0 (OmniSharp.Extensions.LanguageServer.Server.InitializeDelegate c) [0x00000] in <72d5c645fd274cab83fd1f12f360a92e>:0 
  at System.Linq.Enumerable+SelectListIterator`2[TSource,TResult].MoveNext () [0x00048] in <af7da3dfa75342efa3e3326915d316d2>:0 
  at System.Threading.Tasks.Task.WhenAll (System.Collections.Generic.IEnumerable`1[T] tasks) [0x000ba] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at OmniSharp.Extensions.LanguageServer.Server.LanguageServer.MediatR.IRequestHandler<OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeParams,OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeResult>.Handle (OmniSharp.Extensions.LanguageServer.Protocol.Models.InitializeParams request, System.Threading.CancellationToken token) [0x001f4] in <72d5c645fd274cab83fd1f12f360a92e>:0 
  at OmniSharp.Extensions.LanguageServer.Server.Pipelines.ResolveCommandPipeline`2[TRequest,TResponse].Handle (TRequest request, System.Threading.CancellationToken cancellationToken, MediatR.RequestHandlerDelegate`1[TResponse] next) [0x000e1] in <72d5c645fd274cab83fd1f12f360a92e>:0 
  at MediatR.Pipeline.RequestPostProcessorBehavior`2[TRequest,TResponse].Handle (TRequest request, System.Threading.CancellationToken cancellationToken, MediatR.RequestHandlerDelegate`1[TResponse] next) [0x0007e] in <eabe42abe9454fa58bdac29bc6aabb6a>:0 
  at MediatR.Pipeline.RequestPreProcessorBehavior`2[TRequest,TResponse].Handle (TRequest request, System.Threading.CancellationToken cancellationToken, MediatR.RequestHandlerDelegate`1[TResponse] next) [0x0013e] in <eabe42abe9454fa58bdac29bc6aabb6a>:0 
  at OmniSharp.Extensions.JsonRpc.RequestRouterBase`1[TDescriptor].RouteRequest (TDescriptor descriptor, OmniSharp.Extensions.JsonRpc.Server.Request request, System.Threading.CancellationToken token) [0x00399] in <da46d4e8076c414ebe949c0004330da2>:0