aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/lsp-mode-20220505.630/lsp-csharp.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/lsp-mode-20220505.630/lsp-csharp.el')
-rw-r--r--elpa/lsp-mode-20220505.630/lsp-csharp.el472
1 files changed, 472 insertions, 0 deletions
diff --git a/elpa/lsp-mode-20220505.630/lsp-csharp.el b/elpa/lsp-mode-20220505.630/lsp-csharp.el
new file mode 100644
index 0000000..a9ca0d8
--- /dev/null
+++ b/elpa/lsp-mode-20220505.630/lsp-csharp.el
@@ -0,0 +1,472 @@
+;;; lsp-csharp.el --- description -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Jostein Kjønigsen, Saulius Menkevicius
+
+;; Author: Saulius Menkevicius <saulius.menkevicius@fastmail.com>
+;; 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"))
+
+(defgroup lsp-csharp-omnisharp 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")
+ :package-version '(lsp-mode . "8.0.1"))
+
+(defcustom lsp-csharp-server-install-dir
+ (f-join lsp-server-install-dir "omnisharp-roslyn/")
+ "Installation directory for OmniSharp Roslyn server."
+ :group 'lsp-csharp-omnisharp
+ :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-omnisharp
+ :type '(string :tag "Single string value or nil"))
+
+(defcustom lsp-csharp-test-run-buffer-name
+ "*lsp-csharp test run*"
+ "The name of buffer used for outputing lsp-csharp test run results."
+ :group 'lsp-csharp-omnisharp
+ :type 'string)
+
+(defcustom lsp-csharp-solution-file
+ nil
+ "Solution to load when starting the server.
+Usually this is to be set in your .dir-locals.el on the project root directory."
+ :group 'lsp-csharp-omnisharp
+ :type 'string)
+
+(defcustom lsp-csharp-omnisharp-roslyn-download-url
+ (concat "https://github.com/omnisharp/omnisharp-roslyn/releases/latest/download/"
+ (cond ((eq system-type 'windows-nt)
+ ; 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"
+ (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.zip")
+
+ ((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.zip")
+
+ (t "omnisharp-mono.zip")))
+ "Automatic download url for omnisharp-roslyn."
+ :group 'lsp-csharp-omnisharp
+ :type 'string)
+
+(defcustom lsp-csharp-omnisharp-roslyn-store-path
+ (f-join lsp-csharp-server-install-dir "latest" "omnisharp-roslyn.zip")
+ "The path where omnisharp-roslyn .zip archive will be stored."
+ :group 'lsp-csharp-omnisharp
+ :type 'file)
+
+(defcustom lsp-csharp-omnisharp-roslyn-server-dir
+ (f-join lsp-csharp-server-install-dir "latest" "omnisharp-roslyn")
+ "The path where omnisharp-roslyn .zip archive will be extracted."
+ :group 'lsp-csharp-omnisharp
+ :type 'file)
+
+(lsp-dependency
+ 'omnisharp-roslyn
+ `(:download :url lsp-csharp-omnisharp-roslyn-download-url
+ :store-path lsp-csharp-omnisharp-roslyn-store-path))
+
+(defun lsp-csharp--download-server (_client callback error-callback _update?)
+ "Download zip package for omnisharp-roslyn and install it.
+Will invoke CALLBACK on success, ERROR-CALLBACK on error."
+ (lsp-package-ensure
+ 'omnisharp-roslyn
+ (lambda ()
+ (lsp-unzip lsp-csharp-omnisharp-roslyn-store-path
+ lsp-csharp-omnisharp-roslyn-server-dir)
+ (unless (eq system-type 'windows-nt)
+ (let ((run-script (f-join lsp-csharp-omnisharp-roslyn-server-dir "run")))
+ (when (not (f-exists-p run-script))
+ ; create the `run' script when missing (e.g. when server binaries are extracted from omnisharp-mono.zip)
+ ; NOTE: we do not check for presence or version of mono in the system
+ (with-temp-file run-script
+ (insert "#!/bin/bash\n")
+ (insert "BASEDIR=$(dirname \"$0\")\n")
+ (insert "exec mono $BASEDIR/OmniSharp.exe $@\n")))
+ (set-file-modes run-script #o755)))
+ (funcall callback))
+ error-callback))
+
+(defun lsp-csharp--language-server-path ()
+ "Resolve path to use to start the server."
+ (if lsp-csharp-server-path
+ lsp-csharp-server-path
+ (let ((server-dir lsp-csharp-omnisharp-roslyn-server-dir))
+ (when (f-exists? server-dir)
+ (f-join server-dir (cond ((eq system-type 'windows-nt) "OmniSharp.exe")
+ (t "run")))))))
+
+(lsp-defun lsp-csharp-open-project-file ()
+ "Open corresponding project file (.csproj) for the current file."
+ (interactive)
+ (-let* ((project-info-req (lsp-make-omnisharp-project-information-request :file-name (buffer-file-name)))
+ (project-info (lsp-request "o#/project" project-info-req))
+ ((&omnisharp:ProjectInformation :ms-build-project) project-info)
+ ((&omnisharp:MsBuildProject :path) ms-build-project))
+ (find-file path)))
+
+(defun lsp-csharp--get-buffer-code-elements ()
+ "Retrieve code structure by calling into the /v2/codestructure endpoint.
+Returns :elements from omnisharp:CodeStructureResponse."
+ (-let* ((code-structure (lsp-request "o#/v2/codestructure"
+ (lsp-make-omnisharp-code-structure-request :file-name (buffer-file-name))))
+ ((&omnisharp:CodeStructureResponse :elements) code-structure))
+ elements))
+
+(defun lsp-csharp--inspect-code-elements-recursively (fn elements)
+ "Invoke FN for every omnisharp:CodeElement found recursively in ELEMENTS."
+ (seq-each
+ (lambda (el)
+ (funcall fn el)
+ (-let (((&omnisharp:CodeElement :children) el))
+ (lsp-csharp--inspect-code-elements-recursively fn children)))
+ elements))
+
+(defun lsp-csharp--collect-code-elements-recursively (predicate elements)
+ "Flatten the omnisharp:CodeElement tree in ELEMENTS matching PREDICATE."
+ (let ((results nil))
+ (lsp-csharp--inspect-code-elements-recursively (lambda (el)
+ (when (funcall predicate el)
+ (setq results (cons el results))))
+ elements)
+ results))
+
+(lsp-defun lsp-csharp--l-c-within-range (l c (&omnisharp:Range :start :end))
+ "Determine if L (line) and C (column) are within RANGE."
+ (-let* (((&omnisharp:Point :line start-l :column start-c) start)
+ ((&omnisharp:Point :line end-l :column end-c) end))
+ (or (and (= l start-l) (>= c start-c) (or (> end-l start-l) (<= c end-c)))
+ (and (> l start-l) (< l end-l))
+ (and (= l end-l) (<= c end-c)))))
+
+(defun lsp-csharp--code-element-stack-on-l-c (l c elements)
+ "Return omnisharp:CodeElement stack at L (line) and C (column) in ELEMENTS tree."
+ (when-let ((matching-element (seq-find (lambda (el)
+ (-when-let* (((&omnisharp:CodeElement :ranges) el)
+ ((&omnisharp:RangeList :full?) ranges))
+ (lsp-csharp--l-c-within-range l c full?)))
+ elements)))
+ (-let (((&omnisharp:CodeElement :children) matching-element))
+ (cons matching-element (lsp-csharp--code-element-stack-on-l-c l c children)))))
+
+(defun lsp-csharp--code-element-stack-at-point ()
+ "Return omnisharp:CodeElement stack at point as a list."
+ (let ((pos-line (plist-get (lsp--cur-position) :line))
+ (pos-col (plist-get (lsp--cur-position) :character)))
+ (lsp-csharp--code-element-stack-on-l-c pos-line
+ pos-col
+ (lsp-csharp--get-buffer-code-elements))))
+
+(lsp-defun lsp-csharp--code-element-test-method-p (element)
+ "Return test method name and test framework for a given ELEMENT."
+ (when element
+ (-when-let* (((&omnisharp:CodeElement :properties) element)
+ ((&omnisharp:CodeElementProperties :test-method-name? :test-framework?) properties))
+ (list test-method-name? test-framework?))))
+
+(defun lsp-csharp--reset-test-buffer (present-buffer)
+ "Create new or reuse an existing test result output buffer.
+PRESENT-BUFFER will make the buffer be presented to the user."
+ (with-current-buffer (get-buffer-create lsp-csharp-test-run-buffer-name)
+ (compilation-mode)
+ (read-only-mode)
+ (let ((inhibit-read-only t))
+ (erase-buffer)))
+
+ (when present-buffer
+ (display-buffer lsp-csharp-test-run-buffer-name)))
+
+(defun lsp-csharp--start-tests (test-method-framework test-method-names)
+ "Run test(s) identified by TEST-METHOD-NAMES using TEST-METHOD-FRAMEWORK."
+ (if (and test-method-framework test-method-names)
+ (let ((request-message (lsp-make-omnisharp-run-tests-in-class-request
+ :file-name (buffer-file-name)
+ :test-frameworkname test-method-framework
+ :method-names (vconcat test-method-names))))
+ (lsp-csharp--reset-test-buffer t)
+ (lsp-session-set-metadata "last-test-method-framework" test-method-framework)
+ (lsp-session-set-metadata "last-test-method-names" test-method-names)
+ (lsp-request-async "o#/v2/runtestsinclass"
+ request-message
+ (-lambda ((&omnisharp:RunTestResponse))
+ (message "lsp-csharp: Test run has started"))))
+ (message "lsp-csharp: No test methods to run")))
+
+(defun lsp-csharp--test-message (message)
+ "Emit a MESSAGE to lsp-csharp test run buffer."
+ (when-let ((existing-buffer (get-buffer lsp-csharp-test-run-buffer-name))
+ (inhibit-read-only t))
+ (with-current-buffer existing-buffer
+ (save-excursion
+ (goto-char (point-max))
+ (insert message "\n")))))
+
+(defun lsp-csharp-run-test-at-point ()
+ "Start test run at current point (if any)."
+ (interactive)
+ (let* ((stack (lsp-csharp--code-element-stack-at-point))
+ (element-on-point (car (last stack)))
+ (test-method (lsp-csharp--code-element-test-method-p element-on-point))
+ (test-method-name (car test-method))
+ (test-method-framework (car (cdr test-method))))
+ (lsp-csharp--start-tests test-method-framework (list test-method-name))))
+
+(defun lsp-csharp-run-all-tests-in-buffer ()
+ "Run all test methods in the current buffer."
+ (interactive)
+ (let* ((elements (lsp-csharp--get-buffer-code-elements))
+ (test-methods (lsp-csharp--collect-code-elements-recursively 'lsp-csharp--code-element-test-method-p elements))
+ (test-method-framework (car (cdr (lsp-csharp--code-element-test-method-p (car test-methods)))))
+ (test-method-names (mapcar (lambda (method)
+ (car (lsp-csharp--code-element-test-method-p method)))
+ test-methods)))
+ (lsp-csharp--start-tests test-method-framework test-method-names)))
+
+(defun lsp-csharp-run-test-in-buffer ()
+ "Run selected test in current buffer."
+ (interactive)
+ (when-let* ((elements (lsp-csharp--get-buffer-code-elements))
+ (test-methods (lsp-csharp--collect-code-elements-recursively 'lsp-csharp--code-element-test-method-p elements))
+ (test-method-framework (car (cdr (lsp-csharp--code-element-test-method-p (car test-methods)))))
+ (test-method-names (mapcar (lambda (method)
+ (car (lsp-csharp--code-element-test-method-p method)))
+ test-methods))
+ (selected-test-method-name (lsp--completing-read "Select test:" test-method-names 'identity)))
+ (lsp-csharp--start-tests test-method-framework (list selected-test-method-name))))
+
+(defun lsp-csharp-run-last-tests ()
+ "Re-run test(s) that were run last time."
+ (interactive)
+ (if-let ((last-test-method-framework (lsp-session-get-metadata "last-test-method-framework"))
+ (last-test-method-names (lsp-session-get-metadata "last-test-method-names")))
+ (lsp-csharp--start-tests last-test-method-framework last-test-method-names)
+ (message "lsp-csharp: No test method(s) found to be ran previously on this workspace")))
+
+(lsp-defun lsp-csharp--handle-os-error (_workspace (&omnisharp:ErrorMessage :file-name :text))
+ "Handle the 'o#/error' (interop) notification by displaying a message with lsp-warn."
+ (lsp-warn "%s: %s" file-name text))
+
+(lsp-defun lsp-csharp--handle-os-testmessage (_workspace (&omnisharp:TestMessageEvent :message))
+ "Handle the 'o#/testmessage and display test message on lsp-csharp
+test output buffer."
+ (lsp-csharp--test-message message))
+
+(lsp-defun lsp-csharp--handle-os-testcompleted (_workspace (&omnisharp:DotNetTestResult
+ :method-name
+ :outcome
+ :error-message
+ :error-stack-trace
+ :standard-output
+ :standard-error))
+ "Handle the 'o#/testcompleted' message from the server.
+
+Will display the results of the test on the lsp-csharp test output buffer."
+ (let ((passed (string-equal "passed" outcome)))
+ (lsp-csharp--test-message
+ (format "[%s] %s "
+ (propertize (upcase outcome) 'font-lock-face (if passed 'success 'error))
+ method-name))
+
+ (unless passed
+ (lsp-csharp--test-message error-message)
+
+ (when error-stack-trace
+ (lsp-csharp--test-message error-stack-trace))
+
+ (unless (seq-empty-p standard-output)
+ (lsp-csharp--test-message "STANDARD OUTPUT:")
+ (seq-doseq (stdout-line standard-output)
+ (lsp-csharp--test-message stdout-line)))
+
+ (unless (seq-empty-p standard-error)
+ (lsp-csharp--test-message "STANDARD ERROR:")
+ (seq-doseq (stderr-line standard-error)
+ (lsp-csharp--test-message stderr-line))))))
+
+(lsp-defun lsp-csharp--action-client-find-references ((&Command :arguments?))
+ "Read first argument from ACTION as Location and display xrefs for that location
+using the `textDocument/references' request."
+ (-if-let* (((&Location :uri :range) (lsp-seq-first arguments?))
+ ((&Range :start range-start) range)
+ (find-refs-params (append (lsp--text-document-position-params (list :uri uri) range-start)
+ (list :context (list :includeDeclaration json-false))))
+ (locations-found (lsp-request "textDocument/references" find-refs-params)))
+ (lsp-show-xrefs (lsp--locations-to-xref-items locations-found) nil t)
+ (message "No references found")))
+
+(lsp-register-client
+ (make-lsp-client :new-connection
+ (lsp-stdio-connection
+ #'(lambda ()
+ (append
+ (list (lsp-csharp--language-server-path) "-lsp")
+ (when lsp-csharp-solution-file
+ (list "-s" (expand-file-name lsp-csharp-solution-file)))))
+ #'(lambda ()
+ (when-let ((binary (lsp-csharp--language-server-path)))
+ (f-exists? binary))))
+ :major-modes '(csharp-mode csharp-tree-sitter-mode)
+ :server-id 'omnisharp
+ :priority -1
+ :action-handlers (ht ("omnisharp/client/findReferences" 'lsp-csharp--action-client-find-references))
+ :notification-handlers (ht ("o#/projectadded" 'ignore)
+ ("o#/projectchanged" 'ignore)
+ ("o#/projectremoved" 'ignore)
+ ("o#/packagerestorestarted" 'ignore)
+ ("o#/msbuildprojectdiagnostics" 'ignore)
+ ("o#/packagerestorefinished" 'ignore)
+ ("o#/unresolveddependencies" 'ignore)
+ ("o#/error" 'lsp-csharp--handle-os-error)
+ ("o#/testmessage" 'lsp-csharp--handle-os-testmessage)
+ ("o#/testcompleted" 'lsp-csharp--handle-os-testcompleted)
+ ("o#/projectconfiguration" 'ignore)
+ ("o#/projectdiagnosticstatus" 'ignore))
+ :download-server-fn #'lsp-csharp--download-server))
+
+;;
+;; Alternative "csharp-ls" language server support
+;; see https://github.com/razzmatazz/csharp-language-server
+;;
+(lsp-defun lsp-csharp--cls-metadata-uri-handler (uri)
+ "Handle `csharp:/(metadata)' uri from csharp-ls server.
+
+'csharp/metadata' request is issued to retrieve metadata from the server.
+A cache file is created on project root dir that stores this metadata and filename
+is returned so lsp-mode can display this file."
+
+ (-when-let* ((metadata-req (lsp-make-csharp-ls-c-sharp-metadata
+ :text-document (lsp-make-text-document-identifier :uri uri)))
+ (metadata (lsp-request "csharp/metadata" metadata-req))
+ ((&csharp-ls:CSharpMetadataResponse :project-name
+ :assembly-name
+ :symbol-name
+ :source) metadata)
+ (filename (f-join ".cache"
+ "lsp-csharp"
+ "metadata"
+ "projects" project-name
+ "assemblies" assembly-name
+ (concat symbol-name ".cs")))
+ (file-location (expand-file-name filename (lsp-workspace-root)))
+ (metadata-file-location (concat file-location ".metadata-uri"))
+ (path (f-dirname file-location)))
+
+ (unless (file-exists-p file-location)
+ (unless (file-directory-p path)
+ (make-directory path t))
+
+ (with-temp-file metadata-file-location
+ (insert uri))
+
+ (with-temp-file file-location
+ (insert source)))
+
+ file-location))
+
+(defun lsp-csharp--cls-before-file-open (_workspace)
+ "Set `lsp-buffer-uri' variable after C# file is open from *.metadata-uri file."
+
+ (let ((metadata-file-name (concat buffer-file-name ".metadata-uri")))
+ (setq-local lsp-buffer-uri
+ (when (file-exists-p metadata-file-name)
+ (with-temp-buffer (insert-file-contents metadata-file-name)
+ (buffer-string))))))
+
+(defun lsp-csharp--cls-make-launch-cmd ()
+ "Return command line to invoke csharp-ls."
+
+ ;; latest emacs-28 (on macOS) and master (as of Sat Feb 12 EET 2022) has an issue
+ ;; that it launches processes using posix_spawn but does not reset sigmask properly
+ ;; thus causing dotnet runtime to lockup awaiting a SIGCHLD signal that never comes
+ ;; from subprocesses that quit
+ ;;
+ ;; as a workaround we will wrap csharp-ls invocation in "/usr/bin/env --default-signal"
+ ;; (on linux) and "/bin/ksh -c" (on macos) so it launches with proper sigmask
+ ;;
+ ;; see https://lists.gnu.org/archive/html/emacs-devel/2022-02/msg00461.html
+
+ (let ((startup-wrapper (cond ((and (eq 'darwin system-type)
+ (version<= "28.0" emacs-version))
+ (list "/bin/ksh" "-c"))
+
+ ((and (eq 'gnu/linux system-type)
+ (version<= "29.0" emacs-version))
+ (list "/usr/bin/env" "--default-signal"))
+
+ (t nil)))
+ (solution-file-params (when lsp-csharp-solution-file
+ (list "-s" lsp-csharp-solution-file))))
+ (append startup-wrapper
+ (list "csharp-ls")
+ solution-file-params)))
+
+(defun lsp-csharp--cls-test-csharp-ls-present ()
+ "Return non-nil if dotnet tool csharp-ls is installed globally."
+ (string-match-p "csharp-ls"
+ (shell-command-to-string "dotnet tool list -g")))
+
+(defun lsp-csharp--cls-download-server (_client callback error-callback update?)
+ "Install/update csharp-ls language server using `dotnet tool'.
+
+Will invoke CALLBACK or ERROR-CALLBACK based on result. Will update if UPDATE? is t"
+ (lsp-async-start-process
+ callback
+ error-callback
+ "dotnet" "tool" (if update? "update" "install") "-g" "csharp-ls"))
+
+(lsp-register-client
+ (make-lsp-client :new-connection (lsp-stdio-connection #'lsp-csharp--cls-make-launch-cmd
+ #'lsp-csharp--cls-test-csharp-ls-present)
+ :priority -2
+ :server-id 'csharp-ls
+ :major-modes '(csharp-mode csharp-tree-sitter-mode)
+ :before-file-open-fn #'lsp-csharp--cls-before-file-open
+ :uri-handlers (ht ("csharp" #'lsp-csharp--cls-metadata-uri-handler))
+ :download-server-fn #'lsp-csharp--cls-download-server))
+
+(lsp-consistency-check lsp-csharp)
+
+(provide 'lsp-csharp)
+;;; lsp-csharp.el ends here