Zacalot.XYZ

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.