diff options
Diffstat (limited to 'elpa/lsp-mode-20220505.630/lsp-csharp.el')
-rw-r--r-- | elpa/lsp-mode-20220505.630/lsp-csharp.el | 472 |
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 |