lsp-fsharp.el (14344B)
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 (defcustom lsp-fsharp-use-dotnet-tool-for-fsac t 161 "Run FsAutoComplete as a dotnet tool. 162 163 The binary will be invoked via \"dotnet fsautocomplete\" in the 164 project's root directory, which will run a project-local tool if 165 available, else the globally installed tool." 166 :group 'lsp-fsharp 167 :type 'boolean 168 :risky t) 169 170 171 (defcustom lsp-fsharp-use-dotnet-local-tool nil 172 "When running FsAutoComplete as a dotnet tool, use the local version. 173 174 This variable will have no effect if 175 `lsp-fsharp-use-dotnet-tool-for-fsac' is nil. 176 177 This variable is risky as a buffer-local, and should instead be 178 set per-project (e.g. in a .dir-locals.el at the root of a 179 repository)." 180 :group 'lsp-fsharp 181 :type 'boolean 182 :risky t) 183 184 (defcustom lsp-fsharp-workspace-extra-exclude-dirs nil 185 "Additional directories to exclude from FsAutoComplete 186 workspace loading / discovery." 187 :group 'lsp-fsharp 188 :type 'lsp-string-vector) 189 190 (defun lsp-fsharp--fsac-install (_client callback error-callback update?) 191 "Install/update fsautocomplete language server using `dotnet tool'. 192 Will invoke CALLBACK or ERROR-CALLBACK based on result. Will update if 193 UPDATE? is t." 194 (lsp-async-start-process 195 callback 196 error-callback 197 "dotnet" "tool" (if update? "update" "install") (when lsp-fsharp-use-dotnet-local-tool "-g") "fsautocomplete")) 198 199 (defun lsp-fsharp--fsac-cmd () 200 "The location of fsautocomplete executable." 201 (or (when lsp-fsharp-use-dotnet-tool-for-fsac 202 (if lsp-fsharp-use-dotnet-local-tool 203 (list "dotnet" "tool" "run" "fsautocomplete") 204 (list "fsautocomplete"))) 205 (-let [maybe-local-executable (expand-file-name "fsautocomplete" lsp-fsharp-server-install-dir)] 206 (when (f-exists-p maybe-local-executable) 207 maybe-local-executable)) 208 (executable-find "fsautocomplete") 209 (f-join (or (getenv "USERPROFILE") (getenv "HOME")) 210 ".dotnet" "tools" "fsautocomplete"))) 211 212 (defun lsp-fsharp--make-launch-cmd () 213 "Build the command required to launch fsautocomplete." 214 215 ;; emacs-28.1 on macOS has an issue 216 ;; that it launches processes using posix_spawn but does not reset sigmask properly 217 ;; thus causing dotnet runtime to lockup awaiting a SIGCHLD signal that never comes 218 ;; from subprocesses that quit 219 ;; 220 ;; as a workaround we will wrap fsautocomplete invocation in "/bin/ksh -c" (on macos) 221 ;; so it launches with proper sigmask 222 ;; 223 ;; see https://lists.gnu.org/archive/html/emacs-devel/2022-02/msg00461.html 224 ;; -- 225 ;; we also try to resolve full path to fsautocomplete using `executable-find' as 226 ;; our `startup-wrapper' may use $PATH to interpret the location of fsautocomplete 227 ;; and we want to actually use `exec-path' here 228 229 (let ((startup-wrapper (cond ((and (eq 'darwin system-type) 230 (version= "28.1" emacs-version)) 231 (list "/bin/ksh" "-c")) 232 233 (t nil))) 234 (fsautocomplete-exec (lsp-fsharp--fsac-cmd))) 235 (append startup-wrapper 236 (if (listp fsautocomplete-exec) 237 fsautocomplete-exec 238 (list fsautocomplete-exec)) 239 lsp-fsharp-server-args))) 240 241 (defun lsp-fsharp--test-fsautocomplete-present () 242 "Return non-nil if dotnet tool fsautocomplete is installed globally." 243 (if lsp-fsharp-use-dotnet-tool-for-fsac 244 (-let* ((cmd-str (if lsp-fsharp-use-dotnet-local-tool 245 "dotnet tool list" 246 "dotnet tool list -g")) 247 (res (string-match-p "fsautocomplete" 248 (shell-command-to-string cmd-str)))) 249 (if res res 250 (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))) 251 252 (f-exists? (lsp-fsharp--fsac-cmd)))) 253 254 (defun lsp-fsharp--project-list (workspace) 255 "Get the list of files we need to send to fsharp/workspaceLoad." 256 (let* ((base-exlude-dirs ["paket-files" ".git" "packages" "node_modules"]) 257 (exclude-dirs (apply 'vector (append base-exlude-dirs lsp-fsharp-workspace-extra-exclude-dirs))) 258 (response (lsp-request "fsharp/workspacePeek" 259 `(:directory ,(lsp--workspace-root workspace) 260 :deep 10 261 :excludedDirs ,exclude-dirs))) 262 (data (lsp--read-json (lsp-get response :content))) 263 (found (-> data (lsp-get :Data) (lsp-get :Found))) 264 (directory (seq-find (lambda (d) (equal "directory" (lsp-get d :Type))) found))) 265 (-> directory (lsp-get :Data) (lsp-get :Fsprojs)))) 266 267 ;;;###autoload 268 (defun lsp-fsharp--workspace-load (projects) 269 "Load all of the provided PROJECTS." 270 (lsp-request-async "fsharp/workspaceLoad" 271 `(:textDocuments ,(vconcat [] (mapcar (lambda (p) `(:uri ,p)) projects))) 272 (lambda (_) 273 (lsp--info "Workspace Loaded!")))) 274 275 (defvar lsp-fsharp--default-init-options (list) 276 "Default init options to be passed to FSharpAutoComplete, 277 updated conditionally by `lsp-fsharp--make-init-options'.") 278 279 (defun lsp-fsharp--make-init-options () 280 "Init options for F#." 281 (-let [opts lsp-fsharp--default-init-options] 282 (if lsp-fsharp-auto-workspace-init 283 (push '(:AutomaticWorkspaceInit . t) opts) 284 opts))) 285 286 (lsp-register-custom-settings 287 `(("FSharp.KeywordsAutocomplete" lsp-fsharp-keywords-autocomplete t) 288 ("FSharp.ExternalAutocomplete" lsp-fsharp-external-autocomplete t) 289 ("FSharp.Linter" lsp-fsharp-linter t) 290 ("FSharp.UnionCaseStubGeneration" lsp-fsharp-union-case-stub-generation t) 291 ("FSharp.UnionCaseStubGenerationBody" lsp-fsharp-union-case-stub-generation-body) 292 ("FSharp.RecordStubGeneration" lsp-fsharp-record-stub-generation t) 293 ("FSharp.RecordStubGenerationBody" lsp-fsharp-record-stub-generation-body) 294 ("FSharp.InterfaceStubGeneration" lsp-fsharp-interface-stub-generation t) 295 ("FSharp.InterfaceStubGenerationObjectIdentifier" lsp-fsharp-interface-stub-generation-object-identifier) 296 ("FSharp.InterfaceStubGenerationMethodBody" lsp-fsharp-interface-stub-generation-method-body) 297 ("FSharp.UnusedOpensAnalyzer" lsp-fsharp-unused-opens-analyzer t) 298 ("FSharp.UnusedDeclarationsAnalyzer" lsp-fsharp-unused-declarations-analyzer t) 299 ("FSharp.SimplifyNameAnalyzer" lsp-fsharp-simplify-name-analyzer t) 300 ("FSharp.ResolveNamespaces" lsp-fsharp-resolve-namespaces t) 301 ("FSharp.EnableReferenceCodeLens" lsp-fsharp-enable-reference-code-lens t) 302 ("FSharp.GenerateBinlog" lsp-fsharp-generate-binlog t))) 303 304 (lsp-register-client 305 (make-lsp-client :new-connection (lsp-stdio-connection 306 #'lsp-fsharp--make-launch-cmd 307 #'lsp-fsharp--test-fsautocomplete-present) 308 :major-modes '(fsharp-mode) 309 :notification-handlers (ht ("fsharp/notifyCancel" #'ignore) 310 ("fsharp/notifyWorkspace" #'ignore) 311 ("fsharp/fileParsed" #'ignore) 312 ("fsharp/notifyWorkspacePeek" #'ignore) 313 ("fsharp/documentAnalyzed" #'ignore) 314 ("workspace/codeLens/refresh" #'ignore) 315 ("fsharp/testDetected" #'ignore)) 316 :initialization-options 'lsp-fsharp--make-init-options 317 :initialized-fn (lambda (workspace) 318 (with-lsp-workspace workspace 319 ;; Something needs to be calling lsp--set-configuration 320 (progn 321 (lsp--set-configuration 322 (lsp-configuration-section "fsharp")) 323 (lsp-fsharp--workspace-load 324 (lsp-fsharp--project-list workspace))))) 325 :after-open-fn ;; workaround https://github.com/fsharp/FsAutoComplete/issues/833 326 (lambda () 327 (setq-local lsp-default-create-error-handler-fn 328 (lambda (method) 329 (lambda (error) 330 (when 331 (not 332 (seq-find (lambda (s) 333 (string= s (lsp-get error :message))) 334 '("Index was outside the bounds of the array." 335 "No symbol information found" 336 "No ident at this location"))) 337 (lsp--warn 338 "%s" 339 (or (lsp--error-string error) 340 (format "%s Request has failed" method)))))))) 341 :server-id 'fsac 342 :download-server-fn #'lsp-fsharp--fsac-install)) 343 344 (lsp-consistency-check lsp-fsharp) 345 346 (provide 'lsp-fsharp) 347 ;;; lsp-fsharp.el ends here