From cb923eadcc33a3667db0d3d5d2876dfbb4d482ae Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Thu, 21 Feb 2019 16:08:27 -0500 Subject: [PATCH] Add basic LSP support Still needs to be documented, but includes support for the following languages: + C/C++/ObjC + Go + Java + Javascript + OCaml + PHP + Python + Ruby + Scala + Swift + HTML/CSS Relevant to #460, #716, #1186 --- init.example.el | 1 + modules/lang/cc/config.el | 23 ++++++++++++++++-- modules/lang/cc/packages.el | 29 +++++++++++------------ modules/lang/go/config.el | 7 ++++-- modules/lang/java/+lsp.el | 10 ++++++++ modules/lang/java/config.el | 3 ++- modules/lang/java/packages.el | 2 ++ modules/lang/javascript/config.el | 5 ++++ modules/lang/javascript/packages.el | 14 +++++++---- modules/lang/ocaml/config.el | 36 +++++++++++++++-------------- modules/lang/php/config.el | 9 +++++--- modules/lang/php/packages.el | 3 ++- modules/lang/python/config.el | 4 ++++ modules/lang/python/packages.el | 3 ++- modules/lang/ruby/config.el | 4 ++++ modules/lang/rust/config.el | 5 +++- modules/lang/scala/config.el | 11 ++++++++- modules/lang/scala/packages.el | 5 +++- modules/lang/swift/config.el | 11 +++++++-- modules/lang/swift/packages.el | 11 +++++---- modules/lang/web/+css.el | 4 ++++ modules/lang/web/+html.el | 4 ++++ modules/tools/lsp/autoload.el | 7 ++++++ modules/tools/lsp/config.el | 21 +++++++++++++++++ modules/tools/lsp/packages.el | 7 ++++++ 25 files changed, 182 insertions(+), 57 deletions(-) create mode 100644 modules/lang/java/+lsp.el create mode 100644 modules/tools/lsp/autoload.el create mode 100644 modules/tools/lsp/config.el create mode 100644 modules/tools/lsp/packages.el diff --git a/init.example.el b/init.example.el index c72d6b6e1..65c7cc9b8 100644 --- a/init.example.el +++ b/init.example.el @@ -67,6 +67,7 @@ ;;editorconfig ; let someone else argue about tabs vs spaces ;;ein ; tame Jupyter notebooks with emacs ;;gist ; interacting with github gists + ;;lsp ;;macos ; MacOS-specific commands ;;magit ; a git porcelain for Emacs ;;make ; run make tasks from Emacs diff --git a/modules/lang/cc/config.el b/modules/lang/cc/config.el index 9d5fe1b19..53488b288 100644 --- a/modules/lang/cc/config.el +++ b/modules/lang/cc/config.el @@ -125,7 +125,7 @@ compilation database is present in the project.") (def-package! irony - :when (featurep! +irony) + :unless (featurep! +lsp) :commands (irony-install-server irony-mode) :preface (setq irony-server-install-prefix (concat doom-etc-dir "irony-server/")) @@ -181,7 +181,7 @@ compilation database is present in the project.") ;; Rtags Support (def-package! rtags - :when (featurep! +rtags) + :unless (featurep! +lsp) :commands rtags-executable-find :preface (setq rtags-install-path (concat doom-etc-dir "rtags/")) @@ -219,3 +219,22 @@ compilation database is present in the project.") (when (featurep 'evil) (add-hook 'rtags-jump-hook #'evil-set-jump)) (add-hook 'rtags-after-find-file-hook #'recenter)) + + +;; +;; LSP + +(def-package! cquery + :when (featurep! +lsp) + :hook ((c-mode c++-mode objc-mode) . +lsp|init-cquery) + :config + (defun +lsp|init-cquery () + (setq-local company-transformers nil) + (setq-local company-lsp-cache-candidates nil) + (condition-case nil + (lsp) + (user-error nil))) + (setq cquery-extra-init-params + '(:index (:comments 2) + :cacheFormat "msgpack" + :completion (:detailedLabel t)))) diff --git a/modules/lang/cc/packages.el b/modules/lang/cc/packages.el index 776b58a3c..cdb6d85d5 100644 --- a/modules/lang/cc/packages.el +++ b/modules/lang/cc/packages.el @@ -12,18 +12,17 @@ (when (featurep! :completion company) (package! company-glsl :recipe (:fetcher github :repo "Kaali/company-glsl")))) -(when (featurep! +irony) - (package! irony) - (package! irony-eldoc) - (when (featurep! :feature syntax-checker) - (package! flycheck-irony)) - (when (featurep! :completion company) - (package! company-irony) - (package! company-irony-c-headers))) - -(when (featurep! +rtags) - (package! rtags) - (when (featurep! :completion ivy) - (package! ivy-rtags)) - (when (featurep! :completion helm) - (package! helm-rtags))) +(if (featurep! +lsp) + (package! cquery) + (when (package! irony) + (package! irony-eldoc) + (when (featurep! :feature syntax-checker) + (package! flycheck-irony)) + (when (featurep! :completion company) + (package! company-irony) + (package! company-irony-c-headers))) + (when (package! rtags) + (when (featurep! :completion ivy) + (package! ivy-rtags)) + (when (featurep! :completion helm) + (package! helm-rtags)))) diff --git a/modules/lang/go/config.el b/modules/lang/go/config.el index 6bde94e93..0952be4e8 100644 --- a/modules/lang/go/config.el +++ b/modules/lang/go/config.el @@ -20,7 +20,9 @@ "gofmt" "goimports")))) - (add-hook 'go-mode-hook #'go-eldoc-setup) + (if (featurep! +lsp) + (add-hook 'go-mode-hook #'+lsp|init) + (add-hook 'go-mode-hook #'go-eldoc-setup)) (map! :map go-mode-map :localleader @@ -57,7 +59,8 @@ (def-package! company-go - :when (featurep! :completion company) + :when (and (featurep! :completion company) + (not (featurep! +lsp))) :after go-mode :config (set-company-backend! 'go-mode 'company-go) diff --git a/modules/lang/java/+lsp.el b/modules/lang/java/+lsp.el new file mode 100644 index 000000000..88898614d --- /dev/null +++ b/modules/lang/java/+lsp.el @@ -0,0 +1,10 @@ +;;; lang/java/+lsp.el -*- lexical-binding: t; -*- +;;;###if (featurep! +lsp) + +(def-package! lsp-java + :after-call java-mode + :init (add-hook 'java-mode-hook #'+lsp|init) + :config + ;; TODO keybinds + ;; TODO treemacs integration (?) + ) diff --git a/modules/lang/java/config.el b/modules/lang/java/config.el index 5e1e07d5b..955651aaf 100644 --- a/modules/lang/java/config.el +++ b/modules/lang/java/config.el @@ -23,7 +23,8 @@ If the depth is 2, the first two directories are removed: net.lissner.game.") (add-hook 'java-mode-hook #'rainbow-delimiters-mode) -(cond ((featurep! +meghanada) (load! "+meghanada")) +(cond ((featurep! +lsp) (load! "+lsp")) + ((featurep! +meghanada) (load! "+meghanada")) ;; TODO lang/java +lsp (lsp-java?) ;; ((featurep! +lsp) (load! "+lsp")) ) diff --git a/modules/lang/java/packages.el b/modules/lang/java/packages.el index 03deb4911..c9a0014a2 100644 --- a/modules/lang/java/packages.el +++ b/modules/lang/java/packages.el @@ -12,3 +12,5 @@ (when (featurep! :completion company) (package! company-emacs-eclim))) +(when (featurep! +lsp) + (package! lsp-java)) diff --git a/modules/lang/javascript/config.el b/modules/lang/javascript/config.el index d125e96e3..31b4804e5 100644 --- a/modules/lang/javascript/config.el +++ b/modules/lang/javascript/config.el @@ -122,7 +122,12 @@ ;; ;; Tools +(when (featurep! +lsp) + (add-hook! (js2-mode rjsx-mode typescript-mode) #'+lsp|init)) + + (def-package! tide + :unless (featurep! +lsp) :defer t :init ;; Don't let hard errors stop the user from opening js files. diff --git a/modules/lang/javascript/packages.el b/modules/lang/javascript/packages.el index e281c0b8f..c7474537c 100644 --- a/modules/lang/javascript/packages.el +++ b/modules/lang/javascript/packages.el @@ -1,17 +1,21 @@ ;; -*- no-byte-compile: t; -*- ;;; lang/javascript/packages.el +;; major modes (package! coffee-mode) -(package! eslintd-fix) (package! js2-mode) +(package! rjsx-mode) +(package! typescript-mode) + +;; tools +(package! eslintd-fix) (package! js2-refactor) (package! nodejs-repl) -(package! rjsx-mode) -(package! skewer-mode) -(package! tide) -(package! typescript-mode) (package! npm-mode) +(package! skewer-mode) (when (featurep! :feature lookup) (package! xref-js2)) +(unless (featurep! +lsp) + (package! tide)) diff --git a/modules/lang/ocaml/config.el b/modules/lang/ocaml/config.el index f5a5df757..87445332c 100644 --- a/modules/lang/ocaml/config.el +++ b/modules/lang/ocaml/config.el @@ -1,5 +1,9 @@ ;;; lang/ocaml/config.el -*- lexical-binding: t; -*- +(when (featurep! +lsp) + (add-hook! (tuareg-mode reason-mode) #'+lsp|init)) + + (after! tuareg ;; tuareg-mode has the prettify symbols itself (set-pretty-symbols! 'tuareg-mode :alist @@ -18,6 +22,7 @@ (def-package! merlin + :unless (featurep! +lsp) :defer t :init (defun +ocaml|init-merlin () @@ -37,7 +42,20 @@ (map! :localleader :map tuareg-mode-map "t" #'merlin-type-enclosing - "a" #'tuareg-find-alternate-file)) + "a" #'tuareg-find-alternate-file) + + (def-package! merlin-eldoc + :hook (merlin-mode . merlin-eldoc-setup)) + + (def-package! merlin-iedit + :when (featurep! :editor multiple-cursors) + :config + (map! :map tuareg-mode-map + :v "R" #'merlin-iedit-occurrences)) + + (def-package! merlin-imenu + :when (featurep! :emacs imenu) + :hook (merlin-mode . merlin-use-merlin-imenu))) (def-package! flycheck-ocaml @@ -53,22 +71,6 @@ (add-hook 'merlin-mode-hook #'+ocaml|init-flycheck)) - (def-package! merlin-eldoc - :hook (merlin-mode . merlin-eldoc-setup)) - - - (def-package! merlin-iedit - :when (featurep! :editor multiple-cursors) - :config - (map! :map tuareg-mode-map - :v "R" #'merlin-iedit-occurrences)) - - - (def-package! merlin-imenu - :when (featurep! :emacs imenu) - :hook (merlin-mode . merlin-use-merlin-imenu)) - - (def-package! utop :when (featurep! :feature eval) :defer t ; loaded by hook below diff --git a/modules/lang/php/config.el b/modules/lang/php/config.el index c14f73f6d..b3e8ae1d8 100644 --- a/modules/lang/php/config.el +++ b/modules/lang/php/config.el @@ -12,9 +12,11 @@ (set-lookup-handlers! 'php-mode :documentation #'php-search-documentation) (set-formatter! 'php-mode #'php-cs-fixer-fix) - ;; `+php-company-backend' uses `company-phpactor', `php-extras-company' or - ;; `company-dabbrev-code', in that order. - (set-company-backend! 'php-mode '+php-company-backend 'company-dabbrev-code) + (if (featurep! +lsp) + (add-hook 'php-mode-hook #'+lsp|init) + ;; `+php-company-backend' uses `company-phpactor', `php-extras-company' or + ;; `company-dabbrev-code', in that order. + (set-company-backend! 'php-mode '+php-company-backend 'company-dabbrev-code)) ;; Use the smallest `sp-max-pair-length' for optimum `smartparens' performance (setq-hook! 'php-mode-hook sp-max-pair-length 5) @@ -32,6 +34,7 @@ (def-package! phpactor + :unless (featurep! +lsp) :after php-mode :config (set-lookup-handlers! 'php-mode diff --git a/modules/lang/php/packages.el b/modules/lang/php/packages.el index b7f9da3bd..61f3ce303 100644 --- a/modules/lang/php/packages.el +++ b/modules/lang/php/packages.el @@ -5,9 +5,10 @@ (package! php-extras :recipe (:fetcher github :repo "arnested/php-extras")) (package! php-mode) (package! php-refactor-mode) -(package! phpactor :recipe (:fetcher github :repo "emacs-php/phpactor.el" :files ("*"))) (package! phpunit) (when (featurep! +hack) (package! hack-mode :recipe (:fetcher github :repo "hhvm/hack-mode"))) +(unless (featurep! +lsp) + (package! phpactor :recipe (:fetcher github :repo "emacs-php/phpactor.el" :files ("*")))) diff --git a/modules/lang/python/config.el b/modules/lang/python/config.el index 30bac7222..717725a4e 100644 --- a/modules/lang/python/config.el +++ b/modules/lang/python/config.el @@ -46,6 +46,9 @@ called.") :for "for" :return "return" :yield "yield") + (when (featurep! +lsp) + (add-hook 'python-mode-hook #'+lsp|init)) + (define-key python-mode-map (kbd "DEL") nil) ; interferes with smartparens (sp-local-pair 'python-mode "'" nil :unless '(sp-point-before-word-p @@ -63,6 +66,7 @@ called.") (def-package! anaconda-mode + :unless (featurep! +lsp) :hook python-mode :init (setq anaconda-mode-installation-directory (concat doom-etc-dir "anaconda/") diff --git a/modules/lang/python/packages.el b/modules/lang/python/packages.el index cfe418184..f492a90d9 100644 --- a/modules/lang/python/packages.el +++ b/modules/lang/python/packages.el @@ -17,6 +17,7 @@ (package! conda)) ;; Programming environment -(when (package! anaconda-mode) +(unless (featurep! +lsp) + (package! anaconda-mode) (when (featurep! :completion company) (package! company-anaconda))) diff --git a/modules/lang/ruby/config.el b/modules/lang/ruby/config.el index 16eb2cbff..cb391bd37 100644 --- a/modules/lang/ruby/config.el +++ b/modules/lang/ruby/config.el @@ -26,6 +26,9 @@ (set-electric! '(ruby-mode enh-ruby-mode) :words '("else" "end" "elsif")) (set-repl-handler! '(ruby-mode enh-ruby-mode) #'inf-ruby) + (when (featurep! +lsp) + (add-hook 'enh-ruby-mode-hook #'+lsp|init)) + (after! company-dabbrev-code (add-to-list 'company-dabbrev-code-modes 'enh-ruby-mode nil #'eq) (add-to-list 'company-dabbrev-code-modes 'ruby-mode nil #'eq)) @@ -42,6 +45,7 @@ (def-package! robe + :unless (featurep! +lsp) :hook (enh-ruby-mode . robe-mode) :config (set-repl-handler! 'enh-ruby-mode #'robe-start) diff --git a/modules/lang/rust/config.el b/modules/lang/rust/config.el index 39ebcb0f0..ff32dd429 100644 --- a/modules/lang/rust/config.el +++ b/modules/lang/rust/config.el @@ -5,6 +5,9 @@ (set-docsets! 'rust-mode "Rust") (setq rust-indent-method-chain t) + (when (featurep! +lsp) + (add-hook 'rust-mode-hook #'+lsp|init)) + (map! :map rust-mode-map :localleader :prefix "b" @@ -15,6 +18,7 @@ (def-package! racer + :unless (featurep! +lsp) :after rust-mode :config (add-hook 'rust-mode-hook #'racer-mode) @@ -27,4 +31,3 @@ :when (featurep! :feature syntax-checker) :after rust-mode :config (add-hook 'rust-mode-hook #'flycheck-rust-setup)) - diff --git a/modules/lang/scala/config.el b/modules/lang/scala/config.el index 29f5ae5f1..96870e0b5 100644 --- a/modules/lang/scala/config.el +++ b/modules/lang/scala/config.el @@ -6,7 +6,10 @@ (add-to-list 'dtrt-indent-hook-mapping-list '(scala-mode c/c++/java scala-indent:step)))) -(after! ensime +(def-package! ensime + :unless (featurep! +lsp) + :defer t + :config (setq ensime-startup-snapshot-notification nil ensime-startup-notification nil ensime-eldoc-hints 'all @@ -23,3 +26,9 @@ (def-package! sbt-mode :after scala-mode :config (set-repl-handler! 'scala-mode #'run-scala)) + + +(def-package! lsp-scala + :when (featurep! +lsp) + :after scala-mode + :init (add-hook 'scala-mode-hook #'+lsp|init)) diff --git a/modules/lang/scala/packages.el b/modules/lang/scala/packages.el index c458f6851..83207fe79 100644 --- a/modules/lang/scala/packages.el +++ b/modules/lang/scala/packages.el @@ -1,6 +1,9 @@ ;; -*- no-byte-compile: t; -*- ;;; lang/scala/packages.el -(package! ensime) (package! sbt-mode) (package! scala-mode) + +(if (featurep! +lsp) + (package! lsp-scala) + (package! ensime)) diff --git a/modules/lang/swift/config.el b/modules/lang/swift/config.el index 6925ab766..a41d85306 100644 --- a/modules/lang/swift/config.el +++ b/modules/lang/swift/config.el @@ -5,14 +5,21 @@ (def-package! flycheck-swift - :when (featurep! :feature syntax-checker) + :when (and (featurep! :feature syntax-checker) + (not (featurep! +lsp))) :after swift-mode :config (flycheck-swift-setup)) (def-package! company-sourcekit - :when (featurep! :completion company) + :when (and (featurep! :completion company) + (not (featurep! +lsp))) :after swift-mode :config (set-company-backend! 'swift-mode '(company-sourcekit company-yasnippet))) + +(def-package! lsp-sourcekit + :when (featurep! +lsp) + :after swift-mode + :init (add-hook 'swift-mode-hook #'+lsp|init)) diff --git a/modules/lang/swift/packages.el b/modules/lang/swift/packages.el index 73e74da91..baec9033f 100644 --- a/modules/lang/swift/packages.el +++ b/modules/lang/swift/packages.el @@ -3,8 +3,9 @@ (package! swift-mode) -(when (featurep! :completion company) - (package! company-sourcekit)) - -(when (featurep! :feature syntax-checker) - (package! flycheck-swift)) +(if (featurep! +lsp) + (package! lsp-sourcekit) + (when (featurep! :completion company) + (package! company-sourcekit)) + (when (featurep! :feature syntax-checker) + (package! flycheck-swift))) diff --git a/modules/lang/web/+css.el b/modules/lang/web/+css.el index fc7d45e75..2c9ea541e 100644 --- a/modules/lang/web/+css.el +++ b/modules/lang/web/+css.el @@ -35,6 +35,10 @@ ;; ;; Tools +(when (featurep! +lsp) + (add-hook! (css-mode sass-mode less-css-mode) #'+lsp|init)) + + (def-package! counsel-css :when (featurep! :completion ivy) :commands counsel-css diff --git a/modules/lang/web/+html.el b/modules/lang/web/+html.el index 06d6cd721..e11894818 100644 --- a/modules/lang/web/+html.el +++ b/modules/lang/web/+html.el @@ -139,3 +139,7 @@ (set-company-backend! 'web-mode 'company-web-html)) (after! slim-mode (set-company-backend! 'slim-mode 'company-web-slim)) + + +(when (featurep! +lsp) + (add-hook! (html-mode web-mode) #'+lsp|init)) diff --git a/modules/tools/lsp/autoload.el b/modules/tools/lsp/autoload.el new file mode 100644 index 000000000..c13d9efd7 --- /dev/null +++ b/modules/tools/lsp/autoload.el @@ -0,0 +1,7 @@ +;;; feature/lsp/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +lsp|init () + "Enable LSP as late as possible, to allow users to customize it via file or +dir local variables." + (add-hook 'hack-local-variables-hook #'lsp nil t)) diff --git a/modules/tools/lsp/config.el b/modules/tools/lsp/config.el new file mode 100644 index 000000000..e2e690289 --- /dev/null +++ b/modules/tools/lsp/config.el @@ -0,0 +1,21 @@ +;;; tools/lsp/config.el -*- lexical-binding: t; -*- + +(def-package! lsp-ui + :hook (lsp-mode . lsp-ui-mode) + :config + (setq lsp-prefer-flymake nil + lsp-ui-doc-max-height 8 + lsp-ui-doc-max-width 35 + lsp-ui-sideline-ignore-duplicate t) + (define-key! lsp-ui-mode-map + [remap xref-find-definitions] #'lsp-ui-peek-find-definitions + [remap xref-find-references] #'lsp-ui-peek-find-references) + (set-lookup-handlers! 'lsp-ui-mode + :definition #'lsp-ui-peek-find-definitions + :references #'lsp-ui-peek-find-references)) + +(def-package! company-lsp + :when (featurep! :completion company) + :after lsp-mode + :config + (set-company-backend! 'lsp-mode 'company-lsp)) diff --git a/modules/tools/lsp/packages.el b/modules/tools/lsp/packages.el new file mode 100644 index 000000000..efcc53bd1 --- /dev/null +++ b/modules/tools/lsp/packages.el @@ -0,0 +1,7 @@ +;; -*- no-byte-compile: t; -*- +;;; tools/lsp/packages.el + +(package! lsp-mode) +(package! lsp-ui) +(when (featurep! :completion company) + (package! company-lsp))