-
Notifications
You must be signed in to change notification settings - Fork 200
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
Per #590: Handle keywords in nested maps in configuration #790
Conversation
In a now-deleted comment you wrote:
Personally I don't think it's that Ugh, as long as it is cleanly documented somehwere. In Eglot we do use plists for JSON objects and elisp sequences for JSON arrays. We could choose other methods, but I happened to choose this one which I think is the less verbose one (prints better, too, certainly better than the hash-table equivalent). Does you PR change any of this, or does it mostly fix the bugs and improve the documenation? |
f1d013b
to
dff3b0a
Compare
Yeah, I later deleted that comment since I came to realize it wasn't that Ugh. I pushed some new changes. The first commit are for the variant which recursively (instead of just at the top level) rewrites I think just keeping the value untouched and referring to I've added a helper Have a look and feel free to change anything. |
Ah, I missed that
Hmm, so perhaps the recursive rewrite should be kept? |
IIRC the behavior of both these serializers depends very much on additional arguments, in the first case, and special variables in the second case. It should be possible to make these two functions behave equivalently. |
7bf7daf
to
1a29ba2
Compare
@joaotavora Sorry, I got a bit confused yesterday. Now I went with a run-time check of the structure as you initially suggested. I think it looks better now. |
@skangas please have a look as well. The configuration you used here #590 (comment) previously encoded as |
Ah, actually it does work with |
Hmm, there are five separate commits here. Could/should any of these commits be squashed? For example, the one that says "fixup!" and the one that documents this in |
Yeah, if this is a self-contained improvement that it doesn't make sense to split, please to squash as usual. Make history much nicer to read. OTOH if some early commit is laying groundwork for multiple possible other commits, it makes sense to isolate it. |
The commits are a bit sloppy. I didn't want to spend too much time on this before getting some feedback from you. I can clean it up once we agree on the diffs. Ideally the test should include |
Makes total sense 👍 to me. Will try to find time soon. |
eglot-tests.el
Outdated
(let ((value (eglot--check-conf '((:k1 (:k2 . [a "b" 1 2.0]) (k3 . v1))))) | ||
(expected (string-replace "'" "\"" "{'k1':{'k2':['a','b',1,2.0],'k3':'v1'}}"))) | ||
(should (equal (json-encode value) expected)) | ||
(should (equal (json-serialize value) expected)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm not sure we should be testing the return value of json-encode
and json-serialize
here. Should we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The important test is validating that whatever goes through eglot--check-conf
without error should encode to the same value regardless of json-serialize
being available or not. I also wrote:
Ideally the test should include
t nil :json-false
and calljsonrpc--json-encode
twice. Once to triggerjson-serialize
, and once withjson-serialize
uninterned, sojson-encode
is used.
... but it's a bit awkward. eglot--check-conf
may perhaps better belong in jsonrpc
so that internals aren't leaked, but I haven't looked into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eglot--check-conf
can perhaps be called jsonrpc-safe-encode-transform
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like I'm missing something: why should jsonrpc.el
have code that relates to how eglot
represents it's configuration. Does that really make sense?
BTW, we should probably add an fboundp
somewhere, in case json-serialize
is not available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like I'm missing something: why should
jsonrpc.el
have code that relates to howeglot
represents it's configuration. Does that really make sense?
eglot--check-conf
is really checking that a particular value is structured in a way so that it encodes the same regardless of json-serialize
being available. It's should allow what's common between json-encode
and json-serialize
(or transform the value such that it becomes that). It could make sense to send any user input through it, but probably it makes more sense to keep it as is.
BTW, we should probably add an
fboundp
somewhere, in casejson-serialize
is not available.
There is already (skip-unless (fboundp 'json-serialize))
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another point why it could belong in jsonrpc, is that the chosen representation is really based on implementation details of jsonrpc, which eglot should not need to know about. What do you think @joaotavora?
eglot-tests.el
Outdated
(ert-deftest eglot--compare-json-serializers () | ||
(skip-unless (fboundp 'json-serialize)) | ||
(let ((value (eglot--check-conf '((:k1 (:k2 . [a "b" 1 2.0]) (k3 . v1))))) | ||
(expected (string-replace "'" "\"" "{'k1':{'k2':['a','b',1,2.0],'k3':'v1'}}"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there more test cases that are interesting to test? Test coverage could be better: we don't currently test the user-error
in eglot--check-conf
.
For example, how about in addition to this large example up also create smaller, more focused tests that focus on one of the various branches of eglot--check-conf
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think stuffing all the cases in a single example is fine (perhaps the existing one can be made to look a bit better), but I can change if you prefer a bunch of smaller ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally prefer several smaller ones, even if it means some repetition: IME, it is then easier to add new cases, and it is easier to compare the various cases to the various branches.
But yes, it is partly a matter of style, so I don't want to insist too strongly on it. Maybe @joaotavora has an opinion.
eglot.el
Outdated
((listp value) | ||
(cl-loop for elem in value | ||
unless (consp elem) do | ||
(user-error "invalid alist entry `%S' (expected cons)" elem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Probably better as
"Invalid alist entry in configuration: %S"
- Shouldn't this be just an
error
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Probably better as
"Invalid alist entry in configuration: %S"
Sure! Perhaps the string should even be prefixed with eglot:
?
- Shouldn't this be just an
error
?
It'll be signalled when the eglot-workspace-configuration
is invalid, such as when the user is using a plist instead of an alist. I think user-error
is appropriate, can you elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about just using eglot--error
? I think then we get the prefix for free.
AFAIK, the only reason for using user-error
is to not get a stacktrace when debug-on-error
is t. However, I think it would be nice to get a stacktrace in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, I wasn't aware of that 👍
eglot.el
Outdated
collect (let ((k (car elem))) | ||
(if (keywordp k) k (intern (format ":%s" k)))) | ||
collect (eglot--check-conf (cdr elem)))) | ||
(t (user-error "invalid configuration value `%S'" value)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Probably better as
"Invalid configuration value: %S"
- Shouldn't this be just an
error
?
(go-mode | ||
. ((eglot-workspace-configuration | ||
. ((:gopls . (:usePlaceholders t))))))) | ||
. ((:gopls (:usePlaceholders . t))))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding the examples from the README as tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. In some form, either in the big test or as separate ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
Could we also have a test for the case you mentioned in #590: |
@fbergroth was there no need to have this merged in the end? |
I think the changes in eglot.el are useful, but João never got back to this, and I didn't want to pick it up after several months. |
Whoops, sorry indeed that's my bad. The problem is that all the discussion is out of my mental cache now. But on a brief overview, it does look useful and sane. @doolio can you perhaps state your need/use case for this? What caught your attention in this PR? |
Thank you both for the quick responses! I came across this PR when searching through the repository for any mention of It seems across various issues, PRs and discussions that users have struggled to set this variable correctly. I find several variations and attempts to do so. I thought this PR was an effort to simplify the setting of this variable for users. Is that correct? I believe to configure that variable correctly we need to add a plist as the It seems one can use symbols or keyword symbols (i.e. :symbol) for the My attempt so far to configure the
Is using If there is a wiki page under this project I'd be happy to add a Thanks for your time and this package. |
eglot.el
Outdated
unless (consp elem) do | ||
(user-error "invalid alist entry `%S' (expected cons)" elem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where it the case of a plist handled? It seems to me that a plist would always throw this error.
. ((:pyls . (:plugins (:jedi_completion (:include_params t))))))))) | ||
. ((:pyls (:plugins (:jedi_completion (:include_params . t))))))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you include a dot after :include_params
? AFAIU, the only place a cons cell like this can appear in the Elisp -> JSON translation is as an entry of an alist, and this cons appears as an value in a plist.
In fact, (json-encode '(:jedi_completion (:include_params . t)))
gives an error and so does json-serialize
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(json-encode '(:jedi_completion (:include_params . t)))
Thanks for this snippet. With this without the erroneous dot, it seems json-serialize
encodes the JSON keywords as expected but json-encode
only some namely :true
and :[]
.
(json-encode '(:autopep8 (:enabled t))) ; "{\"autopep8\":{\"enabled\":true}}"
(json-encode '(:autopep8 (:enabled nil))) ; "{\"autopep8\":{\"enabled\":null}}"
(json-encode '(:autopep8 (:enabled :null))) ; "{\"autopep8\":{\"enabled\":\"null\"}}"
(json-encode '(:autopep8 (:enabled :false))) ; "{\"autopep8\":{\"enabled\":\"false\"}}"
(json-encode '(:autopep8 (:enabled []))) ; "{\"autopep8\":{\"enabled\":[]}"
(json-serialize '(:autopep8 (:enabled t))) ; "{\"autopep8\":{\"enabled\":true}}"
(json-serialize '(:autopep8 (:enabled nil))) ; "{\"autopep8\":{\"enabled\":{}}}"
(json-serialize '(:autopep8 (:enabled :null))) ; "{\"autopep8\":{\"enabled\":null}}"
(json-serialize '(:autopep8 (:enabled :false))) ; "{\"autopep8\":{\"enabled\":false}}"
(json-serialize '(:autopep8 (:enabled []))) ; "{\"autopep8\":{\"enabled\":[]}"
I believe jsonrpc
uses json-encode
as an alias to json-serialize
so I don't quite understand why :null
and :false
were not acceptable in my above 'dir-locals.el
example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because jsonrpc--json-encode
modifies what Elisp values these functions use for JSON null and false.
To debug your server configuration, you should use jsonrpc--json-encode
only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@doolio in case you didn't know this, the behaviour of json-encode
is affected by multiple dynamic variable bindings.
I think @astoff is also trying to tell you this.
jsonrpc.el
sets these variables. I don't know if you're taking this into account in your tests, or what these tests are supposed to mean. Eglot does not use json-serialize
or json-encode
directly, rather jsonrpc.el
.
It's jsonrpc.el
which sometimes uses one and sometimes uses the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:[]
is not a keyword, it gets read as the keyword :
followed by the empty vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:[]
is not a keyword, it gets read as the keyword:
followed by the empty vector.
Yes, I corrected the above tables. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joaotavora any thoughts on this comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is about how the keys of the JSON objects are handled, so this comment seems a bit unrelated to me. But it is perhaps a bit of a problem that the values in the JSON object have to be provided in the way jsonrcp--json-serialize
expects them, because that function is convenient for the Lisp programmer but not necessarily the most user-friendly.
For the end user, it would maybe be more friendly to allow the following translations:
JSON false <-> :false
JSON true <-> t or :true
JSON null <-> nil or :null
JSON {} <-> I don't know, maybe :{}?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is about how the keys of the JSON objects are handled, so this comment seems a bit unrelated to me.
True, I could open a new issue under jsonrpc.el
if João feels it is more appropriate. I added it here as the discussion was fresh in people's minds and so seemed appropriate.
The translations I suggest above I based on this Emacs manual page.
@doolio While this PR fixes some corner cases, I don't think it simplifies the overall process for the user — or at least it will not change anything for your attempted configuration. But what do you think of this suggestion? |
Also tweak eglot-show-workspace-configuration a bit. * README.md (Workspace configuration): Rework. * eglot.el (eglot-show-workspace-configuration): Rework. (eglot--workspace-configuration-plist): New helper.
Right, and this special requirement on the top level can only add to confusion, right? I added some specific comments to the new README directly to in the commit. Here's one a more "global" comment: The entire "Format" section should not be needed. Instead, Eglot should be such that the following is all you need to say:
|
That's if you call
This is why plists are recommended. As far as I understand those pitfalls are only when you put alists in the sub-section structure. |
Sorry, my mistake, I had some parts of this PR evaled. |
If we allow
|
Yes that would be welcome.
Really? Do they accept the same configuration settings exactly? |
By the way, there's no good reason to use keyword keys in those plists, and if we don't then we protect the user from even knowing about those pitfalls. So, concretely, say this in the simple example:
I don't understand what you mean here: what is more complex?
Do you agree
Yes, or at least for the most part. Just need to change the top level key from |
Yes, from what I can tell you simply need to change the top level key to |
Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist.
Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. Update examples to use pylsp. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist. (eglot-workspace-configuration): Document variable.
@fbergroth #1033 has been merged (sort of), meaning that |
Thanks. Perhaps it's more useful to just do normalization rather than validation (to handle dotted configuration section lookups). I'll close this again for now. |
…pace configurations Also see joaotavora/eglot#790, joaotavora/eglot#1033.
…out workspace configuration * README.md (way): Adjust. * eglot.el (json): Don't require needlessly. (eglot-show-workspace-configuration): Don't depend on json-mode.
… Allow eglot-workspace-configuration to be a plist Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. Update examples to use pylsp. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist. (eglot-workspace-configuration): Document variable.
…pace configurations Also see joaotavora/eglot#790, joaotavora/eglot#1033.
…out workspace configuration * README.md (way): Adjust. * eglot.el (json): Don't require needlessly. (eglot-show-workspace-configuration): Don't depend on json-mode.
… Allow eglot-workspace-configuration to be a plist Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. Update examples to use pylsp. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist. (eglot-workspace-configuration): Document variable.
Also see #790, #1033. #590: joaotavora/eglot#590 #790: joaotavora/eglot#790 #1033: joaotavora/eglot#1033
* README.md (way): Adjust. * eglot.el (json): Don't require needlessly. (eglot-show-workspace-configuration): Don't depend on json-mode. #790: joaotavora/eglot#790 #590: joaotavora/eglot#590
Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. Update examples to use pylsp. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist. (eglot-workspace-configuration): Document variable. #590: joaotavora/eglot#590 #790: joaotavora/eglot#790 #1033: joaotavora/eglot#1033
Also see joaotavora/eglot#790, joaotavora/eglot#1033. GitHub-reference: per joaotavora/eglot#590
* README.md (way): Adjust. * eglot.el (json): Don't require needlessly. (eglot-show-workspace-configuration): Don't depend on json-mode. GitHub-reference: per joaotavora/eglot#790 GitHub-reference: per joaotavora/eglot#590
Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. Update examples to use pylsp. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist. (eglot-workspace-configuration): Document variable. GitHub-reference: per joaotavora/eglot#590 GitHub-reference: per joaotavora/eglot#790 GitHub-reference: per joaotavora/eglot#1033
Also see joaotavora/eglot#790, joaotavora/eglot#1033. GitHub-reference: per joaotavora/eglot#590
* README.md (way): Adjust. * eglot.el (json): Don't require needlessly. (eglot-show-workspace-configuration): Don't depend on json-mode. GitHub-reference: per joaotavora/eglot#790 GitHub-reference: per joaotavora/eglot#590
Suggested-by: Augusto Stoffel <[email protected]> * NEWS.md: Mention change. * README.md (eglot-workspace-configuration): Update yet again. Update examples to use pylsp. * eglot.el (eglot--workspace-configuration-plist): Noop if already a plist. (eglot-handle-request workspace/configuration): Use eglot--workspace-configuration-plist. (eglot-workspace-configuration): Document variable. GitHub-reference: per joaotavora/eglot#590 GitHub-reference: per joaotavora/eglot#790 GitHub-reference: per joaotavora/eglot#1033
(eglot-signal-didChangeConfiguration): Dump configuration recursively