Skip to content

Commit

Permalink
Fix #326: support workspace/configuration
Browse files Browse the repository at this point in the history
This helps users configure servers such as Gopls, which doesn't
support didChangeConfiguration signals.

* README.md (Per-project server configuration): New section.

* eglot.el (eglot-workspace-configuration): Fix docstring.
(eglot-signal-didChangeConfiguration): Rename a variable.
(eglot-handle-request workspace/configuration): New request
handler.
  • Loading branch information
joaotavora committed Oct 26, 2019
1 parent 32ba9d0 commit b406818
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 13 deletions.
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
40 changes: 32 additions & 8 deletions eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down

0 comments on commit b406818

Please sign in to comment.