config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

lsp-csharp.el (26681B)


      1 ;;; lsp-csharp.el --- description -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2019 Jostein Kjønigsen, Saulius Menkevicius
      4 
      5 ;; Author: Saulius Menkevicius <saulius.menkevicius@fastmail.com>
      6 ;; Keywords:
      7 
      8 ;; This program is free software; you can redistribute it and/or modify
      9 ;; it under the terms of the GNU General Public License as published by
     10 ;; the Free Software Foundation, either version 3 of the License, or
     11 ;; (at your option) any later version.
     12 
     13 ;; This program is distributed in the hope that it will be useful,
     14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16 ;; GNU General Public License for more details.
     17 
     18 ;; You should have received a copy of the GNU General Public License
     19 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     20 
     21 ;;; Commentary:
     22 
     23 ;; lsp-csharp client
     24 
     25 ;;; Code:
     26 
     27 (require 'lsp-mode)
     28 (require 'gnutls)
     29 (require 'f)
     30 
     31 (defgroup lsp-csharp nil
     32   "LSP support for C#, using the Omnisharp Language Server.
     33 Version 1.34.3 minimum is required."
     34   :group 'lsp-mode
     35   :link '(url-link "https://github.com/OmniSharp/omnisharp-roslyn"))
     36 
     37 (defgroup lsp-csharp-omnisharp nil
     38   "LSP support for C#, using the Omnisharp Language Server.
     39 Version 1.34.3 minimum is required."
     40   :group 'lsp-mode
     41   :link '(url-link "https://github.com/OmniSharp/omnisharp-roslyn")
     42   :package-version '(lsp-mode . "9.0.0"))
     43 
     44 (defconst lsp-csharp--omnisharp-metadata-uri-re
     45   "^file:///%24metadata%24/Project/\\(.+\\)/Assembly/\\(.+\\)/Symbol/\\(.+\\)\.cs$"
     46   "Regular expression matching omnisharp's metadata uri.
     47 Group 1 contains the Project name
     48 Group 2 contains the Assembly name
     49 Group 3 contains the Type name")
     50 
     51 (defcustom lsp-csharp-server-install-dir
     52   (f-join lsp-server-install-dir "omnisharp-roslyn/")
     53   "Installation directory for OmniSharp Roslyn server."
     54   :group 'lsp-csharp-omnisharp
     55   :type 'directory)
     56 
     57 (defcustom lsp-csharp-server-path
     58   nil
     59   "The path to the OmniSharp Roslyn language-server binary.
     60 Set this if you have the binary installed or have it built yourself."
     61   :group 'lsp-csharp-omnisharp
     62   :type '(string :tag "Single string value or nil"))
     63 
     64 (defcustom lsp-csharp-test-run-buffer-name
     65   "*lsp-csharp test run*"
     66   "The name of buffer used for outputting lsp-csharp test run results."
     67   :group 'lsp-csharp-omnisharp
     68   :type 'string)
     69 
     70 (defcustom lsp-csharp-solution-file
     71   nil
     72   "Solution to load when starting the server.
     73 Usually this is to be set in your .dir-locals.el on the project root directory."
     74   :group 'lsp-csharp-omnisharp
     75   :type 'string)
     76 
     77 (defcustom lsp-csharp-omnisharp-roslyn-download-url
     78   (concat "https://github.com/omnisharp/omnisharp-roslyn/releases/latest/download/"
     79           (cond ((eq system-type 'windows-nt)
     80                  ; On Windows we're trying to avoid a crash starting 64bit .NET PE binaries in
     81                  ; Emacs by using x86 version of omnisharp-roslyn on older (<= 26.4) versions
     82                  ; of Emacs. See https://lists.nongnu.org/archive/html/bug-gnu-emacs/2017-06/msg00893.html"
     83                  (if (and (string-match "^x86_64-.*" system-configuration)
     84                           (version<= "26.4" emacs-version))
     85                      "omnisharp-win-x64.zip"
     86                    "omnisharp-win-x86.zip"))
     87 
     88                 ((eq system-type 'darwin)
     89                  (if (string-match "aarch64-.*" system-configuration)
     90                      "omnisharp-osx-arm64-net6.0.zip"
     91                    "omnisharp-osx-x64-net6.0.zip"))
     92 
     93                 ((and (eq system-type 'gnu/linux)
     94                       (or (eq (string-match "^x86_64" system-configuration) 0)
     95                           (eq (string-match "^i[3-6]86" system-configuration) 0)))
     96                  "omnisharp-linux-x64-net6.0.zip")
     97 
     98                 (t "omnisharp-mono.zip")))
     99   "Automatic download url for omnisharp-roslyn."
    100   :group 'lsp-csharp-omnisharp
    101   :type 'string)
    102 
    103 (defcustom lsp-csharp-omnisharp-roslyn-store-path
    104   (f-join lsp-csharp-server-install-dir "latest" "omnisharp-roslyn.zip")
    105   "The path where omnisharp-roslyn .zip archive will be stored."
    106   :group 'lsp-csharp-omnisharp
    107   :type 'file)
    108 
    109 (defcustom lsp-csharp-omnisharp-roslyn-binary-path
    110   (f-join lsp-csharp-server-install-dir "latest" (if (eq system-type 'windows-nt)
    111                                                      "OmniSharp.exe"
    112                                                    "OmniSharp"))
    113   "The path where omnisharp-roslyn binary after will be stored."
    114   :group 'lsp-csharp-omnisharp
    115   :type 'file)
    116 
    117 (defcustom lsp-csharp-omnisharp-roslyn-server-dir
    118   (f-join lsp-csharp-server-install-dir "latest" "omnisharp-roslyn")
    119   "The path where omnisharp-roslyn .zip archive will be extracted."
    120   :group 'lsp-csharp-omnisharp
    121   :type 'file)
    122 
    123 
    124 (defcustom lsp-csharp-omnisharp-enable-decompilation-support
    125   nil
    126   "Decompile bytecode when browsing method metadata for types in assemblies.
    127 Otherwise only declarations for the methods are visible (the default)."
    128   :group 'lsp-csharp
    129   :type 'boolean)
    130 
    131 (defcustom lsp-csharp-csharpls-use-dotnet-tool t
    132   "Whether to use a dotnet tool version of the expected C#
    133  language server; only available for csharp-ls"
    134   :group 'lsp-csharp
    135   :type 'boolean
    136   :risky t)
    137 
    138 (defcustom lsp-csharp-csharpls-use-local-tool nil
    139   "Whether to use csharp-ls as a global or local dotnet tool.
    140 
    141 Note: this variable has no effect if
    142 lsp-csharp-csharpls-use-dotnet-tool is nil."
    143   :group 'lsp-csharp
    144   :type 'boolean
    145   :risky t)
    146 
    147 (lsp-dependency
    148  'omnisharp-roslyn
    149  `(:download :url lsp-csharp-omnisharp-roslyn-download-url
    150              :decompress :zip
    151              :store-path lsp-csharp-omnisharp-roslyn-store-path
    152              :binary-path lsp-csharp-omnisharp-roslyn-binary-path
    153              :set-executable? t)
    154  '(:system "OmniSharp"))
    155 
    156 (defun lsp-csharp--omnisharp-download-server (_client callback error-callback _update?)
    157   "Download zip package for omnisharp-roslyn and install it.
    158 Will invoke CALLBACK on success, ERROR-CALLBACK on error."
    159   (lsp-package-ensure 'omnisharp-roslyn callback error-callback))
    160 
    161 (defun lsp-csharp--language-server-path ()
    162   "Resolve path to use to start the server."
    163   (let ((executable-name (if (eq system-type 'windows-nt)
    164                              "OmniSharp.exe"
    165                            "OmniSharp")))
    166     (or (and lsp-csharp-server-path
    167              (executable-find lsp-csharp-server-path))
    168         (executable-find executable-name)
    169         (lsp-package-path 'omnisharp-roslyn))))
    170 
    171 (defun lsp-csharp-open-project-file ()
    172   "Open corresponding project file  (.csproj) for the current file."
    173   (interactive)
    174   (-let* ((project-info-req (lsp-make-omnisharp-project-information-request :file-name (buffer-file-name)))
    175           (project-info (lsp-request "o#/project" project-info-req))
    176           ((&omnisharp:ProjectInformation :ms-build-project) project-info)
    177           ((&omnisharp:MsBuildProject :path) ms-build-project))
    178     (find-file path)))
    179 
    180 (defun lsp-csharp--get-buffer-code-elements ()
    181   "Retrieve code structure by calling into the /v2/codestructure endpoint.
    182 Returns :elements from omnisharp:CodeStructureResponse."
    183   (-let* ((code-structure (lsp-request "o#/v2/codestructure"
    184                                        (lsp-make-omnisharp-code-structure-request :file-name (buffer-file-name))))
    185           ((&omnisharp:CodeStructureResponse :elements) code-structure))
    186     elements))
    187 
    188 (defun lsp-csharp--inspect-code-elements-recursively (fn elements)
    189   "Invoke FN for every omnisharp:CodeElement found recursively in ELEMENTS."
    190   (seq-each
    191    (lambda (el)
    192      (funcall fn el)
    193      (-let (((&omnisharp:CodeElement :children) el))
    194        (lsp-csharp--inspect-code-elements-recursively fn children)))
    195    elements))
    196 
    197 (defun lsp-csharp--collect-code-elements-recursively (predicate elements)
    198   "Flatten the omnisharp:CodeElement tree in ELEMENTS matching PREDICATE."
    199   (let ((results nil))
    200     (lsp-csharp--inspect-code-elements-recursively (lambda (el)
    201                                                      (when (funcall predicate el)
    202                                                        (setq results (cons el results))))
    203                                                    elements)
    204     results))
    205 
    206 (lsp-defun lsp-csharp--l-c-within-range (l c (&omnisharp:Range :start :end))
    207   "Determine if L (line) and C (column) are within RANGE."
    208   (-let* (((&omnisharp:Point :line start-l :column start-c) start)
    209           ((&omnisharp:Point :line end-l :column end-c) end))
    210     (or (and (= l start-l) (>= c start-c) (or (> end-l start-l) (<= c end-c)))
    211         (and (> l start-l) (< l end-l))
    212         (and (= l end-l) (<= c end-c)))))
    213 
    214 (defun lsp-csharp--code-element-stack-on-l-c (l c elements)
    215   "Return omnisharp:CodeElement stack at L (line) and C (column) in ELEMENTS tree."
    216   (when-let* ((matching-element (seq-find (lambda (el)
    217                                            (-when-let* (((&omnisharp:CodeElement :ranges) el)
    218                                                         ((&omnisharp:RangeList :full?) ranges))
    219                                              (lsp-csharp--l-c-within-range l c full?)))
    220                                          elements)))
    221     (-let (((&omnisharp:CodeElement :children) matching-element))
    222       (cons matching-element (lsp-csharp--code-element-stack-on-l-c l c children)))))
    223 
    224 (defun lsp-csharp--code-element-stack-at-point ()
    225   "Return omnisharp:CodeElement stack at point as a list."
    226   (let ((pos-line (plist-get (lsp--cur-position) :line))
    227         (pos-col (plist-get (lsp--cur-position) :character)))
    228     (lsp-csharp--code-element-stack-on-l-c pos-line
    229                                            pos-col
    230                                            (lsp-csharp--get-buffer-code-elements))))
    231 
    232 (lsp-defun lsp-csharp--code-element-test-method-p (element)
    233   "Return test method name and test framework for a given ELEMENT."
    234   (when element
    235     (-when-let* (((&omnisharp:CodeElement :properties) element)
    236                  ((&omnisharp:CodeElementProperties :test-method-name? :test-framework?) properties))
    237       (list test-method-name? test-framework?))))
    238 
    239 (defun lsp-csharp--reset-test-buffer (present-buffer)
    240   "Create new or reuse an existing test result output buffer.
    241 PRESENT-BUFFER will make the buffer be presented to the user."
    242   (with-current-buffer (get-buffer-create lsp-csharp-test-run-buffer-name)
    243     (compilation-mode)
    244     (read-only-mode)
    245     (let ((inhibit-read-only t))
    246       (erase-buffer)))
    247 
    248   (when present-buffer
    249     (display-buffer lsp-csharp-test-run-buffer-name)))
    250 
    251 (defun lsp-csharp--start-tests (test-method-framework test-method-names)
    252   "Run test(s) identified by TEST-METHOD-NAMES using TEST-METHOD-FRAMEWORK."
    253   (if (and test-method-framework test-method-names)
    254       (let ((request-message (lsp-make-omnisharp-run-tests-in-class-request
    255                               :file-name (buffer-file-name)
    256                               :test-frameworkname test-method-framework
    257                               :method-names (vconcat test-method-names))))
    258         (lsp-csharp--reset-test-buffer t)
    259         (lsp-session-set-metadata "last-test-method-framework" test-method-framework)
    260         (lsp-session-set-metadata "last-test-method-names" test-method-names)
    261         (lsp-request-async "o#/v2/runtestsinclass"
    262                            request-message
    263                            (-lambda ((&omnisharp:RunTestResponse))
    264                              (message "lsp-csharp: Test run has started"))))
    265     (message "lsp-csharp: No test methods to run")))
    266 
    267 (defun lsp-csharp--test-message (message)
    268   "Emit a MESSAGE to lsp-csharp test run buffer."
    269   (when-let* ((existing-buffer (get-buffer lsp-csharp-test-run-buffer-name))
    270              (inhibit-read-only t))
    271     (with-current-buffer existing-buffer
    272       (save-excursion
    273         (goto-char (point-max))
    274         (insert message "\n")))))
    275 
    276 (defun lsp-csharp-run-test-at-point ()
    277   "Start test run at current point (if any)."
    278   (interactive)
    279   (let* ((stack (lsp-csharp--code-element-stack-at-point))
    280          (element-on-point (car (last stack)))
    281          (test-method (lsp-csharp--code-element-test-method-p element-on-point))
    282          (test-method-name (car test-method))
    283          (test-method-framework (car (cdr test-method))))
    284     (lsp-csharp--start-tests test-method-framework (list test-method-name))))
    285 
    286 (defun lsp-csharp-run-all-tests-in-buffer ()
    287   "Run all test methods in the current buffer."
    288   (interactive)
    289   (let* ((elements (lsp-csharp--get-buffer-code-elements))
    290          (test-methods (lsp-csharp--collect-code-elements-recursively 'lsp-csharp--code-element-test-method-p elements))
    291          (test-method-framework (car (cdr (lsp-csharp--code-element-test-method-p (car test-methods)))))
    292          (test-method-names (mapcar (lambda (method)
    293                                       (car (lsp-csharp--code-element-test-method-p method)))
    294                                     test-methods)))
    295     (lsp-csharp--start-tests test-method-framework test-method-names)))
    296 
    297 (defun lsp-csharp-run-test-in-buffer ()
    298   "Run selected test in current buffer."
    299   (interactive)
    300   (when-let* ((elements (lsp-csharp--get-buffer-code-elements))
    301               (test-methods (lsp-csharp--collect-code-elements-recursively 'lsp-csharp--code-element-test-method-p elements))
    302               (test-method-framework (car (cdr (lsp-csharp--code-element-test-method-p (car test-methods)))))
    303               (test-method-names (mapcar (lambda (method)
    304                                            (car (lsp-csharp--code-element-test-method-p method)))
    305                                          test-methods))
    306               (selected-test-method-name (lsp--completing-read "Select test:" test-method-names 'identity)))
    307     (lsp-csharp--start-tests test-method-framework (list selected-test-method-name))))
    308 
    309 (defun lsp-csharp-run-last-tests ()
    310   "Re-run test(s) that were run last time."
    311   (interactive)
    312   (if-let* ((last-test-method-framework (lsp-session-get-metadata "last-test-method-framework"))
    313            (last-test-method-names (lsp-session-get-metadata "last-test-method-names")))
    314       (lsp-csharp--start-tests last-test-method-framework last-test-method-names)
    315     (message "lsp-csharp: No test method(s) found to be ran previously on this workspace")))
    316 
    317 (lsp-defun lsp-csharp--handle-os-error (_workspace (&omnisharp:ErrorMessage :file-name :text))
    318   "Handle the `o#/error' (interop) notification displaying a message."
    319   (lsp-warn "%s: %s" file-name text))
    320 
    321 (lsp-defun lsp-csharp--handle-os-testmessage (_workspace (&omnisharp:TestMessageEvent :message))
    322   "Handle the `o#/testmessage and display test message on test output buffer."
    323   (lsp-csharp--test-message message))
    324 
    325 (lsp-defun lsp-csharp--handle-os-testcompleted (_workspace (&omnisharp:DotNetTestResult
    326                                                             :method-name
    327                                                             :outcome
    328                                                             :error-message
    329                                                             :error-stack-trace
    330                                                             :standard-output
    331                                                             :standard-error))
    332   "Handle the `o#/testcompleted' message from the server.
    333 
    334 Will display the results of the test on the lsp-csharp test output buffer."
    335   (let ((passed (string-equal "passed" outcome)))
    336     (lsp-csharp--test-message
    337      (format "[%s] %s "
    338              (propertize (upcase outcome) 'font-lock-face (if passed 'success 'error))
    339              method-name))
    340 
    341     (unless passed
    342       (lsp-csharp--test-message error-message)
    343 
    344       (when error-stack-trace
    345         (lsp-csharp--test-message error-stack-trace))
    346 
    347       (unless (seq-empty-p standard-output)
    348         (lsp-csharp--test-message "STANDARD OUTPUT:")
    349         (seq-doseq (stdout-line standard-output)
    350           (lsp-csharp--test-message stdout-line)))
    351 
    352       (unless (seq-empty-p standard-error)
    353         (lsp-csharp--test-message "STANDARD ERROR:")
    354         (seq-doseq (stderr-line standard-error)
    355           (lsp-csharp--test-message stderr-line))))))
    356 
    357 (lsp-defun lsp-csharp--action-client-find-references ((&Command :arguments?))
    358   "Read first argument from ACTION as Location and display xrefs for that location
    359 using the `textDocument/references' request."
    360   (-if-let* (((&Location :uri :range) (lsp-seq-first arguments?))
    361              ((&Range :start range-start) range)
    362              (find-refs-params (append (lsp--text-document-position-params (list :uri uri) range-start)
    363                                        (list :context (list :includeDeclaration json-false))))
    364              (locations-found (lsp-request "textDocument/references" find-refs-params)))
    365       (lsp-show-xrefs (lsp--locations-to-xref-items locations-found) nil t)
    366     (message "No references found")))
    367 
    368 (defun lsp-csharp--omnisharp-path->qualified-name (path)
    369   "Convert PATH to qualified-namespace-like name."
    370   (replace-regexp-in-string
    371    (regexp-quote "/")
    372    "."
    373    path))
    374 
    375 (defun lsp-csharp--omnisharp-metadata-uri-handler (uri)
    376   "Handle `file:/(metadata)' URI from omnisharp-roslyn server.
    377 
    378 The URI is parsed and then `o#/metadata' request is issued to retrieve
    379 metadata from the server. A cache file is created on project root dir that
    380 stores this metadata and filename is returned so lsp-mode can display this file."
    381   (string-match lsp-csharp--omnisharp-metadata-uri-re uri)
    382   (-when-let* ((project-name (lsp-csharp--omnisharp-path->qualified-name (url-unhex-string (match-string 1 uri))))
    383                (assembly-name (lsp-csharp--omnisharp-path->qualified-name (url-unhex-string (match-string 2 uri))))
    384                (type-name (lsp-csharp--omnisharp-path->qualified-name (url-unhex-string (match-string 3 uri))))
    385                (metadata-req (lsp-make-omnisharp-metadata-request :project-name project-name
    386                                                                   :assembly-name assembly-name
    387                                                                   :type-name type-name))
    388                (metadata (lsp-request "o#/metadata" metadata-req))
    389                ((&omnisharp:MetadataResponse :source-name :source) metadata)
    390                (filename (f-join ".cache"
    391                                  "lsp-csharp"
    392                                  "metadata"
    393                                  "Project" project-name
    394                                  "Assembly" assembly-name
    395                                  "Symbol" (concat type-name ".cs")))
    396                (file-location (expand-file-name filename (lsp--suggest-project-root)))
    397                (metadata-file-location (concat file-location ".metadata-uri"))
    398                (path (f-dirname file-location)))
    399 
    400     (unless (find-buffer-visiting file-location)
    401       (unless (file-directory-p path)
    402         (make-directory path t))
    403 
    404       (with-temp-file metadata-file-location
    405         (insert uri))
    406 
    407       (with-temp-file file-location
    408         (insert source)))
    409 
    410     file-location))
    411 
    412 (defun lsp-csharp--omnisharp-uri->path-fn (uri)
    413   "Custom implementation of lsp--uri-to-path function to glue omnisharp's
    414 metadata uri."
    415   (if (string-match-p lsp-csharp--omnisharp-metadata-uri-re uri)
    416       (lsp-csharp--omnisharp-metadata-uri-handler uri)
    417     (lsp--uri-to-path-1 uri)))
    418 
    419 (defun lsp-csharp--omnisharp-environment-fn ()
    420   "Build environment structure for current values of lsp-csharp customizables.
    421 See https://github.com/OmniSharp/omnisharp-roslyn/wiki/Configuration-Options"
    422   `(("OMNISHARP_RoslynExtensionsOptions:enableDecompilationSupport" . ,(if lsp-csharp-omnisharp-enable-decompilation-support "true" "false"))))
    423 
    424 (lsp-register-client
    425  (make-lsp-client :new-connection
    426                   (lsp-stdio-connection
    427                    #'(lambda ()
    428                        (append
    429                         (list (lsp-csharp--language-server-path) "-lsp")
    430                         (when lsp-csharp-solution-file
    431                           (list "-s" (expand-file-name lsp-csharp-solution-file)))))
    432                    #'(lambda ()
    433                        (when-let* ((binary (lsp-csharp--language-server-path)))
    434                          (f-exists? binary))))
    435                   :activation-fn (lsp-activate-on "csharp")
    436                   :server-id 'omnisharp
    437                   :priority -1
    438                   :uri->path-fn #'lsp-csharp--omnisharp-uri->path-fn
    439                   :environment-fn #'lsp-csharp--omnisharp-environment-fn
    440                   :action-handlers (ht ("omnisharp/client/findReferences" 'lsp-csharp--action-client-find-references))
    441                   :notification-handlers (ht ("o#/projectadded" 'ignore)
    442                                              ("o#/projectchanged" 'ignore)
    443                                              ("o#/projectremoved" 'ignore)
    444                                              ("o#/packagerestorestarted" 'ignore)
    445                                              ("o#/msbuildprojectdiagnostics" 'ignore)
    446                                              ("o#/packagerestorefinished" 'ignore)
    447                                              ("o#/unresolveddependencies" 'ignore)
    448                                              ("o#/error" 'lsp-csharp--handle-os-error)
    449                                              ("o#/testmessage" 'lsp-csharp--handle-os-testmessage)
    450                                              ("o#/testcompleted" 'lsp-csharp--handle-os-testcompleted)
    451                                              ("o#/projectconfiguration" 'ignore)
    452                                              ("o#/projectdiagnosticstatus" 'ignore)
    453                                              ("o#/backgrounddiagnosticstatus" 'ignore))
    454                   :download-server-fn #'lsp-csharp--omnisharp-download-server))
    455 
    456 ;;
    457 ;; Alternative "csharp-ls" language server support
    458 ;; see https://github.com/razzmatazz/csharp-language-server
    459 ;;
    460 (lsp-defun lsp-csharp--cls-metadata-uri-handler (uri)
    461   "Handle `csharp:/(metadata)' uri from csharp-ls server.
    462 
    463 `csharp/metadata' request is issued to retrieve metadata from the server.
    464 A cache file is created on project root dir that stores this metadata and
    465 filename is returned so lsp-mode can display this file."
    466 
    467   (-when-let* ((metadata-req (lsp-make-csharp-ls-c-sharp-metadata
    468                               :text-document (lsp-make-text-document-identifier :uri uri)))
    469                (metadata (lsp-request "csharp/metadata" metadata-req))
    470                ((&csharp-ls:CSharpMetadataResponse :project-name
    471                                                    :assembly-name
    472                                                    :symbol-name
    473                                                    :source) metadata)
    474                (filename (f-join ".cache"
    475                                  "lsp-csharp"
    476                                  "metadata"
    477                                  "projects" project-name
    478                                  "assemblies" assembly-name
    479                                  (concat symbol-name ".cs")))
    480                (file-location (expand-file-name filename (lsp-workspace-root)))
    481                (metadata-file-location (concat file-location ".metadata-uri"))
    482                (path (f-dirname file-location)))
    483 
    484     (unless (file-exists-p file-location)
    485       (unless (file-directory-p path)
    486         (make-directory path t))
    487 
    488       (with-temp-file metadata-file-location
    489         (insert uri))
    490 
    491       (with-temp-file file-location
    492         (insert source)))
    493 
    494     file-location))
    495 
    496 (defun lsp-csharp--cls-before-file-open (_workspace)
    497   "Set `lsp-buffer-uri' variable after C# file is open from *.metadata-uri file."
    498 
    499   (let ((metadata-file-name (concat buffer-file-name ".metadata-uri")))
    500     (setq-local lsp-buffer-uri
    501                 (when (file-exists-p metadata-file-name)
    502                   (with-temp-buffer (insert-file-contents metadata-file-name)
    503                                     (buffer-string))))))
    504 
    505 (defun lsp-csharp--cls-find-executable ()
    506   (or (when lsp-csharp-csharpls-use-dotnet-tool
    507         (if lsp-csharp-csharpls-use-local-tool
    508             (list "dotnet" "tool" "run" "csharp-ls")
    509           (list "csharp-ls")))
    510       (executable-find "csharp-ls")      
    511       (f-join (or (getenv "USERPROFILE") (getenv "HOME"))
    512               ".dotnet" "tools" "csharp-ls")))
    513 
    514 (defun lsp-csharp--cls-make-launch-cmd ()
    515   "Return command line to invoke csharp-ls."
    516 
    517   ;; emacs-28.1 on macOS has an issue
    518   ;; that it launches processes using posix_spawn but does not reset sigmask properly
    519   ;; thus causing dotnet runtime to lockup awaiting a SIGCHLD signal that never comes
    520   ;; from subprocesses that quit
    521   ;;
    522   ;; as a workaround we will wrap csharp-ls invocation in "/bin/ksh -c" on macos
    523   ;; so it launches with proper sigmask
    524   ;;
    525   ;; see https://lists.gnu.org/archive/html/emacs-devel/2022-02/msg00461.html
    526 
    527   (let ((startup-wrapper (cond ((and (eq 'darwin system-type)
    528                                      (version= "28.1" emacs-version))
    529                                 (list "/bin/ksh" "-c"))
    530 
    531                                (t nil)))
    532 
    533         (csharp-ls-exec (lsp-csharp--cls-find-executable))
    534 
    535         (solution-file-params (when lsp-csharp-solution-file
    536                                 (list "-s" lsp-csharp-solution-file))))
    537     (append startup-wrapper
    538             (if (listp csharp-ls-exec)
    539                 csharp-ls-exec
    540               (list csharp-ls-exec))
    541             solution-file-params)))
    542 
    543 (defun lsp-csharp--cls-test-csharp-ls-present ()
    544   "Return non-nil if dotnet tool csharp-ls is installed as a dotnet tool."
    545   (string-match-p "csharp-ls"
    546                   (shell-command-to-string
    547                    (if lsp-csharp-csharpls-use-local-tool
    548                        "dotnet tool list"
    549                      "dotnet tool list -g"))))
    550 
    551 (defun lsp-csharp--cls-download-server (_client callback error-callback update?)
    552   "Install/update csharp-ls language server using `dotnet tool'.
    553 
    554 Will invoke CALLBACK or ERROR-CALLBACK based on result.
    555 Will update if UPDATE? is t"
    556   (lsp-async-start-process
    557    callback
    558    error-callback
    559    "dotnet" "tool" (if update? "update" "install") (if lsp-csharp-csharpls-use-local-tool "" "-g") "csharp-ls"))
    560 
    561 (lsp-register-client
    562  (make-lsp-client :new-connection (lsp-stdio-connection #'lsp-csharp--cls-make-launch-cmd)
    563                   :priority -2
    564                   :server-id 'csharp-ls
    565                   :activation-fn (lsp-activate-on "csharp")
    566                   :before-file-open-fn #'lsp-csharp--cls-before-file-open
    567                   :uri-handlers (ht ("csharp" #'lsp-csharp--cls-metadata-uri-handler))
    568                   :download-server-fn #'lsp-csharp--cls-download-server))
    569 
    570 (lsp-consistency-check lsp-csharp)
    571 
    572 (provide 'lsp-csharp)
    573 ;;; lsp-csharp.el ends here