config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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