2023-07-27 19:52:58 +00:00
;;; lsp-fsharp.el --- description -*- lexical-binding: t; -*-
;; Copyright (C) 2019 Reed Mullanix
;; Author: Reed Mullanix <reedmullanix@gmail.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-fsharp client
;;; Code:
( require 'lsp-mode )
( defgroup lsp-fsharp nil
" LSP support for the F# Programming Language, using the FsharpAutoComplete server. "
:link ' ( url-link " https://github.com/fsharp/FsAutoComplete " )
:group 'lsp-mode
:package-version ' ( lsp-mode . " 6.1 " ) )
( defcustom lsp-fsharp-server-install-dir ( f-join lsp-server-install-dir " fsautocomplete/ " )
" Install directory for fsautocomplete server.
The slash is expected at the end. "
:group 'lsp-fsharp
:risky t
:type 'directory
:package-version ' ( lsp-mode . " 6.1 " ) )
( defcustom lsp-fsharp-server-args nil
" Extra arguments for the F# language server. "
:type ' ( repeat string )
:group 'lsp-fsharp
:package-version ' ( lsp-mode . " 6.1 " ) )
( defcustom lsp-fsharp-keywords-autocomplete t
" Provides keywords in autocomplete list. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-external-autocomplete nil
" Provides autocompletion for symbols from not opened namespaces/modules;
inserts open on accept. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-linter t
" Enables FSharpLint integration, provides additional warnings and code
action fixes. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-union-case-stub-generation t
" Enables a code action to generate pattern matching cases. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-union-case-stub-generation-body " failwith \" Not Implemented \" "
" Defines dummy body used by pattern matching generator. "
:group 'lsp-fsharp
:type 'string
:risky t
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-record-stub-generation t
" Enables code action to generate record stub. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-record-stub-generation-body " failwith \" Not Implemented \" "
" Defines dummy body used by record stub generator. "
:group 'lsp-fsharp
:type 'string
:risky t
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-interface-stub-generation t
" Enables code action to generate an interface stub. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-interface-stub-generation-object-identifier " this "
" Defines object identifier used by interface stub generator,
e.g. ` this ' or ` self '. "
:group 'lsp-fsharp
:type 'string
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-interface-stub-generation-method-body " failwith \" Not Implemented \" "
" Defines dummy body used by interface stub generator. "
:group 'lsp-fsharp
:type 'string
:risky t
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-unused-opens-analyzer t
" Enables unused open detection. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-unused-declarations-analyzer t
" Enables unused symbol detection. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-simplify-name-analyzer nil
" Enables simplify name analyzer and remove redundant qualifier quick fix. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-resolve-namespaces t
" Enables resolve namespace quick fix; adds `open' if symbol is from not yet
opened module/namespace. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-enable-reference-code-lens t
" Enables reference count code lenses. "
:group 'lsp-fsharp
:type 'boolean
:package-version ' ( lsp-mode . " 6.2 " ) )
( defcustom lsp-fsharp-auto-workspace-init nil
" Enable automatic workspace initialization.
Do note that this can cause unexpected or challenging behaviors, as solutions
with test projects are not autoloaded by FSharpAutoComplete. "
:group 'lsp-fsharp
:type 'boolean
:risky t )
( defcustom lsp-fsharp-generate-binlog nil
" Generate a binlog for debugging project cracking. "
:group 'lsp-fsharp
:type 'boolean
2024-07-28 16:03:37 +00:00
:package-version ' ( lsp-mode . " 9.0.0 " ) )
2023-07-27 19:52:58 +00:00
( defcustom lsp-fsharp-use-dotnet-tool-for-fsac t
" Run FsAutoComplete as a dotnet tool.
The binary will be invoked via \"dotnet fsautocomplete\" in the
project 's root directory, which will run a project-local tool if
available, else the globally installed tool. "
:group 'lsp-fsharp
:type 'boolean
:risky t )
2024-07-28 16:03:37 +00:00
( 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 nil
" Additional directories to exclude from FsAutoComplete
workspace loading / discovery. "
:group 'lsp-fsharp
:type 'lsp-string-vector )
( 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 " ) )
2023-07-27 19:52:58 +00:00
( defun lsp-fsharp--fsac-cmd ( )
" The location of fsautocomplete executable. "
2024-07-28 16:03:37 +00:00
( or ( when lsp-fsharp-use-dotnet-tool-for-fsac
( if lsp-fsharp-use-dotnet-local-tool
( list " dotnet " " tool " " run " " fsautocomplete " )
( list " fsautocomplete " ) ) )
( -let [ maybe-local-executable ( expand-file-name " fsautocomplete " lsp-fsharp-server-install-dir ) ]
2023-07-27 19:52:58 +00:00
( when ( f-exists-p maybe-local-executable )
maybe-local-executable ) )
( executable-find " fsautocomplete " )
( f-join ( or ( getenv " USERPROFILE " ) ( getenv " HOME " ) )
" .dotnet " " tools " " fsautocomplete " ) ) )
( defun lsp-fsharp--make-launch-cmd ( )
" Build the command required to launch fsautocomplete. "
;; emacs-28.1 on macOS 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 fsautocomplete invocation in "/bin/ksh -c" (on macos)
;; so it launches with proper sigmask
;;
;; see https://lists.gnu.org/archive/html/emacs-devel/2022-02/msg00461.html
;; --
;; we also try to resolve full path to fsautocomplete using `executable-find' as
;; our `startup-wrapper' may use $PATH to interpret the location of fsautocomplete
;; and we want to actually use `exec-path' here
( let ( ( startup-wrapper ( cond ( ( and ( eq 'darwin system-type )
( version= " 28.1 " emacs-version ) )
( list " /bin/ksh " " -c " ) )
( t nil ) ) )
( fsautocomplete-exec ( lsp-fsharp--fsac-cmd ) ) )
( append startup-wrapper
2024-07-28 16:03:37 +00:00
( if ( listp fsautocomplete-exec )
fsautocomplete-exec
( list fsautocomplete-exec ) )
2023-07-27 19:52:58 +00:00
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
2024-07-28 16:03:37 +00:00
( -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 ) ) )
2023-07-27 19:52:58 +00:00
( f-exists? ( lsp-fsharp--fsac-cmd ) ) ) )
( defun lsp-fsharp--project-list ( workspace )
" Get the list of files we need to send to fsharp/workspaceLoad. "
2024-07-28 16:03:37 +00:00
( 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 "
2023-07-27 19:52:58 +00:00
` ( :directory , ( lsp--workspace-root workspace )
:deep 10
2024-07-28 16:03:37 +00:00
:excludedDirs , exclude-dirs ) ) )
2023-07-27 19:52:58 +00:00
( 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. "
( lsp-request-async " fsharp/workspaceLoad "
` ( :textDocuments , ( vconcat [ ] ( mapcar ( lambda ( p ) ` ( :uri , p ) ) projects ) ) )
( lambda ( _ )
( lsp--info " Workspace Loaded! " ) ) ) )
( defvar lsp-fsharp--default-init-options ( list )
" Default init options to be passed to FSharpAutoComplete,
updated conditionally by ` lsp-fsharp--make-init-options '. " )
( defun lsp-fsharp--make-init-options ( )
" Init options for F#. "
( -let [ opts lsp-fsharp--default-init-options ]
( if lsp-fsharp-auto-workspace-init
( push ' ( :AutomaticWorkspaceInit . t ) opts )
opts ) ) )
( lsp-register-custom-settings
` ( ( " FSharp.KeywordsAutocomplete " lsp-fsharp-keywords-autocomplete t )
( " FSharp.ExternalAutocomplete " lsp-fsharp-external-autocomplete t )
( " FSharp.Linter " lsp-fsharp-linter t )
( " FSharp.UnionCaseStubGeneration " lsp-fsharp-union-case-stub-generation t )
( " FSharp.UnionCaseStubGenerationBody " lsp-fsharp-union-case-stub-generation-body )
( " FSharp.RecordStubGeneration " lsp-fsharp-record-stub-generation t )
( " FSharp.RecordStubGenerationBody " lsp-fsharp-record-stub-generation-body )
( " FSharp.InterfaceStubGeneration " lsp-fsharp-interface-stub-generation t )
( " FSharp.InterfaceStubGenerationObjectIdentifier " lsp-fsharp-interface-stub-generation-object-identifier )
( " FSharp.InterfaceStubGenerationMethodBody " lsp-fsharp-interface-stub-generation-method-body )
( " FSharp.UnusedOpensAnalyzer " lsp-fsharp-unused-opens-analyzer t )
( " FSharp.UnusedDeclarationsAnalyzer " lsp-fsharp-unused-declarations-analyzer t )
( " FSharp.SimplifyNameAnalyzer " lsp-fsharp-simplify-name-analyzer t )
( " FSharp.ResolveNamespaces " lsp-fsharp-resolve-namespaces t )
( " FSharp.EnableReferenceCodeLens " lsp-fsharp-enable-reference-code-lens t )
( " FSharp.GenerateBinlog " lsp-fsharp-generate-binlog t ) ) )
( lsp-register-client
( make-lsp-client :new-connection ( lsp-stdio-connection
#' lsp-fsharp--make-launch-cmd
#' lsp-fsharp--test-fsautocomplete-present )
:major-modes ' ( fsharp-mode )
:notification-handlers ( ht ( " fsharp/notifyCancel " #' ignore )
( " fsharp/notifyWorkspace " #' ignore )
( " fsharp/fileParsed " #' ignore )
( " fsharp/notifyWorkspacePeek " #' ignore )
( " fsharp/documentAnalyzed " #' ignore )
( " workspace/codeLens/refresh " #' ignore )
( " fsharp/testDetected " #' ignore ) )
:initialization-options 'lsp-fsharp--make-init-options
:initialized-fn ( lambda ( workspace )
( with-lsp-workspace workspace
;; Something needs to be calling lsp--set-configuration
( progn
( lsp--set-configuration
( lsp-configuration-section " fsharp " ) )
( lsp-fsharp--workspace-load
( lsp-fsharp--project-list workspace ) ) ) ) )
:after-open-fn ;; workaround https://github.com/fsharp/FsAutoComplete/issues/833
( lambda ( )
( setq-local lsp-default-create-error-handler-fn
( lambda ( method )
( lambda ( error )
( when
( not
( seq-find ( lambda ( s )
( string= s ( lsp-get error :message ) ) )
' ( " Index was outside the bounds of the array. "
" No symbol information found "
" No ident at this location " ) ) )
( lsp--warn
" %s "
( or ( lsp--error-string error )
( format " %s Request has failed " method ) ) ) ) ) ) ) )
:server-id 'fsac
:download-server-fn #' lsp-fsharp--fsac-install ) )
( lsp-consistency-check lsp-fsharp )
( provide 'lsp-fsharp )
;;; lsp-fsharp.el ends here