A note on getting Gnu Emacs working with Omnisharp-Roslyn LSP server

After wasting a bit of time the last few days, I figured out how to get the Gnu Emacs editor to work with the Omnisharp-Roslyn LSP server for C#. Finding the right solution required a lot of trial and error work because I work mainly on Windows, and that is completely sacrilegious.

First off: Why Emacs? Sorry, but I grew up with Emacs and vi, starting in the early ’80s. Emacs was decent, and it still is. It comes in handy on the command line for a GUI-less Linux server.

There are two Gnu Emacs clients. One is written by OmniSharp before LSP existed, at https://github.com/OmniSharp/omnisharp-emacs . This client works fine, but the short instructions at http://www.omnisharp.net/#integrations are completely bogus. The other is https://github.com/emacs-lsp/lsp-mode/ . This is what I use below. Like the instructions for omnisharp-emacs, the instructions here are terrible. But, it does work. All operations, like “go to definition” are defined as functions that begin with “lsp-“, available through the M-x command, e.g., M-x lsp-find-references.

  1. Download Gnu Emacs from https://www.gnu.org/software/emacs/download.html , specifically the latest at http://ftp.wayne.edu/gnu/emacs/windows/ . Do not install MSYS2, run pacman as per instructions. While I could get MSYS2 working, the paths are all messed up due to filtering out of Windows binaries, to the point of being useless. In addition, I tried to use the Bash LSP server, but that required npm to be installed–which there isn’t in MSYS2 (though in Node.js, etc, etc, etc). Again, do not go down this road.
  2. Clone the repository https://github.com/OmniSharp/omnisharp-roslyn . Start VS2019, open the file OmniSharp.sln, then build the executable, and make a note of the full path to OmniSharp.exe.
  3. Create an init.el file for Emacs. It should be at c:/Users/etcetcetc/AppData/Roaming/.emacs.d/init.el. You can check the location by opening Gnu Emacs, then in the start-up screen, click on “Open Home Directory”. This file should contain this:
(require 'package)
 (let* ((no-ssl (and (memq system-type '(windows-nt ms-dos))
                     (not (gnutls-available-p))))
        (proto (if no-ssl "http" "https")))
   (when no-ssl
     (warn "\
 Your version of Emacs does not support SSL connections,
 which is unsafe because it allows man-in-the-middle attacks.
 There are two things you can do about this warning:
 Install an Emacs version that does support SSL and be safe.
 Remove this warning from your init file so you won't see it again."))
 ;; Comment/uncomment these two lines to enable/disable MELPA and MELPA Stable as desired
 (add-to-list 'package-archives (cons "melpa" (concat proto "://melpa.org/packages/")) t)
 ;;(add-to-list 'package-archives (cons "melpa-stable" (concat proto "://stable.melpa.org/packages/")) t)
 (when (< emacs-major-version 24)
 ;; For important compatibility libraries like cl-lib
 (add-to-list 'package-archives (cons "gnu" (concat proto "://elpa.gnu.org/packages/")))))
 (package-initialize) 
 ;; Added by Package.el.  This must come before configurations of
 ;; installed packages.  Don't delete this line.  If you don't want it,
 ;; just comment it out by adding a semicolon to the start of the line.
 ;; You may delete these explanatory comments.
 (package-initialize)
 ;;(custom-set-variables
  ;; custom-set-variables was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
 ;; '(package-selected-packages (quote (omnisharp lsp-mode))))
 (custom-set-faces
  ;; custom-set-faces was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
  '(default ((t (:family "Courier New" :foundry "outline" :slant normal :weight normal :height 161 :width normal)))))
 (package-refresh-contents)
 (package-install 'flycheck)
 (global-flycheck-mode)
 (require 'lsp-mode)
 (add-hook 'csharp-mode-hook #'lsp)
 ;;(package-install 'omnisharp)
 ;;(add-hook 'csharp-mode-hook 'omnisharp-mode)
 ;;
 ;;
 (defcustom lsp-clients-csharp-language-server-path
   (expand-file-name "c:/users/etcetcetc/documents/omnisharp-roslyn/bin/debug/omnisharp.stdio.driver/net472/OmniSharp.exe")
   "The path to the OmnisSharp Roslyn language-server."
   :group 'lsp-csharp
   :type '(string :tag "Single string value"))
 (custom-set-variables
  ;; custom-set-variables was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
  '(package-selected-packages (quote (flycheck omnisharp lsp-mode))))

You will need to change the location of the Omnisharp-roslyn server executable which you built in step 2 in this init.el file.

Start Gnu Emacs and open a C# file and follow the instructions. All operations begin with M-x lsp-.

Posted in Tip