diff --git a/NEWS.md b/NEWS.md index 2e05aa2c..0b4cb9e3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,10 @@ +# 1.6 (upcoming) + +##### Support workspace/configuration requests ([#326][github#326]) + +Also a new section "Per-project server configuration" in the README.md +should answer some faq's in this regard. + # 1.5 (20/10/2019) Thanks a lot to Felicián Németh, Ingo Lohmar, and everyone else who @@ -197,3 +204,4 @@ and now said bunch of references--> [github#313]: https://github.com/joaotavora/eglot/issues/313 [github#316]: https://github.com/joaotavora/eglot/issues/316 [github#324]: https://github.com/joaotavora/eglot/issues/324 +[github#326]: https://github.com/joaotavora/eglot/issues/326 diff --git a/README.md b/README.md index f0cbc1ea..231266e6 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,54 @@ it be started as a server. Notice the `:autoport` symbol in there: it is replaced dynamically by a local port believed to be vacant, so that the ensuing TCP connection finds a listening server. -## Handling quirky servers +## Per-project server configuration Most servers can guess good defaults and will operate nicely -out-of-the-box, but some need to be configured specially via LSP's -interfaces. If your server has some quirk or non-conformity, it's -possible to extend Eglot to adapt to it. Here's an example on how to +out-of-the-box, but some need to be configured specially via LSP +interfaces. Additionally, in some situations, you may also want a +particular server to operate differently across different projects. + +Per-project settings are realized with Emacs's _directory variables_ +and the Elisp variable `eglot-workspace-configuration`. To make a +particular Python project always enable Pyls's snippet support, put a +file named `.dir-locals.el` in the project's root: + +```lisp +((python-mode + . ((eglot-workspace-configuration + . ((:pyls . (:plugins (:jedi_completion (:include_params t))))))))) +``` + +This tells Emacs that any `python-mode` buffers in that directory +should have a particular buffer-local value of +`eglot-workspace-configuration`. That variable's value should be +_association list_ of _parameter sections_ which are presumably +understood by the server. In this example, we associate section +`pyls` with the parameters object `(:plugins (:jedi_completion +(:include_params t)))`. + +Now, supposing that you also had some Go code in the very same +project, you can configure the Gopls server in the same file. Adding +a section for `go-mode`, the file's contents become: + +```lisp +((python-mode + . ((eglot-workspace-configuration + . ((:pyls . (:plugins (:jedi_completion (:include_params t)))))))) + (go-mode + . ((eglot-workspace-configuration + . ((:gopls . (:usePlaceholders t))))))) +``` + +If you can't afford an actual `.dir-locals.el` file, or if managing +these files becomes cumbersome, the Emacs manual teaches you +programmatic ways to leverage per-directory local variables. + +## Handling quirky servers + +Some servers need even more special hand-holding to operate correctly. +If your server has some quirk or non-conformity, it's possible to +extend Eglot via Elisp to adapt to it. Here's an example on how to get [cquery][cquery] working: ```lisp @@ -283,7 +325,7 @@ eglot-shutdown`. - [ ] workspace/workspaceFolders (3.6.0) - [ ] workspace/didChangeWorkspaceFolders (3.6.0) - [x] workspace/didChangeConfiguration -- [ ] workspace/configuration (3.6.0) +- [x] workspace/configuration (3.6.0) - [x] workspace/didChangeWatchedFiles - [x] workspace/symbol - [x] workspace/executeCommand diff --git a/eglot.el b/eglot.el index 75ce9376..b9496e16 100644 --- a/eglot.el +++ b/eglot.el @@ -218,6 +218,7 @@ let the buffer grow forever." (defvar eglot--lsp-interface-alist `( (CodeAction (:title) (:kind :diagnostics :edit :command)) + (ConfigurationItem () (:scopeUri :section)) (Command (:title :command) (:arguments)) (CompletionItem (:label) (:kind :detail :documentation :deprecated :preselect @@ -474,7 +475,8 @@ treated as in `eglot-dbind'." :executeCommand `(:dynamicRegistration :json-false) :workspaceEdit `(:documentChanges :json-false) :didChangeWatchedFiles `(:dynamicRegistration t) - :symbol `(:dynamicRegistration :json-false)) + :symbol `(:dynamicRegistration :json-false) + :configuration t) :textDocument (list :synchronization (list @@ -1655,9 +1657,9 @@ Records BEG, END and PRE-CHANGE-LENGTH locally." '((name . eglot--signal-textDocument/didChange))) (defvar-local eglot-workspace-configuration () - "Alist of (SETTING . VALUE) entries configuring the LSP server. -Setting should be a keyword, value can be any value that can be -converted to JSON.") + "Alist of (SECTION . VALUE) entries configuring the LSP server. +SECTION should be a keyword or a string, value can be anything +that can be converted to JSON.") (put 'eglot-workspace-configuration 'safe-local-variable 'listp) @@ -1669,12 +1671,34 @@ When called interactively, use the currently active server" server :workspace/didChangeConfiguration (list :settings - (cl-loop for (k . v) in eglot-workspace-configuration - collect (if (keywordp k) - k - (intern (format ":%s" k))) + (cl-loop for (section . v) in eglot-workspace-configuration + collect (if (keywordp section) + section + (intern (format ":%s" section))) collect v)))) +(cl-defmethod eglot-handle-request + (server (_method (eql workspace/configuration)) &key items) + "Handle server request workspace/configuration." + (apply #'vector + (mapcar + (eglot--lambda ((ConfigurationItem) scopeUri section) + (let* ((path (eglot--uri-to-path scopeUri))) + (when (file-directory-p path) + (with-temp-buffer + (let ((default-directory path)) + (setq-local major-mode (eglot--major-mode server)) + (hack-dir-local-variables-non-file-buffer) + (alist-get section eglot-workspace-configuration + nil nil + (lambda (wsection section) + (string= + (if (keywordp wsection) + (substring (symbol-name wsection) 1) + wsection) + section)))))))) + items))) + (defun eglot--signal-textDocument/didChange () "Send textDocument/didChange to server." (when eglot--recent-changes