From 63baa1a30bc0ac734dffbda56451bc7df9c8b9ab Mon Sep 17 00:00:00 2001 From: Michael Peyton Jones Date: Fri, 18 Sep 2020 16:55:12 +0100 Subject: [PATCH] Rework customization to use lsp-mode's functionality This does two things: - Create `defcustom` variables for settings, matching the vscode extension for all the language server settings. - Use `lsp-mode`'s support for custom settings to handle sending them to the server. This lets users persistently configure their server settings using normal Emacs setting customization. This will probably break people, so should maybe be a major version bump. Fixes #69, #75, #78; supersedes the (excellent) #74 and #76. Notes: - I have not copied the settings from the vscode extension regarding starting the server. We don't try and get prebuilt binaries, so I thought it was simplest to stick to command-and-arguments. - I renamed the process option for consistency with other servers and to avoid references to `hie`. This PR will already break basically everyone, so I thought I might as well do that too. - The current customization functions re-send a `didChangeConfiguration` notification when called. `lsp-mode` does *not* currently do this when you change variables, which is annoying. My inclination is to let them fix it in the name of simplicity, but if anyone really hates not being able to change the formatter without restarting the server I can try and hack something together. --- README.md | 67 ++++--------- lsp-haskell.el | 253 ++++++++++++++++++------------------------------- 2 files changed, 114 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index c10130b..df03775 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,12 @@ lsp-haskell [![MELPA](https://melpa.org/packages/lsp-haskell-badge.svg)](https://melpa.org/#/lsp-haskell) [![Build Status](https://travis-ci.com/emacs-lsp/lsp-haskell.svg?branch=master)](https://travis-ci.com/emacs-lsp/lsp-haskell) An Emacs Lisp library for interacting with -a [haskell-ide-engine](https://github.com/haskell/haskell-ide-engine/) -server using Microsoft's +a Haskell language server such as [`haskell-language-server`](https://github.com/haskell/haskell-langauge-server/) +or [`ghcide`](https://github.com/haskell/ghcide/) +using Microsoft's [Language Server Protocol](https://github.com/Microsoft/language-server-protocol/). -The library is designed to integrate with existing Emacs IDE frameworks -(completion-at-point, xref (beginning with Emacs 25.1), flycheck, haskell-mode, intero, etc). - - -*This package is still under development, and is not recommended for daily use.* +The library acts as a client for [`lsp-mode`](https://github.com/emacs-lsp/lsp-mode). ## Emacs Configuration @@ -27,51 +24,27 @@ this repository, or install from MELPA. Add the following to your `.emacs`: Note: All three packages are also available via MELPA. -It needs the HIE server in your path, so follow the appropriate +It needs the Haskell language server that you plan to use in your path, so follow the appropriate OSX or Linux section below accordingly. -## Hie Installation (OSX, Linux) +## Language server installation -The following steps are recommended to bootstrap `lsp-haskell` on OSX. - -```bash -git clone https://github.com/haskell/haskell-ide-engine -cd haskell-ide-engine -./install.hs hie -``` - -After this, we need to instruct Emacs to prefer `hie-wrapper` over -`hie` so Hie can infer which version of ghc we need for a particular -project. - -```elisp -(setq lsp-haskell-process-path-hie "hie-wrapper") -``` +Follow the instructions on the [`haskell-language-server`](https://github.com/haskell/haskell-language-server) +or [`ghcide`](https://github.com/haskell/ghcide/) repositories to install your server of choice. -## Per project configuration +If you have installed a server other than `haskell-language-server`, make sure to +customize the `lsp-haskell-server-path` variable to point to the executable you +have installed (see below). -HIE has some settings that can be changed on the fly. These are -exposed via a set of interactive functions. +## Server configuration -- `lsp-haskell-set-hlint-on` / `lsp-haskell-set-hlint-off` Turn hlint - checks on or off. -- `lsp-haskell-set-max-number-of-problems` Set the maximum number of - diagnostics reported. -- `lsp-haskell-set-liquid-on` / `lsp-haskell-set-liquid-off` Turn - liquid haskell checks on save on or off. -- `lsp-haskell-set-completion-snippets-on` / - `lsp-haskell-set-completion-snippets-off` Whether completion should - return plain text or snippets. -- `lsp-haskell-set-formatter-brittany` / - `lsp-haskell-set-formatter-floskell` / - `lsp-haskell-set-formatter-ormolu` Set code formatter. +`lsp-haskell` exposes a number of configuration options under the `lsp-haskell` +customization group, which should be set like normal customization variables. +Use `M-x customize-group` to get started. -There are also non-interactive versions that do not actually send the -settings to the live server, but are suitable for use in `.dir-locals` -for a specific project. +This includes a few options for for setting the server executable +and arguments, and numerous settings for configuring the server itself (`hlint`, +choice of formatting provider, etc.). -- `lsp-haskell-set-hlint` -- `lsp-haskell-set-max-problems` -- `lsp-haskell-set-liquid` -- `lsp-haskell-set-completion-snippets` -- `lsp-haskell-set-formatter` +Note that server configuration settings will currently [not](https://github.com/emacs-lsp/lsp-mode/issues/1174) +be applied until the server is restarted. diff --git a/lsp-haskell.el b/lsp-haskell.el index e6c494d..3d53dd5 100644 --- a/lsp-haskell.el +++ b/lsp-haskell.el @@ -31,38 +31,79 @@ ;; --------------------------------------------------------------------- ;; Configuration -;;;###autoload (defgroup lsp-haskell nil "Customization group for ‘lsp-haskell’." :group 'lsp-mode) -;;;###autoload -(defcustom lsp-haskell-process-path-hie - ;; "hie" - "hie-wrapper" - "The path for starting the haskell-ide-engine -server. hie-wrapper exists on HIE master from 2018-06-10" +;; --------------------------------------------------------------------- +;; Language server options + +;; These are registered with lsp-mode below, which handles preparing them for the server. +;; Originally generated from the vscode extension's package.json using lsp-generate-bindings. +;; Should ideally stay in sync with what's offered in the vscode extension. + +(defcustom lsp-haskell-hlint-on + t + "Get suggestions from hlint." :group 'lsp-haskell - :type '(choice (const "hie-wrapper") - (const "haskell-language-server-wrapper") - (const "ghcide") - string)) - -;;;###autoload -(defcustom lsp-haskell-process-args-hie - '("-d" "-l" "/tmp/hie.log") - "The arguments for starting the haskell-ide-engine server. -For a debug log, use `-d -l /tmp/hie.log'." + :type 'boolean) +(defcustom lsp-haskell-max-number-of-problems + 100 + "Controls the maximum number of problems produced by the server." :group 'lsp-haskell - :type '(repeat (string :tag "Argument"))) + :type 'number) +(defcustom + lsp-haskell-diagnostics-on-change + t + "Compute diagnostics continuously as you type. Turn off to only generate diagnostics on file save." + :group 'lsp-haskell + :type 'boolean) +(defcustom lsp-haskell-liquid-on + nil + "Get diagnostics from liquid haskell." + :group 'lsp-haskell + :type 'boolean) +(defcustom lsp-haskell-completion-snippets-on + t + "Show snippets with type information when using code completion." + :group 'lsp-haskell + :type 'boolean) +(defcustom lsp-haskell-format-on-import-on + t + "When adding an import, use the formatter on the result." + :group 'lsp-haskell + :type 'boolean) +(defcustom lsp-haskell-formatting-provider + "ormolu" + "The formatter to use when formatting a document or range." + :group 'lsp-haskell + :type '(choice (const :tag "brittany" "brittany") + (const :tag "floskell" "floskell") + (const :tag "fourmolu" "fourmolu") + (const :tag "ormolu" "ormolu") + (const :tag "stylish-haskell" "stylish-haskell") + (const :tag "none" "none"))) -;;;###autoload -(defcustom lsp-haskell-process-wrapper-function - #'identity - "Use this to wrap the haskell-ide-engine process started by lsp-haskell. +;; --------------------------------------------------------------------- +;; Non-language server options -For example, use the following the start the hie process in a nix-shell: +(defcustom lsp-haskell-server-path + "haskell-language-server" + "The language server executable. Can be something on the $PATH (e.g. 'ghcide') or a path to an executable itself." + :group 'lsp-haskell + :type 'string) + +(defcustom lsp-haskell-server-args + '("-d" "-l" "/tmp/hls.log") + "The arguments for starting the language server. +For a debug log when using haskell-language-server, use `-d -l /tmp/hls.log'." + :group 'lsp-haskell + :type '(repeat (string :tag "Argument"))) +(defcustom lsp-haskell-server-wrapper-function + #'identity + "Use this to wrap the language server process started by lsp-haskell. +For example, use the following the start the process in a nix-shell: (lambda (argv) (append (append (list \"nix-shell\" \"-I\" \".\" \"--command\" ) @@ -76,11 +117,6 @@ For example, use the following the start the hie process in a nix-shell: (function-item :tag "None" :value identity) (function :tag "Custom function"))) -;; --------------------------------------------------------------------- -;; Internal variables - -(defvar lsp-haskell--config-options (make-hash-table)) - ;; --------------------------------------------------------------------- ;; HaRe functions @@ -150,6 +186,7 @@ For example, use the following the start the hie process in a nix-shell: :pos ,(lsp-point-to-position (point)))))) ;; --------------------------------------------------------------------- +;; Miscellaneous useful functions (defun lsp-haskell--session-cabal-dir () "Get the session cabal-dir." @@ -176,144 +213,42 @@ if projectile way fails" dir)))) ;; --------------------------------------------------------------------- - -(defun lsp--haskell-hie-command () - "Comamnd and arguments for launching the inferior hie process. -These are assembled from the customizable variables - `lsp-haskell-process-path-hie' and - `lsp-haskell-process-args-hie'. If the hie executable is - installed via its Makefile, there will be compiler-specific - versions with names like 'hie-8.0.2' or 'hie-8.2.2'." - (append (list lsp-haskell-process-path-hie "--lsp") lsp-haskell-process-args-hie) ) - -;; --------------------------------------------------------------------- - +;; Starting the server and registration with lsp-mode + +(defun lsp-haskell--server-command () + "Command and arguments for launching the inferior language server process. +These are assembled from the customizable variables `lsp-haskell-server-path' +and `lsp-haskell-server-args'." + (append (list lsp-haskell-server-path "--lsp") lsp-haskell-server-args) ) + +;; Register all the language server settings with lsp-mode. +;; Note that customizing these will currently *not* send the updated configuration to the server, +;; users must manually restart. See https://github.com/emacs-lsp/lsp-mode/issues/1174. +(lsp-register-custom-settings '( + ("haskell.formattingProvider" lsp-haskell-formatting-provider) + ("haskell.formatOnImportOn" lsp-haskell-format-on-import-on t) + ("haskell.completionSnippetsOn" lsp-haskell-completion-snippets-on t) + ("haskell.liquidOn" lsp-haskell-liquid-on t) + ("haskell.diagnosticsOnChange" lsp-haskell-diagnostics-on-change t) + ("haskell.maxNumberOfProblems" lsp-haskell-max-number-of-problems) + ("haskell.hlintOn" lsp-haskell-hlint-on t))) + +;; Register the client itself (lsp-register-client (make-lsp--client - :new-connection (lsp-stdio-connection (lambda () (lsp-haskell--hie-command))) + :new-connection (lsp-stdio-connection (lambda () (lsp-haskell--server-command))) :major-modes '(haskell-mode) - :server-id 'hie + ;; This is arbitrary. + :server-id 'lsp-haskell + ;; We need to manually pull out the configuration section and set it. Possibly in + ;; the future lsp-mode will asssociate servers with configuration sections more directly. :initialized-fn (lambda (workspace) (with-lsp-workspace workspace - (lsp-haskell--set-configuration))) - ;; :multi-root t - ;; :initialization-options 'lsp-haskell--make-init-options + (lsp--set-configuration (lsp-configuration-section "haskell")))) + ;; No need to set :language-id, since there isn't one for Haskell and we + ;; don't support multiple languages )) -(defun lsp-haskell--hie-command () - (funcall lsp-haskell-process-wrapper-function (lsp--haskell-hie-command))) - -(cl-defmethod lsp-initialization-options ((_server (eql hie))) - "Initialization options for haskell." - `(:languageServerHaskell ,lsp-haskell--config-options)) - -;; --------------------------------------------------------------------- - -(defun lsp-haskell--set-configuration () - (lsp--set-configuration `(:languageServerHaskell ,lsp-haskell--config-options))) - -(defun lsp-haskell-set-config (name option) - "Set config option NAME to value OPTION in the haskell lsp server." - (puthash name option lsp-haskell--config-options)) - - ;; parseJSON = withObject "Config" $ \v -> do - ;; s <- v .: "languageServerHaskell" - ;; flip (withObject "Config.settings") s $ \o -> Config - ;; <$> o .:? "hlintOn" .!= True - ;; <*> o .:? "maxNumberOfProblems" .!= 100 - ;; <*> o .:? "liquidOn" .!= False - ;; <*> o .:? "completionSnippetsOn" .!= True - -;; ------------------------------------- - -(defun lsp-haskell-set-hlint (val) - "Enable(t)/Disable(nil) running hlint." - (lsp-haskell-set-config "hlintOn" val)) - -(defun lsp-haskell-set-hlint-on () - "Enable running hlint haskell." - (interactive) - (lsp-haskell-set-hlint t) - (lsp-haskell--set-configuration)) - -(defun lsp-haskell-set-hlint-off () - "Disable running hlint." - (interactive) - (lsp-haskell-set-hlint :json-false) - (lsp-haskell--set-configuration)) - -;; ------------------------------------- - -(defun lsp-haskell-set-max-problems (val) - "Set maximum number of problems reported to VAL." - (lsp-haskell-set-config "maxNumberOfProblems" val)) - -(defun lsp-haskell-set-max-number-of-problems (val) - "Set maximum number of problems reported to VAL." - (interactive "nMax number of problems to report: ") - (lsp-haskell-set-max-problems val) - (lsp-haskell--set-configuration)) - -;; ------------------------------------- - -(defun lsp-haskell-set-liquid (val) - "Enable(t)/Disable(nil) running liquid haskell on save." - (lsp-haskell-set-config "liquidOn" val)) - -(defun lsp-haskell-set-liquid-on () - "Enable running liquid haskell on save." - (interactive) - (lsp-haskell-set-liquid t) - (lsp-haskell--set-configuration)) - -(defun lsp-haskell-set-liquid-off () - "Disable running liquid haskell on save." - (interactive) - (lsp-haskell-set-liquid :json-false) - (lsp-haskell--set-configuration)) - -;; ------------------------------------- - -(defun lsp-haskell-set-completion-snippets (val) - "Enable(t)/Disable(nil) providing completion snippets." - (lsp-haskell-set-config "completionSnippetsOn" val)) - -(defun lsp-haskell-set-completion-snippets-on () - "Enable providing completion snippets." - (interactive) - (lsp-haskell-set-completion-snippets t) - (lsp-haskell--set-configuration)) - -(defun lsp-haskell-set-completion-snippets-off () - "Disable providing completion snippets." - (interactive) - (lsp-haskell-set-completion-snippets :json-false) - (lsp-haskell--set-configuration)) - -;; ------------------------------------- - -(defun lsp-haskell-set-formatter (val) - "Set code formatter." - (lsp-haskell-set-config "formattingProvider" val)) - -(defun lsp-haskell-set-formatter-brittany () - "Use brittany." - (interactive) - (lsp-haskell-set-formatter :brittany) - (lsp-haskell--set-configuration)) - -(defun lsp-haskell-set-formatter-floskell () - "Use floskell." - (interactive) - (lsp-haskell-set-formatter :floskell) - (lsp-haskell--set-configuration)) - -(defun lsp-haskell-set-formatter-ormolu () - "Use ormolu." - (interactive) - (lsp-haskell-set-formatter :ormolu) - (lsp-haskell--set-configuration)) - ;; --------------------------------------------------------------------- (provide 'lsp-haskell)