Skip to content

Commit

Permalink
Proposal: make it easier to work with dotnet tools for C#/F#
Browse files Browse the repository at this point in the history
This commit implements an approach proposed in emacs-lsp#3906. Specifically, it provides
new control variables to make it possible to use C# and F# language servers
_either_ as global _or_ local dotnet tools. So far, I've done this by providing
boolean variables to control command construction, but I'm not attached to this
approach.

Closes emacs-lsp#3906.
  • Loading branch information
Gastove committed Jan 13, 2024
1 parent 520882c commit a7e8932
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 22 deletions.
41 changes: 34 additions & 7 deletions clients/lsp-csharp.el
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,20 @@ Usually this is to be set in your .dir-locals.el on the project root directory."
:group 'lsp-csharp-omnisharp
:type 'file)

(defcustom lsp-csharp-csharpls-use-dotnet-tool t
"Whether to use a dotnet tool version of the expected C# language server; only available for csharp-ls"
:group 'lsp-csharp
:type 'boolean
:risky t)

(defcustom lsp-csharp-csharpls-use-local-tool nil
"Whether to use csharp-ls as a global or local dotnet tool.
Note: this variable has no effect if lsp-csharp-csharpls-use-dotnet-tool is nil."
:group 'lsp-csharp
:type 'boolean
:risky t)

(lsp-dependency
'omnisharp-roslyn
`(:download :url lsp-csharp-omnisharp-roslyn-download-url
Expand Down Expand Up @@ -413,6 +427,15 @@ filename is returned so lsp-mode can display this file."
(with-temp-buffer (insert-file-contents metadata-file-name)
(buffer-string))))))

(defun lsp-csharp--cls-find-executable ()
(or (when lsp-csharp-csharpls-use-dotnet-tool
(-flatten (list "dotnet" (if lsp-csharp-csharpls-use-local-tool (list "tool" "run") "") "csharp-ls")))
(executable-find "csharp-ls")
;; NOTE[gastove|2023-02-03] This approach might be remove-able if we
;; standardize on going through the `dotnet' cli.
(f-join (or (getenv "USERPROFILE") (getenv "HOME"))
".dotnet" "tools" "csharp-ls")))

(defun lsp-csharp--cls-make-launch-cmd ()
"Return command line to invoke csharp-ls."

Expand All @@ -432,20 +455,24 @@ filename is returned so lsp-mode can display this file."

(t nil)))

(csharp-ls-exec (or (executable-find "csharp-ls")
(f-join (or (getenv "USERPROFILE") (getenv "HOME"))
".dotnet" "tools" "csharp-ls")))
(csharp-ls-exec (lsp-csharp--cls-find-executable))

(solution-file-params (when lsp-csharp-solution-file
(list "-s" lsp-csharp-solution-file))))
(append startup-wrapper
(list csharp-ls-exec)
(if (listp csharp-ls-exec)
csharp-ls-exec
(list csharp-ls-exec))
solution-file-params)))

(defun lsp-csharp--cls-test-csharp-ls-present ()
"Return non-nil if dotnet tool csharp-ls is installed globally."
"Return non-nil if dotnet tool csharp-ls is installed as a dotnet tool."
(string-match-p "csharp-ls"
(shell-command-to-string "dotnet tool list -g")))
(shell-command-to-string
(if lsp-csharp-csharpls-use-local-tool
"dotnet tool list"
"dotnet tool list -g")))
)

(defun lsp-csharp--cls-download-server (_client callback error-callback update?)
"Install/update csharp-ls language server using `dotnet tool'.
Expand All @@ -455,7 +482,7 @@ Will update if UPDATE? is t"
(lsp-async-start-process
callback
error-callback
"dotnet" "tool" (if update? "update" "install") "-g" "csharp-ls"))
"dotnet" "tool" (if update? "update" "install") (if lsp-csharp-csharpls-use-local-tool "" "-g") "csharp-ls"))

(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection #'lsp-csharp--cls-make-launch-cmd
Expand Down
61 changes: 46 additions & 15 deletions clients/lsp-fsharp.el
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,6 @@ with test projects are not autoloaded by FSharpAutoComplete."
:type 'boolean
:package-version '(lsp-mode . "8.0.1"))

(defun lsp-fsharp--fsac-install (_client callback error-callback update?)
"Install/update fsautocomplete 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" "fsautocomplete"))

(defcustom lsp-fsharp-use-dotnet-tool-for-fsac t
"Run FsAutoComplete as a dotnet tool.
Expand All @@ -176,9 +167,38 @@ available, else the globally installed tool."
:type 'boolean
:risky t)

(defcustom lsp-fsharp-use-dotnet-local-tool nil
"When running FsAutoComplete as a dotnet tool, use the local version.
This variable will have no effect if
`lsp-fsharp-use-dotnet-tool-for-fsac' is nil.
This variable is risky as a buffer-local, and should instead be
set per-project (e.g. in a .dir-locals.el at the root of a
repository)."
:group 'lsp-fsharp
:type 'boolean
:risky t)

(defcustom lsp-fsharp-workspace-extra-exclude-dirs '()
"Additional directories to exclude from FsAutoComplete workspace loading / discovery."
:group 'lsp-fsharp
:type '(repeat string))

(defun lsp-fsharp--fsac-install (_client callback error-callback update?)
"Install/update fsautocomplete 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") (when lsp-fsharp-use-dotnet-local-tool "-g") "fsautocomplete"))

(defun lsp-fsharp--fsac-cmd ()
"The location of fsautocomplete executable."
(or (-let [maybe-local-executable (expand-file-name "fsautocomplete" lsp-fsharp-server-install-dir)]
(or (when lsp-fsharp-use-dotnet-tool-for-fsac
(list "dotnet" (if lsp-fsharp-use-dotnet-local-tool "" "tool") "run" "fsautocomplete"))
(-let [maybe-local-executable (expand-file-name "fsautocomplete" lsp-fsharp-server-install-dir)]
(when (f-exists-p maybe-local-executable)
maybe-local-executable))
(executable-find "fsautocomplete")
Expand Down Expand Up @@ -209,27 +229,38 @@ available, else the globally installed tool."
(t nil)))
(fsautocomplete-exec (lsp-fsharp--fsac-cmd)))
(append startup-wrapper
(list fsautocomplete-exec)
(if (listp fsautocomplete-exec)
fsautocomplete-exec
(list fsautocomplete-exec))
lsp-fsharp-server-args)))

(defun lsp-fsharp--test-fsautocomplete-present ()
"Return non-nil if dotnet tool fsautocomplete is installed globally."
(if lsp-fsharp-use-dotnet-tool-for-fsac
(string-match-p "fsautocomplete"
(shell-command-to-string "dotnet tool list -g"))
(-let* ((cmd-str (if lsp-fsharp-use-dotnet-local-tool
"dotnet tool list"
"dotnet tool list -g"))
(res (string-match-p "fsautocomplete"
(shell-command-to-string cmd-str))))
(if res res
(error "Failed to locate fsautocomplete binary; due to lsp-fsharp-use-dotnet-local-tool == %s, checked with command %s" lsp-fsharp-use-dotnet-local-tool cmd-str)))

(f-exists? (lsp-fsharp--fsac-cmd))))

(defun lsp-fsharp--project-list (workspace)
"Get the list of files we need to send to fsharp/workspaceLoad."
(let* ((response (lsp-request "fsharp/workspacePeek"
(let* ((base-exlude-dirs ["paket-files" ".git" "packages" "node_modules"])
(exclude-dirs (apply 'vector (append base-exlude-dirs lsp-fsharp-workspace-extra-exclude-dirs)))
(response (lsp-request "fsharp/workspacePeek"
`(:directory ,(lsp--workspace-root workspace)
:deep 10
:excludedDirs ["paket-files" ".git" "packages" "node_modules"])))
:excludedDirs ,exclude-dirs)))
(data (lsp--read-json (lsp-get response :content)))
(found (-> data (lsp-get :Data) (lsp-get :Found)))
(directory (seq-find (lambda (d) (equal "directory" (lsp-get d :Type))) found)))
(-> directory (lsp-get :Data) (lsp-get :Fsprojs))))


;;;###autoload
(defun lsp-fsharp--workspace-load (projects)
"Load all of the provided PROJECTS."
Expand Down

0 comments on commit a7e8932

Please sign in to comment.