Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add automatic download for haskell-language-server #97

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
87 changes: 75 additions & 12 deletions lsp-haskell.el
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
;;; lsp-haskell.el --- Haskell support for lsp-mode
;;; lsp-haskell.el --- Haskell support for lsp-mode -*- lexical-binding: t; -*-

;; Version: 1.0
;; Package-Requires: ((lsp-mode "3.0") (haskell-mode "1.0"))
;; Package-Requires: ((lsp-mode "7.0") (haskell-mode "1.0"))
;; Keywords: haskell
;; URL: https://github.com/emacs-lsp/lsp-haskell

Expand Down Expand Up @@ -74,14 +74,14 @@
:group 'lsp-haskell
:type 'boolean)
(defcustom lsp-haskell-formatting-provider
"ormolu"
"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")
: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")))

;; ---------------------------------------------------------------------
Expand Down Expand Up @@ -229,7 +229,7 @@ and `lsp-haskell-server-args' and `lsp-haskell-server-wrapper-function'."
(funcall lsp-haskell-server-wrapper-function (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,
;; 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)
Expand All @@ -246,15 +246,58 @@ and `lsp-haskell-server-args' and `lsp-haskell-server-wrapper-function'."
;; https://microsoft.github.io/language-server-protocol/specification#textDocumentItem
(add-to-list 'lsp-language-id-configuration '(haskell-literate-mode . "haskell"))

(defun lsp-haskell--github-system-type-suffix ()
(pcase system-type
('gnu/linux "Linux")
('windows-nt "Windows")
('darwin "macOS")))

(defcustom lsp-haskell-language-server-github-releases-url
"https://github.com/haskell/haskell-language-server/releases/latest/download/"
"GitHub releases url for haskell-language-server and its binaries. Make sure to include the trailing directory slash."
:type 'string
:group 'lsp-haskell
:package-version '(lsp-haskell . "1.0"))

(lsp-dependency
'haskell-language-server-wrapper
'(:system "haskell-language-server-wrapper")
`(:download :url ,(concat lsp-haskell-language-server-github-releases-url
(format "haskell-language-server-wrapper-%s.gz"
(lsp-haskell--github-system-type-suffix)))
:store-path ,(f-join lsp-server-install-dir "haskell" "haskell-language-server-wrapper")
:decompress :gzip
:set-executable? t))

(defun lsp-haskell--project-ghc-version ()
(let ((wrapper-exe (lsp-package-path 'haskell-language-server-wrapper)))
(if wrapper-exe
(s-trim
(shell-command-to-string
(format "%s --project-ghc-version 2>/dev/null" wrapper-exe)))
nil)))

;; Register the client itself
(lsp-register-client
(make-lsp--client
:new-connection (lsp-stdio-connection (lambda () (lsp-haskell--server-command)))
:new-connection (lsp-stdio-connection
(lambda ()
(let* ((proj-ghc-ver (lsp-haskell--project-ghc-version))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For users who've set up the server binary themselves (and maybe set it with lsp-haskell-server-path) it would be nice to not force the use and download of the wrapper.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is something that I've yet to figure out, don't take this code as the intended behaviour! I'm wondering how we should decide to do the automatic download: Check for lsp-haskell-server-path first and see if it exists?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something like that makes sense. So lsp-haskell-server-path would be how you signal "I know what I'm doing, please just get out of my way and run this binary". And if it's nil, we do the automatic downloading.

(server-name (format "haskell-language-server-%s" proj-ghc-ver))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should support bare haskell-language-server (this insists it has the version suffix). People (like me!) who build HLS themselves will just have a binary called haskell-language-server.

Possibly this is a use for lsp-haskell-server-path: it could be used here if non-nil.

(path-exe (executable-find server-name))
(downloaded-exe
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand right, this will actually download the server when we try and start it up. That's not ideal, but I can see the problem: we don't know which version of the server to install until this point, and anyway the user might have multiple projects with different GHC versions.

I think it would be nicer if the downloading happened only in download-server-fn. But then I guess we'd just have to download the whole big archive. I think that's okay, though, it's not so big, and I don't see what else we can do to avoid having people download things on startup...

(lsp-download-path
:store-path (f-join lsp-server-install-dir "haskell" server-name)
:set-executable? t)))
(lsp--info "Project GHC version is %s" proj-ghc-ver)
(if path-exe
(list path-exe "--lsp")
(list downloaded-exe "--lsp")))))

;; Should run under haskell-mode and haskell-literate-mode. We need to list the
;; latter even though it's a derived mode of the former
:major-modes '(haskell-mode haskell-literate-mode)
;; This is arbitrary.
:server-id 'lsp-haskell
:server-id 'haskell-language-server
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this? Does it matter? In particular, I'm pretty sure it doesn't have to match the name used for lsp-depdendency.

;; 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)
Expand All @@ -263,6 +306,26 @@ and `lsp-haskell-server-args' and `lsp-haskell-server-wrapper-function'."
;; This is somewhat irrelevant, but it is listed in lsp-language-id-configuration, so
;; we should set something consistent here.
:language-id "haskell"
:download-server-fn (lambda (_client callback error-callback _update?)
(lsp-package-ensure
'haskell-language-server-wrapper
(lambda ()
(let*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a named function install-server :: Version -> IO () would be good, might be independently useful (users can call it directly if they want).

((proj-ghc-ver (lsp-haskell--project-ghc-version))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this will only DTRT if it's run from the project directory? I'm not sure what the right behavior for install-server should be, especially in a case like this where we effectively have multiple servers. Maybe needing to run it multiple times isn't crazy? Not sure.

(Again, this would be less of an issue if we just downloaded everything up front)

(binary-store-path
(f-join lsp-server-install-dir "haskell" (format "haskell-language-server-%s" proj-ghc-ver))))
(lsp-download-install
callback
error-callback
:url (concat
lsp-haskell-language-server-github-releases-url
(format
"haskell-language-server-%s-%s.gz"
(lsp-haskell--github-system-type-suffix)
proj-ghc-ver))
:store-path binary-store-path
:decompress :gzip)))
error-callback))
;; This is required for completions to works inside language pragma statements
:completion-in-comments? t
))
Expand Down