lsp-fsharp.el (12934B)
1 ;;; lsp-fsharp.el --- description -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2019 Reed Mullanix 4 5 ;; Author: Reed Mullanix <reedmullanix@gmail.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-fsharp client 24 25 ;;; Code: 26 27 (require 'lsp-mode) 28 29 (defgroup lsp-fsharp nil 30 "LSP support for the F# Programming Language, using the FsharpAutoComplete server." 31 :link '(url-link "https://github.com/fsharp/FsAutoComplete") 32 :group 'lsp-mode 33 :package-version '(lsp-mode . "6.1")) 34 35 (defcustom lsp-fsharp-server-install-dir (f-join lsp-server-install-dir "fsautocomplete/") 36 "Install directory for fsautocomplete server. 37 The slash is expected at the end." 38 :group 'lsp-fsharp 39 :risky t 40 :type 'directory 41 :package-version '(lsp-mode . "6.1")) 42 43 (defcustom lsp-fsharp-server-args nil 44 "Extra arguments for the F# language server." 45 :type '(repeat string) 46 :group 'lsp-fsharp 47 :package-version '(lsp-mode . "6.1")) 48 49 (defcustom lsp-fsharp-keywords-autocomplete t 50 "Provides keywords in autocomplete list." 51 :group 'lsp-fsharp 52 :type 'boolean 53 :package-version '(lsp-mode . "6.2")) 54 55 (defcustom lsp-fsharp-external-autocomplete nil 56 "Provides autocompletion for symbols from not opened namespaces/modules; 57 inserts open on accept." 58 :group 'lsp-fsharp 59 :type 'boolean 60 :package-version '(lsp-mode . "6.2")) 61 62 (defcustom lsp-fsharp-linter t 63 "Enables FSharpLint integration, provides additional warnings and code 64 action fixes." 65 :group 'lsp-fsharp 66 :type 'boolean 67 :package-version '(lsp-mode . "6.2")) 68 69 (defcustom lsp-fsharp-union-case-stub-generation t 70 "Enables a code action to generate pattern matching cases." 71 :group 'lsp-fsharp 72 :type 'boolean 73 :package-version '(lsp-mode . "6.2")) 74 75 (defcustom lsp-fsharp-union-case-stub-generation-body "failwith \"Not Implemented\"" 76 "Defines dummy body used by pattern matching generator." 77 :group 'lsp-fsharp 78 :type 'string 79 :risky t 80 :package-version '(lsp-mode . "6.2")) 81 82 (defcustom lsp-fsharp-record-stub-generation t 83 "Enables code action to generate record stub." 84 :group 'lsp-fsharp 85 :type 'boolean 86 :package-version '(lsp-mode . "6.2")) 87 88 (defcustom lsp-fsharp-record-stub-generation-body "failwith \"Not Implemented\"" 89 "Defines dummy body used by record stub generator." 90 :group 'lsp-fsharp 91 :type 'string 92 :risky t 93 :package-version '(lsp-mode . "6.2")) 94 95 (defcustom lsp-fsharp-interface-stub-generation t 96 "Enables code action to generate an interface stub." 97 :group 'lsp-fsharp 98 :type 'boolean 99 :package-version '(lsp-mode . "6.2")) 100 101 (defcustom lsp-fsharp-interface-stub-generation-object-identifier "this" 102 "Defines object identifier used by interface stub generator, 103 e.g. `this' or `self'." 104 :group 'lsp-fsharp 105 :type 'string 106 :package-version '(lsp-mode . "6.2")) 107 108 (defcustom lsp-fsharp-interface-stub-generation-method-body "failwith \"Not Implemented\"" 109 "Defines dummy body used by interface stub generator." 110 :group 'lsp-fsharp 111 :type 'string 112 :risky t 113 :package-version '(lsp-mode . "6.2")) 114 115 (defcustom lsp-fsharp-unused-opens-analyzer t 116 "Enables unused open detection." 117 :group 'lsp-fsharp 118 :type 'boolean 119 :package-version '(lsp-mode . "6.2")) 120 121 (defcustom lsp-fsharp-unused-declarations-analyzer t 122 "Enables unused symbol detection." 123 :group 'lsp-fsharp 124 :type 'boolean 125 :package-version '(lsp-mode . "6.2")) 126 127 (defcustom lsp-fsharp-simplify-name-analyzer nil 128 "Enables simplify name analyzer and remove redundant qualifier quick fix." 129 :group 'lsp-fsharp 130 :type 'boolean 131 :package-version '(lsp-mode . "6.2")) 132 133 (defcustom lsp-fsharp-resolve-namespaces t 134 "Enables resolve namespace quick fix; adds `open' if symbol is from not yet 135 opened module/namespace." 136 :group 'lsp-fsharp 137 :type 'boolean 138 :package-version '(lsp-mode . "6.2")) 139 140 (defcustom lsp-fsharp-enable-reference-code-lens t 141 "Enables reference count code lenses." 142 :group 'lsp-fsharp 143 :type 'boolean 144 :package-version '(lsp-mode . "6.2")) 145 146 (defcustom lsp-fsharp-auto-workspace-init nil 147 "Enable automatic workspace initialization. 148 Do note that this can cause unexpected or challenging behaviors, as solutions 149 with test projects are not autoloaded by FSharpAutoComplete." 150 :group 'lsp-fsharp 151 :type 'boolean 152 :risky t) 153 154 (defcustom lsp-fsharp-generate-binlog nil 155 "Generate a binlog for debugging project cracking." 156 :group 'lsp-fsharp 157 :type 'boolean 158 :package-version '(lsp-mode . "9.0.0")) 159 160 (defun lsp-fsharp--fsac-install (_client callback error-callback update?) 161 "Install/update fsautocomplete language server using `dotnet tool'. 162 Will invoke CALLBACK or ERROR-CALLBACK based on result. Will update if 163 UPDATE? is t." 164 (lsp-async-start-process 165 callback 166 error-callback 167 "dotnet" "tool" (if update? "update" "install") "-g" "fsautocomplete")) 168 169 (defcustom lsp-fsharp-use-dotnet-tool-for-fsac t 170 "Run FsAutoComplete as a dotnet tool. 171 172 The binary will be invoked via \"dotnet fsautocomplete\" in the 173 project's root directory, which will run a project-local tool if 174 available, else the globally installed tool." 175 :group 'lsp-fsharp 176 :type 'boolean 177 :risky t) 178 179 (defun lsp-fsharp--fsac-cmd () 180 "The location of fsautocomplete executable." 181 (or (-let [maybe-local-executable (expand-file-name "fsautocomplete" lsp-fsharp-server-install-dir)] 182 (when (f-exists-p maybe-local-executable) 183 maybe-local-executable)) 184 (executable-find "fsautocomplete") 185 (f-join (or (getenv "USERPROFILE") (getenv "HOME")) 186 ".dotnet" "tools" "fsautocomplete"))) 187 188 (defun lsp-fsharp--make-launch-cmd () 189 "Build the command required to launch fsautocomplete." 190 191 ;; emacs-28.1 on macOS has an issue 192 ;; that it launches processes using posix_spawn but does not reset sigmask properly 193 ;; thus causing dotnet runtime to lockup awaiting a SIGCHLD signal that never comes 194 ;; from subprocesses that quit 195 ;; 196 ;; as a workaround we will wrap fsautocomplete invocation in "/bin/ksh -c" (on macos) 197 ;; so it launches with proper sigmask 198 ;; 199 ;; see https://lists.gnu.org/archive/html/emacs-devel/2022-02/msg00461.html 200 ;; -- 201 ;; we also try to resolve full path to fsautocomplete using `executable-find' as 202 ;; our `startup-wrapper' may use $PATH to interpret the location of fsautocomplete 203 ;; and we want to actually use `exec-path' here 204 205 (let ((startup-wrapper (cond ((and (eq 'darwin system-type) 206 (version= "28.1" emacs-version)) 207 (list "/bin/ksh" "-c")) 208 209 (t nil))) 210 (fsautocomplete-exec (lsp-fsharp--fsac-cmd))) 211 (append startup-wrapper 212 (list fsautocomplete-exec) 213 lsp-fsharp-server-args))) 214 215 (defun lsp-fsharp--test-fsautocomplete-present () 216 "Return non-nil if dotnet tool fsautocomplete is installed globally." 217 (if lsp-fsharp-use-dotnet-tool-for-fsac 218 (string-match-p "fsautocomplete" 219 (shell-command-to-string "dotnet tool list -g")) 220 (f-exists? (lsp-fsharp--fsac-cmd)))) 221 222 (defun lsp-fsharp--project-list (workspace) 223 "Get the list of files we need to send to fsharp/workspaceLoad." 224 (let* ((response (lsp-request "fsharp/workspacePeek" 225 `(:directory ,(lsp--workspace-root workspace) 226 :deep 10 227 :excludedDirs ["paket-files" ".git" "packages" "node_modules"]))) 228 (data (lsp--read-json (lsp-get response :content))) 229 (found (-> data (lsp-get :Data) (lsp-get :Found))) 230 (directory (seq-find (lambda (d) (equal "directory" (lsp-get d :Type))) found))) 231 (-> directory (lsp-get :Data) (lsp-get :Fsprojs)))) 232 233 ;;;###autoload 234 (defun lsp-fsharp--workspace-load (projects) 235 "Load all of the provided PROJECTS." 236 (lsp-request-async "fsharp/workspaceLoad" 237 `(:textDocuments ,(vconcat [] (mapcar (lambda (p) `(:uri ,p)) projects))) 238 (lambda (_) 239 (lsp--info "Workspace Loaded!")))) 240 241 (defvar lsp-fsharp--default-init-options (list) 242 "Default init options to be passed to FSharpAutoComplete, 243 updated conditionally by `lsp-fsharp--make-init-options'.") 244 245 (defun lsp-fsharp--make-init-options () 246 "Init options for F#." 247 (-let [opts lsp-fsharp--default-init-options] 248 (if lsp-fsharp-auto-workspace-init 249 (push '(:AutomaticWorkspaceInit . t) opts) 250 opts))) 251 252 (lsp-register-custom-settings 253 `(("FSharp.KeywordsAutocomplete" lsp-fsharp-keywords-autocomplete t) 254 ("FSharp.ExternalAutocomplete" lsp-fsharp-external-autocomplete t) 255 ("FSharp.Linter" lsp-fsharp-linter t) 256 ("FSharp.UnionCaseStubGeneration" lsp-fsharp-union-case-stub-generation t) 257 ("FSharp.UnionCaseStubGenerationBody" lsp-fsharp-union-case-stub-generation-body) 258 ("FSharp.RecordStubGeneration" lsp-fsharp-record-stub-generation t) 259 ("FSharp.RecordStubGenerationBody" lsp-fsharp-record-stub-generation-body) 260 ("FSharp.InterfaceStubGeneration" lsp-fsharp-interface-stub-generation t) 261 ("FSharp.InterfaceStubGenerationObjectIdentifier" lsp-fsharp-interface-stub-generation-object-identifier) 262 ("FSharp.InterfaceStubGenerationMethodBody" lsp-fsharp-interface-stub-generation-method-body) 263 ("FSharp.UnusedOpensAnalyzer" lsp-fsharp-unused-opens-analyzer t) 264 ("FSharp.UnusedDeclarationsAnalyzer" lsp-fsharp-unused-declarations-analyzer t) 265 ("FSharp.SimplifyNameAnalyzer" lsp-fsharp-simplify-name-analyzer t) 266 ("FSharp.ResolveNamespaces" lsp-fsharp-resolve-namespaces t) 267 ("FSharp.EnableReferenceCodeLens" lsp-fsharp-enable-reference-code-lens t) 268 ("FSharp.GenerateBinlog" lsp-fsharp-generate-binlog t))) 269 270 (lsp-register-client 271 (make-lsp-client :new-connection (lsp-stdio-connection 272 #'lsp-fsharp--make-launch-cmd 273 #'lsp-fsharp--test-fsautocomplete-present) 274 :major-modes '(fsharp-mode) 275 :notification-handlers (ht ("fsharp/notifyCancel" #'ignore) 276 ("fsharp/notifyWorkspace" #'ignore) 277 ("fsharp/fileParsed" #'ignore) 278 ("fsharp/notifyWorkspacePeek" #'ignore) 279 ("fsharp/documentAnalyzed" #'ignore) 280 ("workspace/codeLens/refresh" #'ignore) 281 ("fsharp/testDetected" #'ignore)) 282 :initialization-options 'lsp-fsharp--make-init-options 283 :initialized-fn (lambda (workspace) 284 (with-lsp-workspace workspace 285 ;; Something needs to be calling lsp--set-configuration 286 (progn 287 (lsp--set-configuration 288 (lsp-configuration-section "fsharp")) 289 (lsp-fsharp--workspace-load 290 (lsp-fsharp--project-list workspace))))) 291 :after-open-fn ;; workaround https://github.com/fsharp/FsAutoComplete/issues/833 292 (lambda () 293 (setq-local lsp-default-create-error-handler-fn 294 (lambda (method) 295 (lambda (error) 296 (when 297 (not 298 (seq-find (lambda (s) 299 (string= s (lsp-get error :message))) 300 '("Index was outside the bounds of the array." 301 "No symbol information found" 302 "No ident at this location"))) 303 (lsp--warn 304 "%s" 305 (or (lsp--error-string error) 306 (format "%s Request has failed" method)))))))) 307 :server-id 'fsac 308 :download-server-fn #'lsp-fsharp--fsac-install)) 309 310 (lsp-consistency-check lsp-fsharp) 311 312 (provide 'lsp-fsharp) 313 ;;; lsp-fsharp.el ends here