Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 20 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
253 changes: 94 additions & 159 deletions lsp-haskell.el
Original file line number Diff line number Diff line change
Expand Up @@ -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\" )
Expand All @@ -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

Expand Down Expand Up @@ -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."
Expand All @@ -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)
Expand Down