forked from emacs-lsp/lsp-mode
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lsp-csharp.el
263 lines (221 loc) · 11.2 KB
/
lsp-csharp.el
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
;;; lsp-csharp.el --- description -*- lexical-binding: t; -*-
;; Copyright (C) 2019 Jostein Kjønigsen, Saulius Menkevicius
;; Author: Saulius Menkevicius <[email protected]>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; lsp-csharp client
;;; Code:
(require 'lsp-mode)
(require 'gnutls)
(require 'f)
(defgroup lsp-csharp nil
"LSP support for C#, using the Omnisharp Language Server.
Version 1.34.3 minimum is required."
:group 'lsp-mode
:link '(url-link "https://github.com/OmniSharp/omnisharp-roslyn"))
(defcustom lsp-csharp-server-install-dir
(f-join lsp-server-install-dir "omnisharp-roslyn/")
"Installation directory for OmniSharp Roslyn server."
:group 'lsp-csharp
:type 'directory)
(defcustom lsp-csharp-server-path
nil
"The path to the OmniSharp Roslyn language-server binary.
Set this if you have the binary installed or have it built yourself."
:group 'lsp-csharp
:type '(string :tag "Single string value or nil"))
(defun lsp-csharp--version-list-latest (lst)
(->> lst
(-sort (lambda (a b) (not (version<= (substring a 1)
(substring b 1)))))
cl-first))
(defun lsp-csharp--latest-installed-version ()
"Returns latest version of the server installed on the machine (if any)."
(lsp-csharp--version-list-latest
(when (f-dir? lsp-csharp-server-install-dir)
(seq-filter
(lambda (f) (s-starts-with-p "v" f))
(seq-map 'f-filename (f-entries lsp-csharp-server-install-dir))))))
(defun lsp-csharp--fetch-json (url)
"Retrieves and parses JSON from URL."
(with-temp-buffer
(url-insert-file-contents url)
(let ((json-false :false))
(json-read))))
(defun lsp-csharp--latest-available-version ()
"Returns latest version of the server available from github."
(lsp-csharp--version-list-latest
(seq-map (lambda (elt) (s-trim (cdr (assq 'name elt))))
(lsp-csharp--fetch-json "https://api.github.com/repos/OmniSharp/omnisharp-roslyn/releases"))))
(defun lsp-csharp--server-dir (version)
"The location of the installed OmniSharp server for VERSION."
(when version
(f-join (expand-file-name lsp-csharp-server-install-dir) version)))
(defun lsp-csharp--server-bin (version)
"The location of OmniSharp executable/script to use to start the server."
(let ((server-dir (lsp-csharp--server-dir version)))
(when server-dir
(f-join server-dir (cond ((eq system-type 'windows-nt) "OmniSharp.exe")
(t "run"))))))
(defun lsp-csharp--server-package-filename ()
"Returns name of tgz/zip file to be used for downloading the server
for auto installation.
On Windows we're trying to avoid a crash starting 64bit .NET PE binaries in
Emacs by using x86 version of omnisharp-roslyn on older (<= 26.4) versions
of Emacs. See https://lists.nongnu.org/archive/html/bug-gnu-emacs/2017-06/msg00893.html"
(cond ((eq system-type 'windows-nt)
(if (and (string-match "^x86_64-.*" system-configuration)
(version<= "26.4" emacs-version))
"omnisharp-win-x64.zip"
"omnisharp-win-x86.zip"))
((eq system-type 'darwin)
"omnisharp-osx.tar.gz")
((and (eq system-type 'gnu/linux)
(or (eq (string-match "^x86_64" system-configuration) 0)
(eq (string-match "^i[3-6]86" system-configuration) 0)))
"omnisharp-linux-x64.tar.gz")
(t "omnisharp-mono.tar.gz")))
(defun lsp-csharp--server-package-url (version)
"Returns URL to tgz/zip file to be used for downloading the server VERSION
for installation."
(concat "https://github.com/OmniSharp/omnisharp-roslyn/releases/download"
"/" version
"/" (lsp-csharp--server-package-filename)))
(defun lsp-csharp--extract-server (url filename reinstall)
"Downloads and extracts a tgz/zip into the same directory."
;; remove the file if reinstall is set
(if (and reinstall (f-exists-p filename))
(f-delete filename))
(lsp-csharp--download url filename)
(let ((target-dir (f-dirname filename)))
(message (format "lsp-csharp: extracting \"%s\" to \"%s\""
(f-filename filename)
target-dir))
(lsp-csharp--extract filename target-dir)))
(defun lsp-csharp-update-server ()
"Checks if the currently installed version (if any) is lower than then one
available on github and if so, downloads and installs a newer version."
(interactive)
(let ((latest-version (lsp-csharp--latest-available-version))
(installed-version (lsp-csharp--latest-installed-version)))
(if latest-version
(progn
(if (and latest-version
(or (not installed-version)
(version< (substring installed-version 1)
(substring latest-version 1))))
(lsp-csharp--install-server latest-version nil))
(message "lsp-csharp-update-server: latest installed version is %s; latest available is %s"
(lsp-csharp--latest-installed-version)
latest-version))
(message "lsp-csharp-update-server: cannot retrieve latest version info"))))
(defun lsp-csharp--install-server (update-version ask-confirmation)
"Installs (or updates to UPDATE-VERSION) server binary unless it is already installed."
(let ((installed-version (lsp-csharp--latest-installed-version))
(target-version (or update-version (lsp-csharp--latest-available-version))))
(if (and target-version
(not (string-equal installed-version target-version)))
(progn
(message "lsp-csharp-update-server: current version is %s; installing %s.."
(or installed-version "(none)")
target-version)
(if (or (not ask-confirmation)
(yes-or-no-p (format "OmniSharp Roslyn Server %s. Do you want to download and install %s now?"
(if installed-version
(format "can be updated, currently installed version is %s" installed-version)
"is not installed")
target-version)))
(let ((new-server-dir (lsp-csharp--server-dir target-version))
(new-server-bin (lsp-csharp--server-bin target-version))
(package-filename (lsp-csharp--server-package-filename))
(package-url (lsp-csharp--server-package-url target-version)))
(mkdir new-server-dir t)
(lsp-csharp--extract-server package-url
(f-join new-server-dir package-filename)
nil)
(unless (and new-server-bin (file-exists-p new-server-bin))
(error "Failed to auto-install the server %s; file \"%s\" was not found"
target-version new-server-bin))))))))
(defun lsp-csharp--get-or-install-server ()
"Resolves path to server binary installed, otherwise, if not found
will ask the user if we can download and install it.
Returns location of script or a binary to use to start the server."
(let ((installed-bin (lsp-csharp--server-bin (lsp-csharp--latest-installed-version))))
(if (and installed-bin (file-exists-p installed-bin))
installed-bin
(progn
(lsp-csharp--install-server nil t)
(let ((installed-bin (lsp-csharp--server-bin (lsp-csharp--latest-installed-version))))
(unless installed-bin
(error "Server binary is required for LSP C# to work."))
installed-bin)))))
(defun lsp-csharp--download (url filename)
"Downloads file from URL as FILENAME. Will not do anything should
the file exist already."
(unless (f-exists-p filename)
(message (format "lsp-csharp: downloading from \"%s\"..." url))
(let ((gnutls-algorithm-priority
(if (and (not gnutls-algorithm-priority)
(boundp 'libgnutls-version)
(>= libgnutls-version 30603)
(version<= emacs-version "26.2"))
"NORMAL:-VERS-TLS1.3"
gnutls-algorithm-priority)))
(url-copy-file url filename nil))))
(defun lsp-csharp--extract (filename target-dir)
"Extracts FILENAME which is a downloaded omnisharp-roslyn server
tarball or a zip file (based on a current platform) to TARGET-DIR."
(cond
((eq system-type 'windows-nt)
;; on windows, we attempt to use powershell v5+, available on Windows 10+
(let ((powershell-version (substring
(shell-command-to-string "powershell -command \"(Get-Host).Version.Major\"")
0 -1)))
(if (>= (string-to-number powershell-version) 5)
(call-process "powershell"
nil
nil
nil
"-command"
(concat "add-type -assembly system.io.compression.filesystem;"
"[io.compression.zipfile]::ExtractToDirectory(\"" filename "\", \"" target-dir "\")"))
(message (concat "lsp-csharp: for automatic server installation procedure"
" to work on Windows you need to have powershell v5+ installed")))))
((or (eq system-type 'gnu/linux)
(eq system-type 'darwin))
(call-process "tar" nil nil t "xf" filename "-C" target-dir))
(t (error "lsp-csharp cannot extract \"%s\" on platform %s (yet)" filename system-type))))
(defun lsp-csharp--language-server-command ()
"Resolves path and arguments to use to start the server.
Will attempt to install the server if it is not installed already for the
current platform."
(if lsp-csharp-server-path
(list lsp-csharp-server-path "-lsp")
(list (lsp-csharp--server-bin (lsp-csharp--latest-installed-version)) "-lsp")))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection
#'lsp-csharp--language-server-command
(lambda ()
(when-let (binary (lsp-csharp--server-bin (lsp-csharp--latest-installed-version)))
(f-exists? binary))))
:major-modes '(csharp-mode)
:server-id 'csharp
:download-server-fn
(lambda (_client callback error-callback _update?)
(condition-case err
(progn
(lsp-csharp--install-server nil nil)
(funcall callback))
(error (funcall error-callback (error-message-string err)))))))
(provide 'lsp-csharp)
;;; lsp-csharp.el ends here