Getting C# Autocomplete on Windows Emacs
I recently started getting back into working in the Godot Engine some and I wanted to use C# in it, so of course the first step of this was to try and make Emacs a usable development environment for this.
The kind of autocomplete that is desirable for working in C# is something like the IntelliSense that Microsoft offers in their Visual Studio products, as it allows your editor to understand your project’s structure in a manner that gives you useful tools such as auto completion, linting, error checking, etc. This functionality can be added to Emacs by installing a package for interfacing with a Language Server Protocol (LSP) server, which runs as a separate program Emacs communicates with by LSP, a JSON-RPC protocol.
Now for Emacs, there are two popular LSP server packages available for this purpose: lsp-mode and eglot. I chose to go with Eglot just because it has become built-into Emacs recently in version 29. I did also try the omnisharp-emacs package briefly and found it was very convenient to set up, but because it has been depreciated and eglot is a much more generalized LSP solution I didn’t stick with it.
So after setting up Eglot to use omnisharp-roslyn as an LSP server with the following configuration:
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
`(csharp-mode . ,(eglot-alternatives
'(("~/programs/omnisharp/winx64net6_0/OmniSharp.exe" "-lsp")
)))))
And then updating my csharp-mode
config to enable it:
(use-package csharp-mode
:hook
(csharp-mode . eglot-ensure))
I found that it functioned perfectly, but that triggering the autocompletion menu to open caused it to lag immensely. Looking online, I gathered that others have had similar experiences on Windows. This wasn’t terribly surprising, since in the past I had briefly tested similar C# autocomplete with a Unity project and had a similar experience.
But for my purposes, constantly having the autocompletion menu popup was not a priority, so I could simply turn off the idle auto completion popup for my auto-completion package, company-mode
while in C#, then adding manual binding to perform autocompletion:
(use-package company
:bind
("C-." . company-complete))
(use-package csharp-mode
:hook
(
(csharp-mode . eglot-ensure)
(csharp-mode . (lambda ()
(setq-local company-idle-delay nil)))))
While I was looking into finding ways to speed this up further, I came across eglot-ignored-server-capabilities
, which allows Eglot’s various functions to be disabled, so I set it up to disable various features I didn’t have any need of in the hopes that this will further boost performance:
(use-package csharp-mode
:hook
(
(csharp-mode . eglot-ensure)
(csharp-mode . (lambda ()
(setq-local company-idle-delay nil)
(setq-local eglot-ignored-server-capabilities
`(
;; :hoverProvider ;Documentation on hover
;; :completionProvider ;Code completion
;; :signatureHelpProvider ;Function signature help (Gives arguments when inside parentheses of function)
;; :definitionProvider ;Go to definition
;; :typeDefinitionProvider ;Go to type definition
;; :implementationProvider ;Go to implementation
;; :declarationProvider ;Go to declaration
;; :referencesProvider ;Find references
:documentHighlightProvider ;Highlight symbols automatically
:documentSymbolProvider ;List symbols in buffer
:workspaceSymbolProvider ;List symbols in workspace
:codeActionProvider ;Execute code actions
:codeLensProvider ;Code lens
:documentFormattingProvider ;Format buffer
:documentRangeFormattingProvider ;Format portion of buffer
:documentOnTypeFormattingProvider ;On-type formatting
;; :renameProvider ;Rename symbol
:documentLinkProvider ;Highlight links in document
:colorProvider ;Decorate color references
:foldingRangeProvider ;Fold regions of buffer
:executeCommandProvider ;Execute custom commands
:inlayHintProvider ;Inlay hints
))))))
After applying these configuration changes, I found the development environment very usable. But because I started missing basic non-manual autocomplete features, I turned to use the auto-complete package alongside my company-mode
autocompletion (whether it is a cardinal sin to have multiple auto-completion packages installed in an Emacs config, I do not know) and now I use it for non-manual auto-completion in C# buffers. This allowed for words within the buffer to be quickly auto-completed, such as variable and class names. But I lacked keyword completion, and I couldn’t find a ac-source
package for including these, so I put together and installed a small package called auto-complete-csharp to provide them:
(use-package auto-complete-csharp
:straight (
:host github
:repo "Zacalot/auto-complete-csharp")
:hook
(csharp-mode . (lambda ()
(setq ac-sources '(ac-source-csharp
ac-source-abbrev
ac-source-words-in-same-mode-buffers))
(auto-complete-mode))))
So now with all this done, I finally have a usable configuration for fast C# completion in Emacs on Windows with the only downside being that I have to press C-.
to get intelligent autocompletion. It’d be nice if there was a way to speed up eglot+omnisharp on Windows for C#, but I haven’t been able to come across any. I’ll definitely update this if I ever come across a better solution.