[clojure] Add lsp server support

This commit is contained in:
Maximilian Wolff 2020-11-24 21:35:31 +01:00
parent 77d84b14e0
commit 6d39cd5548
No known key found for this signature in database
GPG key ID: 2DD07025BFDBD89A
5 changed files with 140 additions and 84 deletions

View file

@ -13,6 +13,7 @@
- [[#install][Install]]
- [[#add-the-clojure-layer-manually][Add the Clojure Layer manually]]
- [[#pretty-symbols][Pretty Symbols]]
- [[#optional-lsp-server][Optional LSP server]]
- [[#enabling-automatic-linting][Enabling Automatic Linting]]
- [[#enable-clj-kondo-linter][Enable clj-kondo linter]]
- [[#enable-joker-linter][Enable joker linter]]
@ -75,10 +76,11 @@ and a full suite of tooling for Clojure development.
The following Spacemacs layers should also be added for a complete experience.
- auto-completion
- syntax-checking (provides flycheck for linter support)
- LSP
*** Other optional features
- Refactoring via [[https://github.com/clojure-emacs/clj-refactor.el][clj-refactor]]
- Debugging with [[https://github.com/clojure-emacs/sayid][sayid]] (beta)
- Debugging with [[https://github.com/clojure-emacs/sayid][sayid]]
*** References
- [[https://docs.cider.mx/cider/][CIDER documentation]]
@ -111,6 +113,33 @@ Or set this variable when loading the configuration layer:
'((clojure :variables clojure-enable-fancify-symbols t)))
#+END_SRC
** Optional LSP server
Traditionally the clojure developing environment is working
exclusively with an external server called CIDER.
This has some drawbacks especially that most features require
a working REPL session in the background. Newer layers work
with a standard LSP server providing standard
bindings for most features. In addition =lsp-UI= provides a more
modern UI for visualising linter results and providing
code actions.
The server is automatically activated when you have the =lsp-layer=
installed in your dotfile. Alternatively you can force a
certain backend by setting the =clojure-backend= variable
in your dotfile:
#+BEGIN_SRC emacs-lisp
(setq-default dotspacemacs-configuration-layers
'((clojure :variables clojure-backend 'lsp)))
#+END_SRC
The server is started automatically when a supported file is opened.
It will also provide a flycheck integration via =lsp-flycheck= which
will run the configured linter from your project config.
See [[https://github.com/snoe/clojure-lsp][here]] for details. The server is installed automatically
in your emacs directory however if this does not work
it can also be installed manually from [[https://github.com/snoe/clojure-lsp/releases/latest/download/clojure-lsp][here]].
** Enabling Automatic Linting
[[https://github.com/borkdude/clj-kondo][clj-kondo]], [[https://github.com/candid82/joker][joker]] and [[https://github.com/clojure-emacs/squiggly-clojure][squiggly-clojure]] provide automated linting via =flycheck=.
These packages are disabled by default as they require the relevant linter binaries

View file

@ -28,3 +28,8 @@
(defvar clojure-enable-linters nil
"If non-nil, enable clojure linters.")
(defvar clojure-backend nil
"The backend to use for IDE features.
Possible values are `lsp' and `cider'.
If `nil' then 'cider` is the default backend unless `lsp' layer is used")

View file

@ -9,30 +9,43 @@
;;
;;; License: GPLv3
(defun spacemacs//clojure-backend ()
"Return selected backend."
(if clojure-backend
clojure-backend
(cond
((configuration-layer/layer-used-p 'lsp) 'lsp)
(t 'cider))))
(defun spacemacs//clojure-setup-backend ()
"Conditionally setup clojure backend."
(pcase (spacemacs//clojure-backend)
(`lsp (lsp))))
(defun clojure/fancify-symbols (mode)
"Pretty symbols for Clojure's anonymous functions and sets,
like (λ [a] (+ a 5)), ƒ(+ % 5), and {2 4 6}."
(font-lock-add-keywords mode
`(("(\\(fn\\)[[[:space:]]"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "λ")
nil)))
("(\\(partial\\)[[[:space:]]"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "Ƥ")
nil)))
("(\\(comp\\)[[[:space:]]"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "")
nil)))
("\\(#\\)("
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "ƒ")
nil)))
("\\(#\\){"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "")
nil))))))
`(("(\\(fn\\)[[[:space:]]"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "λ")
nil)))
("(\\(partial\\)[[[:space:]]"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "Ƥ")
nil)))
("(\\(comp\\)[[[:space:]]"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "")
nil)))
("\\(#\\)("
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "ƒ")
nil)))
("\\(#\\){"
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "")
nil))))))
(defun spacemacs/cider-eval-sexp-end-of-line ()
@ -54,7 +67,7 @@
(indent-region pt-max (point))
(cider-repl-return)
(with-selected-window (get-buffer-window (cider-current-connection))
(goto-char (point-max))))))
(goto-char (point-max))))))
(defun spacemacs/cider-send-last-sexp-to-repl ()
"Send last sexp to REPL and evaluate it without changing

View file

@ -0,0 +1,14 @@
;;; layers.el --- Clojure Layer layers File for Spacemacs
;;
;; Copyright (c) 2012-2020 Sylvain Benner & Contributors
;;
;; Author: Maximilian Wolff <smile13241324@gmail.com>
;; URL: https://github.com/syl20bnr/spacemacs
;;
;; This file is not part of GNU Emacs.
;;
;;; License: GPLv3
(when (and (boundp 'clojure-backend)
(eq clojure-backend 'lsp))
(configuration-layer/declare-layer-dependencies '(lsp)))

View file

@ -9,36 +9,36 @@
;;
;;; License: GPLv3
(setq clojure-packages
'(
cider
cider-eval-sexp-fu
(clj-refactor :toggle clojure-enable-clj-refactor)
(helm-cider :toggle (configuration-layer/layer-used-p 'helm))
clojure-mode
(clojure-snippets :toggle (configuration-layer/layer-used-p 'auto-completion))
company
eldoc
evil-cleverparens
flycheck
(flycheck-clojure :toggle (memq 'squiggly (if (listp clojure-enable-linters)
clojure-enable-linters
(list clojure-enable-linters))))
(flycheck-clj-kondo :toggle (memq 'clj-kondo (if (listp clojure-enable-linters)
clojure-enable-linters
(list clojure-enable-linters))))
(flycheck-joker :toggle (memq 'joker (if (listp clojure-enable-linters)
clojure-enable-linters
(list clojure-enable-linters))))
ggtags
counsel-gtags
helm-gtags
org
parinfer
popwin
(sayid :toggle clojure-enable-sayid)
smartparens
subword))
(defconst clojure-packages
'(
cider
cider-eval-sexp-fu
(clj-refactor :toggle clojure-enable-clj-refactor)
(helm-cider :toggle (configuration-layer/layer-used-p 'helm))
clojure-mode
(clojure-snippets :toggle (configuration-layer/layer-used-p 'auto-completion))
company
eldoc
evil-cleverparens
flycheck
(flycheck-clojure :toggle (memq 'squiggly (if (listp clojure-enable-linters)
clojure-enable-linters
(list clojure-enable-linters))))
(flycheck-clj-kondo :toggle (memq 'clj-kondo (if (listp clojure-enable-linters)
clojure-enable-linters
(list clojure-enable-linters))))
(flycheck-joker :toggle (memq 'joker (if (listp clojure-enable-linters)
clojure-enable-linters
(list clojure-enable-linters))))
ggtags
counsel-gtags
helm-gtags
org
parinfer
popwin
(sayid :toggle clojure-enable-sayid)
smartparens
subword))
(defun clojure/init-cider ()
@ -64,14 +64,11 @@
;; TODO: having this work for cider-macroexpansion-mode would be nice,
;; but the problem is that it uses clojure-mode as its major-mode
(let ((cider--key-binding-prefixes
'(("m=" . "format")
("m=e" . "edn")
'(("m=e" . "edn")
("md" . "debug")
("mdv" . "inspect values")
("me" . "evaluation")
("mep" . "pretty print")
("mg" . "goto")
("mh" . "documentation")
("mm" . "manage repls")
("mml" . "link session")
("mmS" . "sibling sessions")
@ -81,31 +78,39 @@
("msc" . "connect external repl")
("msj" . "jack-in")
("msq" . "quit/restart repl")
("mt" . "test")
("mt" . "test")))
(cider--key-binding-non-lsp-prefixes
'(("m=" . "format")
("mg" . "goto") ;; no lsp
("mh" . "documentation")
("mT" . "toggle"))))
(spacemacs|forall-clojure-modes m
(mapc (lambda (x) (spacemacs/declare-prefix-for-mode
m (car x) (cdr x)))
cider--key-binding-prefixes)
(unless (eq (spacemacs//clojure-backend) 'lsp)
(mapc (lambda (x) (spacemacs/declare-prefix-for-mode
m (car x) (cdr x)))
cider--key-binding-non-lsp-prefixes)
(spacemacs/set-leader-keys-for-major-mode m
"hh" 'cider-doc
"=r" 'cider-format-region
"ge" 'cider-jump-to-compilation-error
"gr" 'cider-find-resource
"gs" 'cider-browse-spec
"gS" 'cider-browse-spec-all))
(spacemacs/set-leader-keys-for-major-mode m
;; shortcuts
"'" 'sesman-start
;; help / documentation
"ha" 'cider-apropos
"hc" 'cider-cheatsheet
"hd" 'cider-clojuredocs
"hh" 'cider-doc
"hj" 'cider-javadoc
"hn" 'cider-browse-ns
"hN" 'cider-browse-ns-all
"hs" 'cider-browse-spec
"hS" 'cider-browse-spec-all
;; evaluate in source code buffer
"e;" 'cider-eval-defun-to-comment
"e$" 'spacemacs/cider-eval-sexp-end-of-line
@ -128,25 +133,17 @@
"ev" 'cider-eval-sexp-at-point
"eV" 'cider-eval-sexp-up-to-point
"ew" 'cider-eval-last-sexp-and-replace
;; format code style
"==" 'cider-format-buffer
"=eb" 'cider-format-edn-buffer
"=ee" 'cider-format-edn-last-sexp
"=er" 'cider-format-edn-region
"=f" 'cider-format-defun
"=r" 'cider-format-region
;; goto
"gb" 'cider-pop-back
"gc" 'cider-classpath
"gg" 'spacemacs/clj-find-var
"ge" 'cider-jump-to-compilation-error
"gn" 'cider-find-ns
"gr" 'cider-find-resource
"gs" 'cider-browse-spec
"gS" 'cider-browse-spec-all
;; manage cider connections / sesman
"mb" 'sesman-browser
"mi" 'sesman-info
@ -160,7 +157,6 @@
"mSj" 'cider-connect-sibling-clj
"mSs" 'cider-connect-sibling-cljs
"ms" 'sesman-start
;; send code - spacemacs convention
"sa" (if (eq m 'cider-repl-mode)
'cider-switch-to-last-clojure-buffer
@ -190,13 +186,11 @@
"sr" 'spacemacs/cider-send-region-to-repl
"sR" 'spacemacs/cider-send-region-to-repl-focus
"su" 'cider-repl-require-repl-utils
;; toggle options
"Te" 'cider-enlighten-mode
"Tf" 'spacemacs/cider-toggle-repl-font-locking
"Tp" 'spacemacs/cider-toggle-repl-pretty-printing
"Tt" 'cider-auto-test-mode
;; cider-tests
"ta" 'spacemacs/cider-test-run-all-tests
"tb" 'cider-test-show-report
@ -205,7 +199,6 @@
"tp" 'spacemacs/cider-test-run-project-tests
"tr" 'spacemacs/cider-test-rerun-failed-tests
"tt" 'spacemacs/cider-test-run-focused-test
;; cider-debug and inspect
"db" 'cider-debug-defun-at-point
"de" 'spacemacs/cider-display-error-buffer
@ -214,7 +207,6 @@
"dvi" 'cider-inspect
"dvl" 'cider-inspect-last-result
"dvv" 'cider-inspect-expr
;; profile
"p+" 'cider-profile-samples
"pc" 'cider-profile-clear
@ -224,7 +216,6 @@
"pt" 'cider-profile-toggle
"pv" 'cider-profile-var-profiled-p)))
;; cider-repl-mode only
(spacemacs/set-leader-keys-for-major-mode 'cider-repl-mode
"," 'cider-repl-handle-shortcut)
@ -351,10 +342,10 @@
(add-to-list 'auto-mode-alist '("\\.boot\\'" . clojure-mode))
;; This regexp matches shebang expressions like `#!/usr/bin/env boot'
(add-to-list 'magic-mode-alist '("#!.*boot\\s-*$" . clojure-mode))
(add-hook 'clojure-mode-hook #'spacemacs//clojure-setup-backend)
;; Define all the prefixes here, although most of them apply only to bindings in clj-refactor
(let ((clj-refactor--key-binding-prefixes
'(("mr" . "refactor")
("mra" . "add")
'(("mra" . "add")
("mrc" . "cycle/clean/convert")
("mrd" . "destructure")
("mre" . "extract/expand")
@ -366,11 +357,17 @@
("mrr" . "remove/rename/replace")
("mrs" . "show/sort/stop")
("mrt" . "thread")
("mru" . "unwind/update"))))
("mru" . "unwind/update")))
(clj-refactor--key-binding-non-lsp-prefixes
'(("mr" . "refactor"))))
(spacemacs|forall-clojure-modes m
(mapc (lambda (x) (spacemacs/declare-prefix-for-mode
m (car x) (cdr x)))
clj-refactor--key-binding-prefixes)
(unless (eq (spacemacs//clojure-backend) 'lsp)
(mapc (lambda (x) (spacemacs/declare-prefix-for-mode
m (car x) (cdr x)))
clj-refactor--key-binding-non-lsp-prefixes))
(spacemacs/set-leader-keys-for-major-mode m
"=l" 'clojure-align
"ran" 'clojure-insert-ns-form
@ -430,9 +427,7 @@
(defun clojure/post-init-company ()
(spacemacs|add-company-backends
:backends company-capf
:modes
cider-mode
cider-repl-mode))
:modes clojure-mode clojurec-mode clojurescript-mode clojurex-mode cide-clojure-interaction-mode cider-mode cider-repl-mode))
(defun clojure/post-init-ggtags ()
(add-hook 'clojure-mode-local-vars-hook #'spacemacs/ggtags-mode-enable))