* [ELPA] New package: eglot @ 2018-05-10 22:34 João Távora 2018-05-11 6:19 ` Eli Zaretskii 2018-05-12 15:47 ` [ELPA] New package: eglot Stefan Monnier 0 siblings, 2 replies; 38+ messages in thread From: João Távora @ 2018-05-10 22:34 UTC (permalink / raw) To: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1733 bytes --] Hello, I'd like to add Eglot, my new package, to GNU ELPA. Eglot, for Emacs Polyglot, is an Emacs client for the LSP, or Language Server Protocol (https://microsoft.github.io/language-server-protocol/). For those unfamilar with LSP, here's a quick primer. M-x eglot connects to LSP subprocesses locally or through the network. Thereafter, via a JSON-based RPC protocol, information about the source code is exchanged with the server: the client tells is about the (unsaved) buffer's contents, and the server provides information to feed xref.el, flymake.el, eldoc.el, auto-completion, and other IDE-like functionality. Because Eglot is language-agnostic, the big idea is that this gives Emacs an uniform IDE UI for any language one can find a suitable server for. There is a prominent existing Emacs package, emacs-lsp, that shares largely the same goals. I have contacted its author and explained why I started from scratch. This is summarized in a list of user-visible and under-the-hood differences between the two projects in Eglot's home page: https://github.com/joaotavora/eglot#differences-to-lsp-modeel emacs-lsp and its many plugins live at: https://github.com/emacs-lsp One notable difference is that Eglot requires Emacs 26 and integrates with the new flymake.el redesign, whereas emacs-lsp uses flycheck.el for that. Another design goal is a single M-x eglot entry point, instead of different eglot-<language>.el and M-x eglot-<language> commands for each language. If the package is accepted, I think I already have commit rights to the GNU ELPA repo and would like to develop this as a Git subtree, just like yasnippet. Thank you for your attention, João [-- Attachment #2: Eglot, Emacs Polyglot --] [-- Type: application/emacs-lisp, Size: 65837 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [ELPA] New package: eglot 2018-05-10 22:34 [ELPA] New package: eglot João Távora @ 2018-05-11 6:19 ` Eli Zaretskii 2018-05-11 11:29 ` João Távora 2018-05-18 16:27 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) João Távora 2018-05-12 15:47 ` [ELPA] New package: eglot Stefan Monnier 1 sibling, 2 replies; 38+ messages in thread From: Eli Zaretskii @ 2018-05-11 6:19 UTC (permalink / raw) To: João Távora; +Cc: emacs-devel > From: João Távora <joaotavora@gmail.com> > Date: Thu, 10 May 2018 23:34:40 +0100 > > I'd like to add Eglot, my new package, to GNU ELPA. > > Eglot, for Emacs Polyglot, is an Emacs client for the LSP, or Language > Server Protocol (https://microsoft.github.io/language-server-protocol/). FWIW, I think LSP support should be part of the core Emacs distribution, as it's an infrastructure necessary for modern support of programming modes. (I didn't yet look at the package, so if it has separate infrastructure and application levels, perhaps only the infrastructure layers should be in Emacs.) Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [ELPA] New package: eglot 2018-05-11 6:19 ` Eli Zaretskii @ 2018-05-11 11:29 ` João Távora 2018-05-11 12:15 ` Eli Zaretskii 2018-05-18 16:27 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) João Távora 1 sibling, 1 reply; 38+ messages in thread From: João Távora @ 2018-05-11 11:29 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: João Távora <joaotavora@gmail.com> >> Date: Thu, 10 May 2018 23:34:40 +0100 >> >> I'd like to add Eglot, my new package, to GNU ELPA. >> >> Eglot, for Emacs Polyglot, is an Emacs client for the LSP, or Language >> Server Protocol (https://microsoft.github.io/language-server-protocol/). > > FWIW, I think LSP support should be part of the core Emacs > distribution, as it's an infrastructure necessary for modern support > of programming modes. It'd be great if eglot.el went into core, of course, and I'd welcome the additional level of scrutiny that usually brings. However, if it does come to that, and since Emacs 27 is reasonably distant, I'd like to keep distributing for Emacs 26 and developing outside Emacs or in parallel with it for at least some time. > (I didn't yet look at the package, so if it has separate > infrastructure and application levels, perhaps only the infrastructure > layers should be in Emacs.) Depends on where you draw the line, but if you really do want LSP to be in core, then you need the full eglot.el, which has (almost) no language-specific code. If you just want JSON-RPC support in Emacs (for whichever other applications that might use it), then you only need a part of eglot.el. Regardless of which parts make it into core, a json-rpc.el could be easily be extracted out of eglot.el if it is deemed useful. João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [ELPA] New package: eglot 2018-05-11 11:29 ` João Távora @ 2018-05-11 12:15 ` Eli Zaretskii 0 siblings, 0 replies; 38+ messages in thread From: Eli Zaretskii @ 2018-05-11 12:15 UTC (permalink / raw) To: João Távora; +Cc: emacs-devel > From: João Távora <joaotavora@gmail.com> > Cc: emacs-devel@gnu.org > Date: Fri, 11 May 2018 12:29:03 +0100 > > > FWIW, I think LSP support should be part of the core Emacs > > distribution, as it's an infrastructure necessary for modern support > > of programming modes. > > It'd be great if eglot.el went into core, of course, and I'd welcome the > additional level of scrutiny that usually brings. However, if it does > come to that, and since Emacs 27 is reasonably distant, I'd like to keep > distributing for Emacs 26 and developing outside Emacs or in parallel > with it for at least some time. New packages could be added in Emacs 26.2, IMO. ^ permalink raw reply [flat|nested] 38+ messages in thread
* New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) 2018-05-11 6:19 ` Eli Zaretskii 2018-05-11 11:29 ` João Távora @ 2018-05-18 16:27 ` João Távora 2018-05-19 8:46 ` Eli Zaretskii 2018-05-19 11:34 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) Philipp Stephani 1 sibling, 2 replies; 38+ messages in thread From: João Távora @ 2018-05-18 16:27 UTC (permalink / raw) To: Eli Zaretskii; +Cc: monnier, vibhavp, emacs-devel [-- Attachment #1: Type: text/plain, Size: 1604 bytes --] Eli Zaretskii <eliz@gnu.org> writes: >> From: João Távora <joaotavora@gmail.com> >> Date: Thu, 10 May 2018 23:34:40 +0100 >> >> I'd like to add Eglot, my new package, to GNU ELPA. >> >> Eglot, for Emacs Polyglot, is an Emacs client for the LSP, or Language >> Server Protocol (https://microsoft.github.io/language-server-protocol/). > > FWIW, I think LSP support should be part of the core Emacs > distribution, as it's an infrastructure necessary for modern support > of programming modes. (I didn't yet look at the package, so if it has > separate infrastructure and application levels, perhaps only the > infrastructure layers should be in Emacs.) Hi again, Eli So, off-list Stefan likewise suggested that some level of LSP "infrastructure" is integrated into Emacs, which could lead to merging eglot.el and other LSP clients like lsp-mode.el in the future. As JSONRPC support is a mandatory component of LSP, I've extracted a library implementing the JSONRPC spec (see www.jsonrpc.org) out of eglot.el. I propose this library, jrpc.el, for Emacs core (or GNU ELPA, or both). I hope people can review it, comment on it, and propose changes, particularly developers of other existing LSP-modes. I attach to the end of this mail with a reasonably complete ";;; Commentary" header, including a simple usage example. JSONRPC is apparently also used for other non-LSP applications. See http://json-rpc.info/. There's a jsonrpc-refactor branch of eglot.el using jrpc.el, for those who want to test it with a real application. Thanks in advance, João [-- Attachment #2: jrpc.el --] [-- Type: application/emacs-lisp, Size: 29295 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) 2018-05-18 16:27 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) João Távora @ 2018-05-19 8:46 ` Eli Zaretskii 2018-05-20 15:54 ` New jrpc.el JSONRPC library João Távora 2018-05-19 11:34 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) Philipp Stephani 1 sibling, 1 reply; 38+ messages in thread From: Eli Zaretskii @ 2018-05-19 8:46 UTC (permalink / raw) To: João Távora; +Cc: monnier, vibhavp, emacs-devel > From: João Távora <joaotavora@gmail.com> > Cc: emacs-devel@gnu.org, monnier@iro.umontreal.ca, vibhavp@gmail.com > Date: Fri, 18 May 2018 17:27:54 +0100 > > So, off-list Stefan likewise suggested that some level of LSP > "infrastructure" is integrated into Emacs, which could lead to merging > eglot.el and other LSP clients like lsp-mode.el in the future. > > As JSONRPC support is a mandatory component of LSP, I've extracted a > library implementing the JSONRPC spec (see www.jsonrpc.org) out of > eglot.el. > > I propose this library, jrpc.el, for Emacs core (or GNU ELPA, or > both). I hope people can review it, comment on it, and propose changes, > particularly developers of other existing LSP-modes. I attach to the > end of this mail with a reasonably complete ";;; Commentary" header, > including a simple usage example. > > JSONRPC is apparently also used for other non-LSP applications. See > http://json-rpc.info/. Thanks, this sounds like a good addition. Does it (or can it) work with the built-in JSON support we have on the master branch? And I still wonder whether more of eglot's LSP support should be in core. After all, only customizing LSP for a specific language should be left for the major modes, the rest of interaction with LSP is probably language-agnostic, right? ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-19 8:46 ` Eli Zaretskii @ 2018-05-20 15:54 ` João Távora 2018-05-20 16:02 ` Eli Zaretskii 2018-05-20 19:13 ` New jrpc.el JSONRPC library Clément Pit-Claudel 0 siblings, 2 replies; 38+ messages in thread From: João Távora @ 2018-05-20 15:54 UTC (permalink / raw) To: Eli Zaretskii; +Cc: monnier, vibhavp, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: João Távora <joaotavora@gmail.com> >> Cc: emacs-devel@gnu.org, monnier@iro.umontreal.ca, vibhavp@gmail.com >> Date: Fri, 18 May 2018 17:27:54 +0100 >> >> So, off-list Stefan likewise suggested that some level of LSP >> "infrastructure" is integrated into Emacs, which could lead to merging >> eglot.el and other LSP clients like lsp-mode.el in the future. >> >> As JSONRPC support is a mandatory component of LSP, I've extracted a >> library implementing the JSONRPC spec (see www.jsonrpc.org) out of >> eglot.el. >> >> I propose this library, jrpc.el, for Emacs core (or GNU ELPA, or >> both). I hope people can review it, comment on it, and propose changes, >> particularly developers of other existing LSP-modes. I attach to the >> end of this mail with a reasonably complete ";;; Commentary" header, >> including a simple usage example. >> >> JSONRPC is apparently also used for other non-LSP applications. See >> http://json-rpc.info/. > > Thanks, this sounds like a good addition. Does it (or can it) work > with the built-in JSON support we have on the master branch? Not yet. I didn't know about that, but indeed it has to be on the roadmap, so I'll start looking into it. > And I still wonder whether more of eglot's LSP support should be in > core. After all, only customizing LSP for a specific language should > be left for the major modes, the rest of interaction with LSP is > probably language-agnostic, right? In fact, no. At least not ideally. *All* of LSP is language-agnostic. The only two things eglot.el does mode-locally are: 1. a very small tweak for `rust-mode', whose not-entirely-compliant RLS server has some trouble that eglot works around and 2. a global `eglot-server-programs' variable mapping major-modes to server-launching commands. The first should go into rust-mode.el (not currently in Emacs/ELPA). The second one could be replaced by a (setq eglot-server-program <program>) int he mode hook. So again, if LSP support is wanted, eglot.el fits that bill (and better that others IMNSHO :-) But I'd let it mature out of core for a little while more. João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 15:54 ` New jrpc.el JSONRPC library João Távora @ 2018-05-20 16:02 ` Eli Zaretskii 2018-05-29 1:08 ` João Távora 2018-06-28 12:13 ` [PATCH] jsonrpc.el João Távora 2018-05-20 19:13 ` New jrpc.el JSONRPC library Clément Pit-Claudel 1 sibling, 2 replies; 38+ messages in thread From: Eli Zaretskii @ 2018-05-20 16:02 UTC (permalink / raw) To: João Távora; +Cc: monnier, vibhavp, emacs-devel > From: joaotavora@gmail.com (João Távora) > Cc: emacs-devel@gnu.org, monnier@iro.umontreal.ca, vibhavp@gmail.com > Date: Sun, 20 May 2018 16:54:42 +0100 > > > Thanks, this sounds like a good addition. Does it (or can it) work > > with the built-in JSON support we have on the master branch? > > Not yet. I didn't know about that, but indeed it has to be on the > roadmap, so I'll start looking into it. Thanks, I think this is an important development direction. We added JSON implementation in C for a good reason, so we'd like to have other core package be able to use that. > So again, if LSP support is wanted, eglot.el fits that bill (and better > that others IMNSHO :-) But I'd let it mature out of core for a little > while more. Fair enough, thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 16:02 ` Eli Zaretskii @ 2018-05-29 1:08 ` João Távora 2018-06-28 12:13 ` [PATCH] jsonrpc.el João Távora 1 sibling, 0 replies; 38+ messages in thread From: João Távora @ 2018-05-29 1:08 UTC (permalink / raw) To: Eli Zaretskii; +Cc: phst, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> > Thanks, this sounds like a good addition. Does it (or can it) work >> > with the built-in JSON support we have on the master branch? >> Not yet. I didn't know about that, but indeed it has to be on the >> roadmap, so I'll start looking into it. > Thanks, I think this is an important development direction. We added > JSON implementation in C for a good reason, so we'd like to have other > core package be able to use that. After having a look at json.c, I confirmed that it isn't designed to work with property lists (plists). Is this for a very good technical reason or just a personal preference for alists and hash-tables? If json.c provided plist support it could more easily become a drop-in replacement for json.el in the future. In my opinion, as I wrote elsewhere, it would also assist in the destructuring of JSON objects in elisp with cl-compatible lambda lists (cl-defmethod, cl-destructuring-bind, etc..) It appears to be very easy to add plist support to json-parse-string in json.c. See the attached patch. If there are no great objections I can add support for plist support to json-serialize as well. In this case it would make sense to respect json.el's variable json-object-type. Thanks, João diff --git a/src/json.c b/src/json.c index b046d34f66..710d1cf888 100644 --- a/src/json.c +++ b/src/json.c @@ -605,6 +605,7 @@ OBJECT. */) enum json_object_type { json_object_hashtable, json_object_alist, + json_object_plist }; /* Convert a JSON object to a Lisp object. */ @@ -692,6 +693,25 @@ json_to_lisp (json_t *json, enum json_object_type object_type) result = Fnreverse (result); break; } + case json_object_plist: + { + result = Qnil; + const char *key_str; + json_t *value; + json_object_foreach (json, key_str, value) + { + /* No idea if using AUTO_STRING and Fconcat for + making keywords is idiomatic, but seems to work + nicely */ + AUTO_STRING (colon, ":"); + Lisp_Object key = + Fintern (CALLN (Fconcat, colon, json_build_string (key_str)) + , Qnil); + result = Fcons (json_to_lisp (value, object_type), result); + result = Fcons (key, result); + } + break; + } default: /* Can't get here. */ emacs_abort (); @@ -721,8 +741,10 @@ json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args) return json_object_hashtable; else if (EQ (value, Qalist)) return json_object_alist; + else if (EQ (value, Qplist)) + return json_object_plist; else - wrong_choice (list2 (Qhash_table, Qalist), value); + wrong_choice (list3 (Qhash_table, Qalist, Qplist), value); } default: wrong_type_argument (Qplistp, Flist (nargs, args)); @@ -731,16 +753,17 @@ json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args) DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY, NULL, - doc: /* Parse the JSON STRING into a Lisp object. -This is essentially the reverse operation of `json-serialize', which -see. The returned object will be a vector, hashtable, or alist. Its + doc: /* Parse the JSON STRING into a Lisp object. This is +essentially the reverse operation of `json-serialize', which see. The +returned object will be a vector, hashtable, alist, or plist. Its elements will be `:null', `:false', t, numbers, strings, or further -vectors, hashtables, and alists. If there are duplicate keys in an -object, all but the last one are ignored. If STRING doesn't contain a -valid JSON object, an error of type `json-parse-error' is signaled. -The keyword argument `:object-type' specifies which Lisp type is used -to represent objects; it can be `hash-table' or `alist'. -usage: (json-parse-string STRING &key (OBJECT-TYPE \\='hash-table)) */) +vectors, hashtables, alists, or plists. If there are duplicate keys +in an object, all but the last one are ignored. If STRING doesn't +contain a valid JSON object, an error of type `json-parse-error' is +signaled. The keyword argument `:object-type' specifies which Lisp +type is used to represent objects; it can be `hash-table', `alist' or +`plist'. +usage: (json-parse-string STRING &key (OBJECT-TYPE \\='hash-table)) */) (ptrdiff_t nargs, Lisp_Object *args) { ptrdiff_t count = SPECPDL_INDEX (); @@ -912,6 +935,7 @@ syms_of_json (void) DEFSYM (QCobject_type, ":object-type"); DEFSYM (Qalist, "alist"); + DEFSYM (Qplist, "plist") defsubr (&Sjson_serialize); defsubr (&Sjson_insert); ^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH] jsonrpc.el 2018-05-20 16:02 ` Eli Zaretskii 2018-05-29 1:08 ` João Távora @ 2018-06-28 12:13 ` João Távora 2018-06-28 13:18 ` Eli Zaretskii 1 sibling, 1 reply; 38+ messages in thread From: João Távora @ 2018-06-28 12:13 UTC (permalink / raw) To: Eli Zaretskii; +Cc: monnier, vibhavp, emacs-devel [-- Attachment #1: Type: text/plain, Size: 1245 bytes --] Eli Zaretskii <eliz@gnu.org> writes: >> > Thanks, this sounds like a good addition. Does it (or can it) work >> > with the built-in JSON support we have on the master branch? >> Not yet. I didn't know about that, but indeed it has to be on the >> roadmap, so I'll start looking into it. > Thanks, I think this is an important development direction. We added > JSON implementation in C for a good reason, so we'd like to have other > core package be able to use that. A few weeks ago, I posted my jsonrpc.el here and had good feedback: http://lists.gnu.org/archive/html/emacs-devel/2018-06/msg00288.html If noone objects, I'm going to push the attached patch to core. Then I'll work on a manual and NEWS entry. I'll also make a 1-file ELPA package out of the new lisp/jsonrpc.el file. This way eglot.el (already in ELPA) can specify that as a dependency when running on Emacs 26.1. >> So again, if LSP support is wanted, eglot.el fits that bill (and >> better than others IMNSHO :-) But I'd let it mature out of core for a >> little while more. > Fair enough, thanks. eglot.el is coming along nicely. If the core-and-ELPA strategy works out for jsonrpc.el, we can think about pushing that too. João [-- Attachment #2: 0001-Add-lisp-jsonrpc.el.patch --] [-- Type: text/x-diff, Size: 45639 bytes --] From 48ad6deb166642520e2ad66152ac05290d0709ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= <joaotavora@gmail.com> Date: Thu, 28 Jun 2018 13:05:38 +0100 Subject: [PATCH] Add lisp/jsonrpc.el * lisp/jsonrpc.el: New file * test/lisp/jsonrpc-tests.el: New file --- lisp/jsonrpc.el | 738 +++++++++++++++++++++++++++++++++++++ test/lisp/jsonrpc-tests.el | 242 ++++++++++++ 2 files changed, 980 insertions(+) create mode 100644 lisp/jsonrpc.el create mode 100644 test/lisp/jsonrpc-tests.el diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el new file mode 100644 index 0000000000..70044320b4 --- /dev/null +++ b/lisp/jsonrpc.el @@ -0,0 +1,738 @@ +;;; jsonrpc.el --- JSON-RPC library -*- lexical-binding: t; -*- + +;; Copyright (C) 2018 Free Software Foundation, Inc. + +;; Author: João Távora <joaotavora@gmail.com> +;; Maintainer: João Távora <joaotavora@gmail.com> +;; Keywords: processes, languages, extensions + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; (Library originally extracted from eglot.el, an Emacs LSP client) +;; +;; This library implements the JSONRPC 2.0 specification as described +;; in http://www.jsonrpc.org/. As the name suggests, JSONRPC is a +;; generic Remote Procedure Call protocol designed around JSON +;; objects. +;; +;; Quoting from the spec: "[JSONRPC] is transport agnostic in that the +;; concepts can be used within the same process, over sockets, over +;; http, or in many various message passing environments." +;; +;; To model this agnosticism, jsonrpc.el uses objects derived from a +;; base `jsonrpc-connection' class, which is "abstract" or "virtual" +;; (in modern OO parlance) and represents the connection to the remote +;; JSON endpoint. Around this class we can define two distinct APIs: +;; +;; 1) A user interface to the JSONRPC _application_, whereby the +;; `jsonrpc-connection' object is instantiated and used to communicate +;; with the remote JSONRPC enpoint. +;; +;; In this scenario, the JSONRPC application makes the object using +;; `make-instance' and initiates contacts to the remove endpoint by +;; passing it to `jsonrpc-notify', `jsonrpc-request' and +;; `jsonrpc-async-request'. For handling remotely initiated contacts, +;; which generally come in asynchronously, the `make-instance' +;; invocation should include `:request-dispatcher' and +;; `:notification-dispatcher' initargs, which are two functions +;; receiving the connection object, a symbol naming the JSONRPC +;; method, and a JSONRPC "params" object. +;; +;; The function passed as `:request-dispatcher' handles the remote +;; endpoint's requests, which expect a reply from the local endpoint. +;; The function may return locally or non-locally (error). A local +;; return value should be a JSON object which determines a success +;; response and is serialized in the JSONRPC "result" object forwarded +;; to the server. If, however, it uses the `jsonrpc-error' function +;; to exit non-locally, this responds to the server with a JSONRPC +;; "error" object instead, the details of which are filled out with +;; the arguments with whatever was passed to `jsonrpc-error'. A +;; suitable error reponse is also sent to the server if the function +;; error unexpectedly with any other error that doesn't originate in a +;; deliberate call to `jsonrpc-error'. +;; +;; 2) A inheritance-based interface to the JSONPRPC _transport +;; implementation_, whereby `jsonrpc-connection' is subclassed so +;; users of the user interface can communicate with JSONRPC endpoints +;; using different underlying transport strategies. +;; +;; There are mandatory and optional parts to this API. +;; +;; For initiating contacts to the endpoint and replying to it, that +;; subclass of `jsonrpc-connection' must implement +;; `jsonrpc-connection-send' method. +;; +;; Likewise, for handling the three types remote endpoint's contacts +;; (responses to requests, remotely initiated requests and remotely +;; initiated notifications) the transport implementation must arrange +;; for the function `jsonrpc-connection-receive' to be called after +;; noticing a new JSONRPC message on the wire (whatever that "wire" +;; may be). +;; +;; Finally, and optionally, the `jsonrpc-connection' subclass should +;; implement `jsonrpc-shutdown' and `jsonrpc-running-p' if these +;; concepts apply to the transport. +;; +;; For convenience, jsonrpc.el comes built-in with a +;; `jsonrpc-process-connection' transport implementation that can talk +;; to local subprocesses (through stdin/stdout) and TCP hosts using +;; sockets. This uses some basic HTTP-style enveloping headers for +;; JSON objects sent over the wire. For an example of an application +;; using this transport scheme on top of JSONRPC, see the Language +;; Server Protocol +;; (https://microsoft.github.io/language-server-protocol/specification). +;; `jsonrpc-process-connection' also implements `jsonrpc-shutdown', +;; `jsonrpc-running-p'. +;; +;;;; About deferred requests and `jsonrpc-connection-p': +;; +;; In the user API. +;; +;;;; JSON object format: +;; +;; JSON objects are exchanged as keyword-value plists: plists are +;; handed to the dispatcher functions and, likewise, plists should be +;; given to `jsonrpc-notify', `jsonrpc-request' and +;; `jsonrpc-async-request'. +;; +;; To facilitate handling plists, this library make liberal use of +;; cl-lib.el and suggests (but doesn't force) its clients to do the +;; same. A macro `jsonrpc-lambda' can be used to create a lambda for +;; destructuring a JSON-object like in this example: +;; +;; (jsonrpc-async-request +;; myproc :frobnicate `(:foo "trix") +;; :success-fn (jsonrpc-lambda (&key bar baz &allow-other-keys) +;; (message "Server replied back %s and %s!" +;; bar baz)) +;; :error-fn (jsonrpc-lambda (&key code message _data) +;; (message "Sadly, server reports %s: %s" +;; code message))) +;; +;;; Code: + +(require 'cl-lib) +(require 'json) +(require 'eieio) +(require 'subr-x) +(require 'warnings) +(require 'pcase) +(require 'ert) ; to escape a `condition-case-unless-debug' +(require 'array) ; xor + +\f +;;; Public API +;;; +;;;###autoload +(defclass jsonrpc-connection () + ((name + :accessor jsonrpc-name + :initarg :name + :documentation "A name for the connection") + (-request-dispatcher + :accessor jsonrpc--request-dispatcher + :initform #'ignore + :initarg :request-dispatcher + :documentation "Dispatcher for remotely invoked requests.") + (-notification-dispatcher + :accessor jsonrpc--notification-dispatcher + :initform #'ignore + :initarg :notification-dispatcher + :documentation "Dispatcher for remotely invoked notifications.") + (last-error + :accessor jsonrpc-last-error + :documentation "Last JSONRPC error message received from endpoint.") + (-request-continuations + :initform (make-hash-table) + :accessor jsonrpc--request-continuations + :documentation "A hash table of request ID to continuation lambdas.") + (-events-buffer + :accessor jsonrpc--events-buffer + :documentation "A buffer pretty-printing the JSON-RPC RPC events") + (-deferred-actions + :initform (make-hash-table :test #'equal) + :accessor jsonrpc--deferred-actions + :documentation "Map (DEFERRED BUF) to (FN TIMER ID). FN is\ +a saved DEFERRED `async-request' from BUF, to be sent not later\ +than TIMER as ID.") + (-next-request-id + :initform 0 + :accessor jsonrpc--next-request-id + :documentation "Next number used for a request")) + :documentation "Base class representing a JSONRPC connection. +The following initargs are accepted: + +:NAME (mandatory), a string naming the connection + +:REQUEST-DISPATCHER (optional), a function of three +arguments (CONN METHOD PARAMS) for handling JSONRPC requests. +CONN is a `jsonrpc-connection' object, method is a symbol, and +PARAMS is a plist representing a JSON object. The function is +expected to return a JSONRPC result, a plist of (:result +RESULT) or signal an error of type `jsonrpc-error'. + +:NOTIFICATION-DISPATCHER (optional), a function of three +arguments (CONN METHOD PARAMS) for handling JSONRPC +notifications. CONN, METHOD and PARAMS are the same as in +:REQUEST-DISPATCHER.") + +;;; API mandatory +(cl-defgeneric jsonrpc-connection-send (conn &key id method params result error) + "Send a JSONRPC message to connection CONN. +ID, METHOD, PARAMS, RESULT and ERROR. ") + +;;; API optional +(cl-defgeneric jsonrpc-shutdown (conn) + "Shutdown the JSONRPC connection CONN.") + +;;; API optional +(cl-defgeneric jsonrpc-running-p (conn) + "Tell if the JSONRPC connection CONN is still running.") + +;;; API optional +(cl-defgeneric jsonrpc-connection-ready-p (connection what) + "Tell if CONNECTION is ready for WHAT in current buffer. +If it isn't, a deferrable `jsonrpc-async-request' will be +deferred to the future. By default, all connections are ready +for sending requests immediately." + (:method (_s _what) ;; by default all connections are ready + t)) + +\f +;;; Convenience +;;; +(cl-defmacro jsonrpc-lambda (cl-lambda-list &body body) + (declare (indent 1) (debug (sexp &rest form))) + (let ((e (gensym "jsonrpc-lambda-elem"))) + `(lambda (,e) (apply (cl-function (lambda ,cl-lambda-list ,@body)) ,e)))) + +(defun jsonrpc-events-buffer (connection) + "Get or create JSONRPC events buffer for CONNECTION." + (let* ((probe (jsonrpc--events-buffer connection)) + (buffer (or (and (buffer-live-p probe) + probe) + (let ((buffer (get-buffer-create + (format "*%s events*" + (jsonrpc-name connection))))) + (with-current-buffer buffer + (buffer-disable-undo) + (read-only-mode t) + (setf (jsonrpc--events-buffer connection) buffer)) + buffer)))) + buffer)) + +(defun jsonrpc-forget-pending-continuations (connection) + "Stop waiting for responses from the current JSONRPC CONNECTION." + (clrhash (jsonrpc--request-continuations connection))) + +(defun jsonrpc-connection-receive (connection message) + "Process MESSAGE just received from CONNECTION. +This function will destructure MESSAGE and call the appropriate +dispatcher in CONNECTION." + (cl-destructuring-bind (&key method id error params result _jsonrpc) + message + (let (continuations) + (jsonrpc--log-event connection message 'server) + (setf (jsonrpc-last-error connection) error) + (cond + (;; A remote request + (and method id) + (let* ((debug-on-error (and debug-on-error (not (ert-running-test)))) + (reply + (condition-case-unless-debug _ignore + (condition-case oops + `(:result ,(funcall (jsonrpc--request-dispatcher connection) + connection (intern method) params)) + (jsonrpc-error + `(:error + (:code + ,(or (alist-get 'jsonrpc-error-code (cdr oops)) -32603) + :message ,(or (alist-get 'jsonrpc-error-message + (cdr oops)) + "Internal error"))))) + (error + `(:error (:code -32603 :message "Internal error")))))) + (apply #'jsonrpc--reply connection id reply))) + (;; A remote notification + method + (funcall (jsonrpc--notification-dispatcher connection) + connection (intern method) params)) + (;; A remote response + (setq continuations + (and id (gethash id (jsonrpc--request-continuations connection)))) + (let ((timer (nth 2 continuations))) + (when timer (cancel-timer timer))) + (remhash id (jsonrpc--request-continuations connection)) + (if error (funcall (nth 1 continuations) error) + (funcall (nth 0 continuations) result))) + (;; An abnormal situation + id (jsonrpc--warn "No continuation for id %s" id))) + (jsonrpc--call-deferred connection)))) + +\f +;;; Contacting the remote endpoint +;;; +(defun jsonrpc-error (&rest args) + "Error out with FORMAT and ARGS. +If invoked inside a dispatcher function, this function is suitable +for replying to the remote endpoint with an error message. + +ARGS can be of the form (FORMAT-STRING . MOREARGS) for replying +with a -32603 error code and a message formed by formatting +FORMAT-STRING with MOREARGS. + +Alternatively ARGS can be plist representing a JSONRPC error +object, using the keywords `:code', `:message' and `:data'." + (if (stringp (car args)) + (let ((msg + (apply #'format-message (car args) (cdr args)))) + (signal 'jsonrpc-error + `(,msg + (jsonrpc-error-code . ,32603) + (jsonrpc-error-message . ,msg)))) + (cl-destructuring-bind (&key code message data) args + (signal 'jsonrpc-error + `(,(format "[jsonrpc] error ") + (jsonrpc-error-code . ,code) + (jsonrpc-error-message . ,message) + (jsonrpc-error-data . ,data)))))) + +(cl-defun jsonrpc-async-request (connection + method + params + &rest args + &key _success-fn _error-fn + _timeout-fn + _timeout _deferred) + "Make a request to CONNECTION, expecting a reply, return immediately. +The JSONRPC request is formed by METHOD, a symbol, and PARAMS a +JSON object. + +The caller can expect SUCCESS-FN or ERROR-FN to be called with a +JSONRPC `:result' or `:error' object, respectively. If this +doesn't happen after TIMEOUT seconds (defaults to +`jsonrpc-request-timeout'), the caller can expect TIMEOUT-FN to be +called with no arguments. The default values of SUCCESS-FN, +ERROR-FN and TIMEOUT-FN simply log the events into +`jsonrpc-events-buffer'. + +If DEFERRED is non-nil, maybe defer the request to a future time +when the server is thought to be ready according to +`jsonrpc-connection-ready-p' (which see). The request might +never be sent at all, in case it is overridden in the meantime by +a new request with identical DEFERRED and for the same buffer. +However, in that situation, the original timeout is kept. + +Returns nil." + (apply #'jsonrpc--async-request-1 connection method params args) + nil) + +(cl-defun jsonrpc-request (connection method params &key deferred timeout) + "Make a request to CONNECTION, wait for a reply. +Like `jsonrpc-async-request' for CONNECTION, METHOD and PARAMS, but +synchronous, i.e. doesn't exit until anything +interesting (success, error or timeout) happens. Furthermore, +only exit locally (and return the JSONRPC result object) if the +request is successful, otherwise exit non-locally with an error +of type `jsonrpc-error'. + +DEFERRED is passed to `jsonrpc-async-request', which see." + (let* ((tag (cl-gensym "jsonrpc-request-catch-tag")) id-and-timer + (retval + (unwind-protect ; protect against user-quit, for example + (catch tag + (setq + id-and-timer + (jsonrpc--async-request-1 + connection method params + :success-fn (lambda (result) (throw tag `(done ,result))) + :error-fn + (jsonrpc-lambda + (&key code message data) + (throw tag `(error (jsonrpc-error-code . ,code) + (jsonrpc-error-message . ,message) + (jsonrpc-error-data . ,data)))) + :timeout-fn + (lambda () + (throw tag '(error (jsonrpc-error-message . "Timed out")))) + :deferred deferred + :timeout timeout)) + (while t (accept-process-output nil 30))) + (pcase-let* ((`(,id ,timer) id-and-timer)) + (remhash id (jsonrpc--request-continuations connection)) + (remhash (list deferred (current-buffer)) + (jsonrpc--deferred-actions connection)) + (when timer (cancel-timer timer)))))) + (when (eq 'error (car retval)) + (signal 'jsonrpc-error + (cons + (format "request id=%s failed:" (car id-and-timer)) + (cdr retval)))) + (cadr retval))) + +(cl-defun jsonrpc-notify (connection method params) + "Notify CONNECTION of something, don't expect a reply." + (jsonrpc-connection-send connection + :method method + :params params)) + +(defconst jrpc-default-request-timeout 10 + "Time in seconds before timing out a JSONRPC request.") + +\f +;;; Specfic to `jsonrpc-process-connection' +;;; +;;;###autoload +(defclass jsonrpc-process-connection (jsonrpc-connection) + ((-process + :initarg :process :accessor jsonrpc--process + :documentation "Process object wrapped by the this connection.") + (-expected-bytes + :accessor jsonrpc--expected-bytes + :documentation "How many bytes declared by server") + (-on-shutdown + :accessor jsonrpc--on-shutdown + :initform #'ignore + :initarg :on-shutdown + :documentation "Function run when the process dies.")) + :documentation "A JSONRPC connection over an Emacs process. +The following initargs are accepted: + +:PROCESS (mandatory), a live running Emacs process object or a +function of no arguments producing one such object. The process +represents either a pipe connection to locally running process or +a stream connection to a network host. The remote endpoint is +expected to understand JSONRPC messages with basic HTTP-style +enveloping headers such as \"Content-Length:\". + +:ON-SHUTDOWN (optional), a function of one argument, the +connection object, called when the process dies .") + +(cl-defmethod initialize-instance ((conn jsonrpc-process-connection) slots) + (cl-call-next-method) + (let* ((proc (plist-get slots :process)) + (proc (if (functionp proc) (funcall proc) proc)) + (buffer (get-buffer-create (format "*%s output*" (process-name proc)))) + (stderr (get-buffer-create (format "*%s stderr*" (process-name proc))))) + (setf (jsonrpc--process conn) proc) + (set-process-buffer proc buffer) + (process-put proc 'jsonrpc-stderr stderr) + (set-process-filter proc #'jsonrpc--process-filter) + (set-process-sentinel proc #'jsonrpc--process-sentinel) + (with-current-buffer (process-buffer proc) + (set-marker (process-mark proc) (point-min)) + (let ((inhibit-read-only t)) (erase-buffer) (read-only-mode t) proc)) + (process-put proc 'jsonrpc-connection conn))) + +(cl-defmethod jsonrpc-connection-send ((connection jsonrpc-process-connection) + &rest args + &key + _id + method + _params + _result + _error + _partial) + "Send MESSAGE, a JSON object, to CONNECTION." + (when method + (plist-put args :method + (cond ((keywordp method) (substring (symbol-name method) 1)) + ((and method (symbolp method)) (symbol-name method))))) + (let* ( (message `(:jsonrpc "2.0" ,@args)) + (json (jsonrpc--json-encode message)) + (headers + `(("Content-Length" . ,(format "%d" (string-bytes json))) + ;; ("Content-Type" . "application/vscode-jsonrpc; charset=utf-8") + ))) + (process-send-string + (jsonrpc--process connection) + (cl-loop for (header . value) in headers + concat (concat header ": " value "\r\n") into header-section + finally return (format "%s\r\n%s" header-section json))) + (jsonrpc--log-event connection message 'client))) + +(defun jsonrpc-process-type (conn) + "Return the `process-type' of JSONRPC connection CONN." + (process-type (jsonrpc--process conn))) + +(cl-defmethod jsonrpc-running-p ((conn jsonrpc-process-connection)) + "Return non-nil if JSONRPC connection CONN is running." + (process-live-p (jsonrpc--process conn))) + +(cl-defmethod jsonrpc-shutdown ((conn jsonrpc-process-connection)) + "Shutdown the JSONRPC connection CONN." + (cl-loop + with proc = (jsonrpc--process conn) + do + (delete-process proc) + (accept-process-output nil 0.1) + while (not (process-get proc 'jsonrpc-sentinel-done)) + do (jsonrpc--warn + "Sentinel for %s still hasn't run, deleting it!" proc))) + +(defun jsonrpc-stderr-buffer (conn) + "Get CONN's standard error buffer, if any." + (process-get (jsonrpc--process conn) 'jsonrpc-stderr)) + +\f +;;; Private stuff +;;; +(define-error 'jsonrpc-error "jsonrpc-error") + +(defun jsonrpc--json-read () + "Read JSON object in buffer, move point to end of buffer." + ;; TODO: I guess we can make these macros if/when jsonrpc.el + ;; goes into Emacs core. + (cond ((fboundp 'json-parse-buffer) (json-parse-buffer + :object-type 'plist + :null-object nil + :false-object :json-false)) + (t (let ((json-object-type 'plist)) + (json-read))))) + +(defun jsonrpc--json-encode (object) + "Encode OBJECT into a JSON string." + (cond ((fboundp 'json-serialize) (json-serialize + object + :false-object :json-false + :null-object nil)) + (t (let ((json-false :json-false) + (json-null nil)) + (json-encode object))))) + +(cl-defun jsonrpc--reply (connection id &key (result nil result-supplied-p) error) + "Reply to CONNECTION's request ID with RESULT or ERROR." + (jsonrpc-connection-send connection :id id :result result :error error)) + +(defun jsonrpc--call-deferred (connection) + "Call CONNECTION's deferred actions, who may again defer themselves." + (when-let ((actions (hash-table-values (jsonrpc--deferred-actions connection)))) + (jsonrpc--debug connection `(:maybe-run-deferred ,(mapcar #'caddr actions))) + (mapc #'funcall (mapcar #'car actions)))) + +(defun jsonrpc--process-sentinel (proc change) + "Called when PROC undergoes CHANGE." + (let ((connection (process-get proc 'jsonrpc-connection))) + (jsonrpc--debug connection `(:message "Connection state changed" :change ,change)) + (when (not (process-live-p proc)) + (with-current-buffer (jsonrpc-events-buffer connection) + (let ((inhibit-read-only t)) + (insert "\n----------b---y---e---b---y---e----------\n"))) + ;; Cancel outstanding timers + (maphash (lambda (_id triplet) + (pcase-let ((`(,_success ,_error ,timeout) triplet)) + (when timeout (cancel-timer timeout)))) + (jsonrpc--request-continuations connection)) + (unwind-protect + ;; Call all outstanding error handlers + (maphash (lambda (_id triplet) + (pcase-let ((`(,_success ,error ,_timeout) triplet)) + (funcall error `(:code -1 :message "Server died")))) + (jsonrpc--request-continuations connection)) + (jsonrpc--message "Server exited with status %s" (process-exit-status proc)) + (process-put proc 'jsonrpc-sentinel-done t) + (delete-process proc) + (funcall (jsonrpc--on-shutdown connection) connection))))) + +(defun jsonrpc--process-filter (proc string) + "Called when new data STRING has arrived for PROC." + (when (buffer-live-p (process-buffer proc)) + (with-current-buffer (process-buffer proc) + (let* ((inhibit-read-only t) + (connection (process-get proc 'jsonrpc-connection)) + (expected-bytes (jsonrpc--expected-bytes connection))) + ;; Insert the text, advancing the process marker. + ;; + (save-excursion + (goto-char (process-mark proc)) + (insert string) + (set-marker (process-mark proc) (point))) + ;; Loop (more than one message might have arrived) + ;; + (unwind-protect + (let (done) + (while (not done) + (cond + ((not expected-bytes) + ;; Starting a new message + ;; + (setq expected-bytes + (and (search-forward-regexp + "\\(?:.*: .*\r\n\\)*Content-Length: \ +*\\([[:digit:]]+\\)\r\n\\(?:.*: .*\r\n\\)*\r\n" + (+ (point) 100) + t) + (string-to-number (match-string 1)))) + (unless expected-bytes + (setq done :waiting-for-new-message))) + (t + ;; Attempt to complete a message body + ;; + (let ((available-bytes (- (position-bytes (process-mark proc)) + (position-bytes (point))))) + (cond + ((>= available-bytes + expected-bytes) + (let* ((message-end (byte-to-position + (+ (position-bytes (point)) + expected-bytes)))) + (unwind-protect + (save-restriction + (narrow-to-region (point) message-end) + (let* ((json-message + (condition-case-unless-debug oops + (jsonrpc--json-read) + (error + (jsonrpc--warn "Invalid JSON: %s %s" + (cdr oops) (buffer-string)) + nil)))) + (when json-message + ;; Process content in another + ;; buffer, shielding proc buffer from + ;; tamper + (with-temp-buffer + (jsonrpc-connection-receive connection + json-message))))) + (goto-char message-end) + (delete-region (point-min) (point)) + (setq expected-bytes nil)))) + (t + ;; Message is still incomplete + ;; + (setq done :waiting-for-more-bytes-in-this-message)))))))) + ;; Saved parsing state for next visit to this filter + ;; + (setf (jsonrpc--expected-bytes connection) expected-bytes)))))) + +(cl-defun jsonrpc--async-request-1 (connection + method + params + &rest args + &key success-fn error-fn timeout-fn + (timeout jrpc-default-request-timeout) + (deferred nil)) + "Does actual work for `jsonrpc-async-request'. + +Return a list (ID TIMER). ID is the new request's ID, or nil if +the request was deferred. TIMER is a timer object set (or nil, if +TIMEOUT is nil)." + (pcase-let* ((buf (current-buffer)) (point (point)) + (`(,_ ,timer ,old-id) + (and deferred (gethash (list deferred buf) + (jsonrpc--deferred-actions connection)))) + (id (or old-id (cl-incf (jsonrpc--next-request-id connection)))) + (make-timer + (lambda ( ) + (when timeout + (run-with-timer + timeout nil + (lambda () + (remhash id (jsonrpc--request-continuations connection)) + (remhash (list deferred buf) + (jsonrpc--deferred-actions connection)) + (if timeout-fn (funcall timeout-fn) + (jsonrpc--debug + connection `(:timed-out ,method :id ,id + :params ,params))))))))) + (when deferred + (if (jsonrpc-connection-ready-p connection deferred) + ;; Server is ready, we jump below and send it immediately. + (remhash (list deferred buf) (jsonrpc--deferred-actions connection)) + ;; Otherwise, save in `eglot--deferred-actions' and exit non-locally + (unless old-id + (jsonrpc--debug connection `(:deferring ,method :id ,id :params + ,params))) + (puthash (list deferred buf) + (list (lambda () + (when (buffer-live-p buf) + (with-current-buffer buf + (save-excursion (goto-char point) + (apply #'jsonrpc-async-request + connection + method params args))))) + (or timer (setq timer (funcall make-timer))) id) + (jsonrpc--deferred-actions connection)) + (cl-return-from jsonrpc--async-request-1 (list id timer)))) + ;; Really send it + ;; + (jsonrpc-connection-send connection + :id id + :method method + :params params) + (puthash id + (list (or success-fn + (jsonrpc-lambda (&rest _ignored) + (jsonrpc--debug + connection (list :message "success ignored" + :id id)))) + (or error-fn + (jsonrpc-lambda (&key code message &allow-other-keys) + (jsonrpc--debug + connection (list + :message + (format "error ignored, status set (%s)" + message) + :id id :error code)))) + (setq timer (funcall make-timer))) + (jsonrpc--request-continuations connection)) + (list id timer))) + +(defun jsonrpc--message (format &rest args) + "Message out with FORMAT with ARGS." + (message "[jsonrpc] %s" (apply #'format format args))) + +(defun jsonrpc--debug (server format &rest args) + "Debug message for SERVER with FORMAT and ARGS." + (jsonrpc--log-event + server (if (stringp format)`(:message ,(format format args)) format))) + +(defun jsonrpc--warn (format &rest args) + "Warning message with FORMAT and ARGS." + (apply #'jsonrpc--message (concat "(warning) " format) args) + (let ((warning-minimum-level :error)) + (display-warning 'jsonrpc + (apply #'format format args) + :warning))) + +(defun jsonrpc--log-event (connection message &optional type) + "Log a JSONRPC-related event. +CONNECTION is the current connection. MESSAGE is a JSON-like +plist. TYPE is a symbol saying if this is a client or server +originated." + (with-current-buffer (jsonrpc-events-buffer connection) + (cl-destructuring-bind (&key method id error &allow-other-keys) message + (let* ((inhibit-read-only t) + (subtype (cond ((and method id) 'request) + (method 'notification) + (id 'reply) + (t 'message))) + (type + (concat (format "%s" (or type 'internal)) + (if type + (format "-%s" subtype))))) + (goto-char (point-max)) + (let ((msg (format "%s%s%s %s:\n%s\n" + type + (if id (format " (id:%s)" id) "") + (if error " ERROR" "") + (current-time-string) + (pp-to-string message)))) + (when error + (setq msg (propertize msg 'face 'error))) + (insert-before-markers msg)))))) + +(provide 'jsonrpc) +;;; jsonrpc.el ends here diff --git a/test/lisp/jsonrpc-tests.el b/test/lisp/jsonrpc-tests.el new file mode 100644 index 0000000000..bfdb513ada --- /dev/null +++ b/test/lisp/jsonrpc-tests.el @@ -0,0 +1,242 @@ +;;; jsonrpc-tests.el --- tests for jsonrpc.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2018 Free Software Foundation, Inc. + +;; Author: João Távora <joaotavora@gmail.com> +;; Maintainer: João Távora <joaotavora@gmail.com> +;; Keywords: tests + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; About "deferred" tests, `jsonrpc--test-client' has a flag that we +;; test this flag in the this `jsonrpc-connection-ready-p' API method. +;; It holds any `jsonrpc-request's and `jsonrpc-async-request's +;; explicitly passed `:deferred'. After clearing the flag, the held +;; requests are actually sent to the server in the next opportunity +;; (when receiving or sending something to the server). + +;;; Code: + +(require 'ert) +(require 'jsonrpc) +(require 'eieio) + +(defclass jsonrpc--test-endpoint (jsonrpc-process-connection) + ((scp :accessor jsonrpc--shutdown-complete-p))) + +(defclass jsonrpc--test-client (jsonrpc--test-endpoint) + ((hold-deferred :initform t :accessor jsonrpc--hold-deferred))) + +(cl-defmacro jsonrpc--with-emacsrpc-fixture ((endpoint-sym) &body body) + (declare (indent 1) (debug t)) + (let ((server (gensym "server-")) (listen-server (gensym "listen-server-"))) + `(let* (,server + (,listen-server + (make-network-process + :name "Emacs RPC server" :server t :host "localhost" + :service 0 + :log (lambda (_server client _message) + (setq ,server + (make-instance + 'jsonrpc--test-endpoint + :name (process-name client) + :process client + :request-dispatcher + (lambda (_endpoint method params) + (unless (memq method '(+ - * / vconcat append + sit-for ignore)) + (signal 'jsonrpc-error + `((jsonrpc-error-message + . "Sorry, this isn't allowed") + (jsonrpc-error-code . -32601)))) + (apply method (append params nil))) + :on-shutdown + (lambda (conn) + (setf (jsonrpc--shutdown-complete-p conn) t))))))) + (,endpoint-sym (make-instance + 'jsonrpc--test-client + "Emacs RPC client" + :process + (open-network-stream "JSONRPC test tcp endpoint" + nil "localhost" + (process-contact ,listen-server + :service)) + :on-shutdown + (lambda (conn) + (setf (jsonrpc--shutdown-complete-p conn) t))))) + (unwind-protect + (progn + (cl-assert ,endpoint-sym) + ,@body + (kill-buffer (jsonrpc--events-buffer ,endpoint-sym)) + (when ,server + (kill-buffer (jsonrpc--events-buffer ,server)))) + (unwind-protect + (jsonrpc-shutdown ,endpoint-sym) + (unwind-protect + (jsonrpc-shutdown ,server) + (cl-loop do (delete-process ,listen-server) + while (progn (accept-process-output nil 0.1) + (process-live-p ,listen-server)) + do (jsonrpc--message + "test listen-server is still running, waiting")))))))) + +(ert-deftest returns-3 () + "A basic test for adding two numbers in our test RPC." + (jsonrpc--with-emacsrpc-fixture (conn) + (should (= 3 (jsonrpc-request conn '+ [1 2]))))) + +(ert-deftest errors-with--32601 () + "Errors with -32601" + (jsonrpc--with-emacsrpc-fixture (conn) + (condition-case err + (progn + (jsonrpc-request conn 'delete-directory "~/tmp") + (ert-fail "A `jsonrpc-error' should have been signalled!")) + (jsonrpc-error + (should (= -32601 (cdr (assoc 'jsonrpc-error-code (cdr err))))))))) + +(ert-deftest signals-an--32603-JSONRPC-error () + "Signals an -32603 JSONRPC error." + (jsonrpc--with-emacsrpc-fixture (conn) + (condition-case err + (progn + (jsonrpc-request conn '+ ["a" 2]) + (ert-fail "A `jsonrpc-error' should have been signalled!")) + (jsonrpc-error + (should (= -32603 (cdr (assoc 'jsonrpc-error-code (cdr err))))))))) + +(ert-deftest times-out () + "Request for 3-sec sit-for with 1-sec timeout times out." + (jsonrpc--with-emacsrpc-fixture (conn) + (should-error + (jsonrpc-request conn 'sit-for [3] :timeout 1)))) + +(ert-deftest doesnt-time-out () + :tags '(:expensive-test) + "Request for 1-sec sit-for with 2-sec timeout succeeds." + (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc-request conn 'sit-for [1] :timeout 2))) + +(ert-deftest stretching-it-but-works () + "Vector of numbers or vector of vector of numbers are serialized." + (jsonrpc--with-emacsrpc-fixture (conn) + ;; (vconcat [1 2 3] [3 4 5]) => [1 2 3 3 4 5] which can be + ;; serialized. + (should (equal + [1 2 3 3 4 5] + (jsonrpc-request conn 'vconcat [[1 2 3] [3 4 5]]))))) + +(ert-deftest json-el-cant-serialize-this () + "Can't serialize a response that is half-vector/half-list." + (jsonrpc--with-emacsrpc-fixture (conn) + (should-error + ;; (append [1 2 3] [3 4 5]) => (1 2 3 . [3 4 5]), which can't be + ;; serialized + (jsonrpc-request conn 'append [[1 2 3] [3 4 5]])))) + +(cl-defmethod jsonrpc-connection-ready-p + ((conn jsonrpc--test-client) what) + (and (cl-call-next-method) + (or (not (string-match "deferred" what)) + (not (jsonrpc--hold-deferred conn))))) + +(ert-deftest deferred-action-toolate () + :tags '(:expensive-test) + "Deferred request fails because noone clears the flag." + (jsonrpc--with-emacsrpc-fixture (conn) + (should-error + (jsonrpc-request conn '+ [1 2] + :deferred "deferred-testing" :timeout 0.5) + :type 'jsonrpc-error) + (should + (= 3 (jsonrpc-request conn '+ [1 2] + :timeout 0.5))))) + +(ert-deftest deferred-action-intime () + :tags '(:expensive-test) + "Deferred request barely makes it after event clears a flag." + ;; Send an async request, which returns immediately. However the + ;; success fun which sets the flag only runs after some time. + (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc-async-request conn + 'sit-for [0.5] + :success-fn + (lambda (_result) + (setf (jsonrpc--hold-deferred conn) nil))) + ;; Now wait for an answer to this request, which should be sent as + ;; soon as the previous one is answered. + (should + (= 3 (jsonrpc-request conn '+ [1 2] + :deferred "deferred" + :timeout 1))))) + +(ert-deftest deferred-action-complex-tests () + :tags '(:expensive-test) + "Test a more complex situation with deferred requests." + (jsonrpc--with-emacsrpc-fixture (conn) + (let (n-deferred-1 + n-deferred-2 + second-deferred-went-through-p) + ;; This returns immediately + (jsonrpc-async-request + conn + 'sit-for [0.1] + :success-fn + (lambda (_result) + ;; this only gets runs after the "first deferred" is stashed. + (setq n-deferred-1 + (hash-table-count (jsonrpc--deferred-actions conn))))) + (should-error + ;; This stashes the request and waits. It will error because + ;; no-one clears the "hold deferred" flag. + (jsonrpc-request conn 'ignore ["first deferred"] + :deferred "first deferred" + :timeout 0.5) + :type 'jsonrpc-error) + ;; The error means the deferred actions stash is now empty + (should (zerop (hash-table-count (jsonrpc--deferred-actions conn)))) + ;; Again, this returns immediately. + (jsonrpc-async-request + conn + 'sit-for [0.1] + :success-fn + (lambda (_result) + ;; This gets run while "third deferred" below is waiting for + ;; a reply. Notice that we clear the flag in time here. + (setq n-deferred-2 (hash-table-count (jsonrpc--deferred-actions conn))) + (setf (jsonrpc--hold-deferred conn) nil))) + ;; This again stashes a request and returns immediately. + (jsonrpc-async-request conn 'ignore ["second deferred"] + :deferred "second deferred" + :timeout 1 + :success-fn + (lambda (_result) + (setq second-deferred-went-through-p t))) + ;; And this also stashes a request, but waits. Eventually the + ;; flag is cleared in time and both requests go through. + (jsonrpc-request conn 'ignore ["third deferred"] + :deferred "third deferred" + :timeout 1) + (should second-deferred-went-through-p) + (should (eq 1 n-deferred-1)) + (should (eq 2 n-deferred-2)) + (should (eq 0 (hash-table-count (jsonrpc--deferred-actions conn))))))) + + + +(provide 'jsonrpc-tests) +;;; jsonrpc-tests.el ends here -- 2.17.1 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH] jsonrpc.el 2018-06-28 12:13 ` [PATCH] jsonrpc.el João Távora @ 2018-06-28 13:18 ` Eli Zaretskii 2018-06-28 13:23 ` João Távora 0 siblings, 1 reply; 38+ messages in thread From: Eli Zaretskii @ 2018-06-28 13:18 UTC (permalink / raw) To: João Távora; +Cc: monnier, vibhavp, emacs-devel > From: João Távora <joaotavora@gmail.com> > Cc: emacs-devel@gnu.org, monnier@iro.umontreal.ca, vibhavp@gmail.com > Date: Thu, 28 Jun 2018 13:13:40 +0100 > > A few weeks ago, I posted my jsonrpc.el here and had good feedback: > > http://lists.gnu.org/archive/html/emacs-devel/2018-06/msg00288.html > > If noone objects, I'm going to push the attached patch to core. Then > I'll work on a manual and NEWS entry. I'd prefer if you could commit the documentation changes together with the code. At least some minimal documentation. I really dislike to have significant code changes in the repository without the docs to go with them, unless it's on a scratch/temporary branch. Is that feasible for you? Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH] jsonrpc.el 2018-06-28 13:18 ` Eli Zaretskii @ 2018-06-28 13:23 ` João Távora 2018-06-28 13:40 ` Eli Zaretskii 0 siblings, 1 reply; 38+ messages in thread From: João Távora @ 2018-06-28 13:23 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Stefan Monnier, Vibhav Pant, emacs-devel [-- Attachment #1: Type: text/plain, Size: 1095 bytes --] On Thu, Jun 28, 2018 at 2:18 PM, Eli Zaretskii <eliz@gnu.org> wrote: > > From: João Távora <joaotavora@gmail.com> > > Cc: emacs-devel@gnu.org, monnier@iro.umontreal.ca, vibhavp@gmail.com > > Date: Thu, 28 Jun 2018 13:13:40 +0100 > > > > A few weeks ago, I posted my jsonrpc.el here and had good feedback: > > > > http://lists.gnu.org/archive/html/emacs-devel/2018-06/msg00288.html > > > > If noone objects, I'm going to push the attached patch to core. Then > > I'll work on a manual and NEWS entry. > > I'd prefer if you could commit the documentation changes together with > the code. At least some minimal documentation. I really dislike to > have significant code changes in the repository without the docs to go > with them, unless it's on a scratch/temporary branch. > > Is that feasible for you? > Sure, no problem. The manual is mostly already the reasonably thorough "Commentary: " section, so I'll translate that to texi and direct readers there. Where should I put the manual BTW? The Elisp manual, right? Where exactly? -- João Távora [-- Attachment #2: Type: text/html, Size: 2027 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH] jsonrpc.el 2018-06-28 13:23 ` João Távora @ 2018-06-28 13:40 ` Eli Zaretskii 0 siblings, 0 replies; 38+ messages in thread From: Eli Zaretskii @ 2018-06-28 13:40 UTC (permalink / raw) To: João Távora; +Cc: monnier, vibhavp, emacs-devel > From: João Távora <joaotavora@gmail.com> > Date: Thu, 28 Jun 2018 14:23:00 +0100 > Cc: emacs-devel <emacs-devel@gnu.org>, Stefan Monnier <monnier@iro.umontreal.ca>, > Vibhav Pant <vibhavp@gmail.com> > > I'd prefer if you could commit the documentation changes together with > the code. At least some minimal documentation. I really dislike to > have significant code changes in the repository without the docs to go > with them, unless it's on a scratch/temporary branch. > > Is that feasible for you? > > Sure, no problem. The manual is mostly already the reasonably thorough > "Commentary: " section, so I'll translate that to texi and direct readers there. Thanks. > Where should I put the manual BTW? The Elisp manual, right? Yes. > Where exactly? Either inside "Parsing JSON" or right after it, on the same sectioning level. I think. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 15:54 ` New jrpc.el JSONRPC library João Távora 2018-05-20 16:02 ` Eli Zaretskii @ 2018-05-20 19:13 ` Clément Pit-Claudel 2018-05-20 20:35 ` Josh Elsasser 2018-05-20 23:11 ` João Távora 1 sibling, 2 replies; 38+ messages in thread From: Clément Pit-Claudel @ 2018-05-20 19:13 UTC (permalink / raw) To: emacs-devel On 2018-05-20 11:54, João Távora wrote: > In fact, no. At least not ideally. *All* of LSP is > language-agnostic. Doesn't LSP-mode let you use language-specific additions, for modes that require more complex interaction than the default style? ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 19:13 ` New jrpc.el JSONRPC library Clément Pit-Claudel @ 2018-05-20 20:35 ` Josh Elsasser 2018-05-20 23:22 ` João Távora 2018-05-20 23:11 ` João Távora 1 sibling, 1 reply; 38+ messages in thread From: Josh Elsasser @ 2018-05-20 20:35 UTC (permalink / raw) To: Clément Pit-Claudel; +Cc: joaotavora, emacs-devel On May 20, 2018, at 12:13 PM, Clément Pit-Claudel <cpitclaudel@gmail.com> wrote: > On 2018-05-20 11:54, João Távora wrote: >> In fact, no. At least not ideally. *All* of LSP is >> language-agnostic. > > Doesn't LSP-mode let you use language-specific additions, for modes that require more complex interaction than the default style? Yes, there’s some LSP-mode package right now with a broad set of custom extensions. cquery[1] has a set of extensions to provide richer cross-references, colour inactive code regions, and run semantic highlighting over a file. They hook in via an lsp-mode primitive right now (`lsp-client-on-notification-client’) that registers callback for custom JSON RPC methods. After working through the eglot.el source, it looks like the same thing could be managed by just defining a `eglot—server-myLangServer/customMethodName` handler. The API for sending/receive input from the server looks really clean, too -- the various custom extensions floating around out there could easily be implemented as langserver-specific packages with a depend on eglot.el [1]: https://github.com/cquery-project/emacs-cquery ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 20:35 ` Josh Elsasser @ 2018-05-20 23:22 ` João Távora 2018-05-21 3:32 ` Josh Elsasser 0 siblings, 1 reply; 38+ messages in thread From: João Távora @ 2018-05-20 23:22 UTC (permalink / raw) To: Josh Elsasser; +Cc: Clément Pit-Claudel, emacs-devel Josh Elsasser <josh@elsasser.ca> writes: > On May 20, 2018, at 12:13 PM, Clément Pit-Claudel <cpitclaudel@gmail.com> wrote: >> On 2018-05-20 11:54, João Távora wrote: > After working through the eglot.el source, it looks like the same > thing could be managed by just defining a > `eglot—server-myLangServer/customMethodName` handler. The API for > sending/receive input from the server looks really clean, too -- the > various custom extensions floating around out there could easily be > implemented as langserver-specific packages with a depend on eglot.el Thanks for the positive feedback, and indeed creating a clean API was a design goal, but notice that: (cl-defun eglot--server-myLangServer/customMethodName (proc &key param1 param2) ...) has two dashes in it. It isn't (yet) a part of the official eglot.el LSP API, and your particular example might very well become something like: (cl-defmethod eglot-handle-notification (proc (method (eql :myLangServer/customMethodName)) &key param1 param2) ...) or, for requests (cl-defmethod eglot-handle-request (proc id (method (eql :myLangServer/customMethodName)) &key param1 param2) ...) This seems cleaner than the "automagical" function names, although it has two minor drawbacks: 1. M-x imenu won't let me search methods by their specializers 2. eldoc mode doesn't highlight :param1 and :param2 while writing a method call (but this isn't really a problem in this case because you're not going to be writing the function calls yourself). João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 23:22 ` João Távora @ 2018-05-21 3:32 ` Josh Elsasser 0 siblings, 0 replies; 38+ messages in thread From: Josh Elsasser @ 2018-05-21 3:32 UTC (permalink / raw) To: João Távora; +Cc: Clément Pit-Claudel, emacs-devel Clément Pit-Claudel <cpitclaudel@gmail.com> wrote: > Thanks for the positive feedback, and indeed creating a clean API was a > design goal, but notice that: > > (cl-defun eglot--server-myLangServer/customMethodName > (proc &key param1 param2) > ...) > > has two dashes in it. It isn't (yet) a part of the official eglot.el LSP > API, and your particular example might very well become something like: Ah, fair. I was mostly thinking aloud about extending the current GitHub master with cquery support, which'd mean hideifdef support (if I was going to use it at my day job, at least). May’ve been getting a little ahead of myself :) > > (cl-defmethod eglot-handle-notification > (proc (method (eql :myLangServer/customMethodName)) &key param1 param2) > ...) > > or, for requests > > (cl-defmethod eglot-handle-request > (proc id (method (eql :myLangServer/customMethodName)) &key param1 param2) > ...) > > This seems cleaner than the "automagical" function names, although it > has two minor drawbacks: IMHO the cl-defmethod approach looks a lot nicer for someone hoping to build off eglot. Took a bit of headscratching to make the connection between the intern + fboundp and having the various handler functions, and I could see it being a pain to manage scores of magic functions scattered throughout files. Can’t wait to give eglot a spin, though! I’ll try and implement / test basic cquery support in the next couple days. I’ll need to add an extra per-proc variable to pass a required initialzationOptions param (persistent cache directory), but that shouldn’t prove too difficult. -- Josh ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 19:13 ` New jrpc.el JSONRPC library Clément Pit-Claudel 2018-05-20 20:35 ` Josh Elsasser @ 2018-05-20 23:11 ` João Távora 2018-05-21 0:14 ` Clément Pit-Claudel 1 sibling, 1 reply; 38+ messages in thread From: João Távora @ 2018-05-20 23:11 UTC (permalink / raw) To: Clément Pit-Claudel; +Cc: emacs-devel Clément Pit-Claudel <cpitclaudel@gmail.com> writes: > On 2018-05-20 11:54, João Távora wrote: >> In fact, no. At least not ideally. *All* of LSP is >> language-agnostic. > > Doesn't LSP-mode let you use language-specific additions, for modes > that require more complex interaction than the default style? If your question is about lsp-mode.el, I'm not the best person to ask. If it's about about LSP in general, here's my understanding of the spec. LSP 3.0 allows "experimental" features to the protocol to be explored between clients and servers, but as the wording suggests, the idea is that these features are incubated in a some server-client combinations, and eventually make it into the official spec in a language-agnostic way. LSP goes a long way to provide, in a language-agnostic way, things that Emacs would normally do major-modely (like "codeAction" for interactive commands, "rangeFormatting" for indentation, etc). That said, as Josh points out in the other thread, eglot.el's plan is not to shut the door on these experiments in any way. That hypothetical code should go into the major-mode's .el file, hopefully briefly, until eventually it ends up in eglot.el as a part of the official spec. João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 23:11 ` João Távora @ 2018-05-21 0:14 ` Clément Pit-Claudel 0 siblings, 0 replies; 38+ messages in thread From: Clément Pit-Claudel @ 2018-05-21 0:14 UTC (permalink / raw) To: João Távora; +Cc: emacs-devel On 2018-05-20 19:11, João Távora wrote: >> Doesn't LSP-mode let you use language-specific additions, for modes >> that require more complex interaction than the default style? > > If your question is about lsp-mode.el, I'm not the best person to ask. Sorry, I meant LSP. I'm not sure why I added -mode after this. > That said, as Josh points out in the other thread, eglot.el's plan is > not to shut the door on these experiments in any way. That hypothetical > code should go into the major-mode's .el file, hopefully briefly, until > eventually it ends up in eglot.el as a part of the official spec. Very nice. The specific scenario I'm curious about is proof assistants like Coq or F*. In these, the buffer is divided into a number of regions (definitions), each of which has a state: unsent, sent, processed. The user tells emacs which fragments to send and when, and the server responds as it processes the fragments. I'd love to be able to move to LSP, instead of having to recode clients every time. Clément. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) 2018-05-18 16:27 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) João Távora 2018-05-19 8:46 ` Eli Zaretskii @ 2018-05-19 11:34 ` Philipp Stephani 2018-05-19 14:11 ` Eli Zaretskii 2018-05-20 15:43 ` New jrpc.el JSONRPC library João Távora 1 sibling, 2 replies; 38+ messages in thread From: Philipp Stephani @ 2018-05-19 11:34 UTC (permalink / raw) To: João Távora; +Cc: Eli Zaretskii, monnier, vibhavp, emacs-devel [-- Attachment #1: Type: text/plain, Size: 5075 bytes --] João Távora <joaotavora@gmail.com> schrieb am Fr., 18. Mai 2018 um 18:28 Uhr: > Eli Zaretskii <eliz@gnu.org> writes: > > >> From: João Távora <joaotavora@gmail.com> > >> Date: Thu, 10 May 2018 23:34:40 +0100 > >> > >> I'd like to add Eglot, my new package, to GNU ELPA. > >> > >> Eglot, for Emacs Polyglot, is an Emacs client for the LSP, or Language > >> Server Protocol (https://microsoft.github.io/language-server-protocol/ > ). > > > > FWIW, I think LSP support should be part of the core Emacs > > distribution, as it's an infrastructure necessary for modern support > > of programming modes. (I didn't yet look at the package, so if it has > > separate infrastructure and application levels, perhaps only the > > infrastructure layers should be in Emacs.) > > Hi again, Eli > > So, off-list Stefan likewise suggested that some level of LSP > "infrastructure" is integrated into Emacs, which could lead to merging > eglot.el and other LSP clients like lsp-mode.el in the future. > > As JSONRPC support is a mandatory component of LSP, I've extracted a > library implementing the JSONRPC spec (see www.jsonrpc.org) out of > eglot.el. > > I propose this library, jrpc.el, for Emacs core (or GNU ELPA, or > both). I hope people can review it, comment on it, and propose changes, > particularly developers of other existing LSP-modes. I attach to the > end of this mail with a reasonably complete ";;; Commentary" header, > including a simple usage example. > Thanks. I think that separating out JSONRPC from LSP and having a JSONRPC library in core are highly valuable additions that I'd strongly support. Some review comments follow; sorry that there are so many, but I think as a foundational library jrpc.el should be as high-quality as possible: - Please add extensive unit and integration tests. There are lots of corner cases (what happens when the server times out, sends an invalid or incomplete response, sends responses in the wrong order, etc.) that should all be tested. - Please remove all defcustoms and defvars. jrpc.el is a low-level library, it doesn't make sense for it to maintain global state or be globally customizable. Rather, the timeout could either be a mandatory argument, or a reasonable constant default could be selected, and the other variables should be per-process. - Also please remove `jrpc-current-process'. There is no general notion of a "current" process; clients should maintain process state themselves and pass process objects explictly. It seems this is only used as completion helper anyway, so I think you can remove it entirely and replace it with something based on `process-list'. - Please don't use the `error' symbol directly. Instead, define your own error symbols for each error category using `define-error'. Only then clients have the chance to catch and handle errors. - `jrpc-define-process-var' isn't JSONRPC-specific, it should be moved somewhere else (subr.el)? - Instead of defining process variables, consider using a structure or EIEIO class to encapsulate all needed state for the process client. - Please make it possible to run servers remotely. Unfortunately this isn't possible with `make-process', so you have to use `start-file-process' instead if `default-directory' is remote. As an alternative to using `default-directory', consider passing a REMOTE-DIRECTORY argument to `jrpc-connect' so that the behavior doesn't depend on ambient context. - I think many of the names can be internal (i.e. contain a double dash) unless they are explicitly part of the public API. - Probably jrpc-async-request should just return nil. Or is there a situation where callers have to know about IDs or timers? I think those should be treated as implementation details and not be exposed to clients. - Please use hashtables or alists instead of plists. There are many more functions to operate on hashtables and alists, and json.c doesn't support plists. - The HTTP-style enveloping (using HTTP headers to separate responses) is technically not part of JSONRPC. You could either add support for other enveloping methods (though I'm not aware of any), or at least call this out in the documentation. - I think `string-bytes' isn't guaranteed to return the number of UTF-8 bytes. Rather you should encode the string explicitly to a unibyte string using `encode-coding-string', and then use `no-conversion' for the process coding systems. - Consider also sending (and parsing) a Content-Type header: Content-Type: application/vscode-jsonrpc; charset=utf-8. - I don't quite understand how `jrpc-ready-predicates' is supposed to be used. Are clients supposed to have some form of server-specific sidechannel to detect when the server is ready? It might be simpler to solve this problem in Emacs core, e.g. by adding a timeout to `process-send-string'. - I'd call the library jsonrpc.el (or json-rpc.el or jsonrpc2.el or...) if that name isn't already taken. Thanks again for working on this! [-- Attachment #2: Type: text/html, Size: 6226 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) 2018-05-19 11:34 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) Philipp Stephani @ 2018-05-19 14:11 ` Eli Zaretskii 2018-05-20 15:43 ` New jrpc.el JSONRPC library João Távora 1 sibling, 0 replies; 38+ messages in thread From: Eli Zaretskii @ 2018-05-19 14:11 UTC (permalink / raw) To: Philipp Stephani; +Cc: emacs-devel, joaotavora, vibhavp, monnier > From: Philipp Stephani <p.stephani2@gmail.com> > Date: Sat, 19 May 2018 13:34:45 +0200 > Cc: Eli Zaretskii <eliz@gnu.org>, monnier@iro.umontreal.ca, vibhavp@gmail.com, > emacs-devel@gnu.org > > - I think `string-bytes' isn't guaranteed to return the number of UTF-8 > bytes. Yes, it is guaranteed. See its implementation. There could be a problem if the original string includes raw bytes -- then the value returned by string-bytes will not match the length of the bytestream after encoding by UTF-8. But since JSON requires UTF-8 without any raw bytes, I'm not sure it matters that we will send an incorrect byte count in that case, because it means there's a bug in the Lisp program which produced the string. > Rather you should encode the string explicitly to a unibyte string > using `encode-coding-string', and then use `no-conversion' for the process > coding systems. That's possible, but since process-send-string encodes the string, it should be enough to bind coding-system-for-write to utf-8, and let process-send-string encode it. Assuming we trust the caller to produce a string without raw bytes, that is. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-19 11:34 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) Philipp Stephani 2018-05-19 14:11 ` Eli Zaretskii @ 2018-05-20 15:43 ` João Távora 2018-05-20 16:06 ` Eli Zaretskii ` (2 more replies) 1 sibling, 3 replies; 38+ messages in thread From: João Távora @ 2018-05-20 15:43 UTC (permalink / raw) To: Philipp Stephani; +Cc: Eli Zaretskii, monnier, vibhavp, emacs-devel Philipp Stephani <p.stephani2@gmail.com> writes: > João Távora <joaotavora@gmail.com> schrieb am Fr., 18. Mai 2018 um 18:28 Uhr: > >> I propose this library, jrpc.el, for Emacs core (or GNU ELPA, or > Thanks. I think that separating out JSONRPC from LSP and having a > JSONRPC library in core are highly valuable additions that I'd > strongly support. Some review comments follow; sorry that there are > so many, but I think as a foundational library jrpc.el should be as > high-quality as possible: Apology *not accepted* :-). Having reviewers provide so many insightful comments is a previlige and a pleasure. The high standards of the Emacs project are one of the reasons I like to contribute to it. I'll triaged your comments into "No", "Maybe", "Ok". The former require some more convincing on your part :-) I've already applied some of your suggestions (and some that Stefan sent off-list, too). See the jsonrpc-refactor branch of Eglot's github repository. The latest version of jsonrpc.el (I changed the name) lives in: https://raw.githubusercontent.com/joaotavora/eglot/jsonrpc-refactor/jsonrpc.el > - Please add extensive unit and integration tests. There are lots of > corner cases (what happens when the server times out, sends an invalid > or incomplete response, sends responses in the wrong order, etc.) that > should all be tested. OK. It's in the plans, if not in the stars. Help appreciated. :-) We can convert some of those examples to tests. Eglot's jsonrpc-refactor branch, where I'm currently developing jsonrpc.el, already has integration tests, so at least a part of jsonrpc.el is already covered. > - Please remove all defcustoms and defvars. jrpc.el is a low-level > library, it doesn't make sense for it to maintain global state or be > globally customizable. Rather, the timeout could either be a mandatory > argument, or a reasonable constant default could be selected, and the > other variables should be per-process. OK. Makes sense. Done. > - Also please remove `jrpc-current-process'. There is no general > notion of a "current" process; Maybe. Not convinced. It's opininated, sure. It supposes that just most JSONRPC applications would have the notion of a "session". So jsonrpc.el makes this suggestion to its users, allowing them to configure what a session means for them. They would also get access to M-x jsonrpc-events-buffer. This is entirely optional on the client's part, but I see no harm in JSONRPC providing this convenience. Perhaps the name is off and should mention "session". > - Please don't use the `error' symbol directly. Instead, define your > own error symbols for each error category using `define-error'. Only > then clients have the chance to catch and handle errors. OK. Done. Though technically they can already catch and handle them. What they can't do is catch *only* JSONRPC errors and let the others thru. I happen to think that's a bad idea because a JSONRPC error is an error in your program. But there's no reason to deny this, too. > - `jrpc-define-process-var' isn't JSONRPC-specific, it should be moved > somewhere else (subr.el)? Yes, but if we do this it'll be harder for clients like eglot.el to keep supporting Emacs 26.1 (assuming jsonrpc.el would be in GNU ELPA and master simultaneously). > - Instead of defining process variables, consider using a structure or > EIEIO class to encapsulate all needed state for the process client. > Maybe. Structures is a bad idea, but defclass isn't at all. > - Please make it possible to run servers remotely. Unfortunately this > isn't possible with `make-process', so you have to use > `start-file-process' instead if `default-directory' is remote. As an > alternative to using `default-directory', consider passing a > REMOTE-DIRECTORY argument to `jrpc-connect' so that the behavior > doesn't depend on ambient context. Maybe. I don't fully understand the example. But jsonrpc-connect allows you to pass any pre-connected process object. It will replace its buffer, sentinel and filter. In the "autostart-server" branch of eglot.el, for example, there is a function to I start a local process with special arguments to that it open a server, then connect to it, then return that connected process. This is for Windows LSP servers that can't to stdio comms. > - I think many of the names can be internal (i.e. contain a double > dash) unless they are explicitly part of the public API. Maybe. What names? The names that I want to make public are already public, and all the others aren't. > - Probably jrpc-async-request should just return nil. Or is there a > situation where callers have to know about IDs or timers? I think > those should be treated as implementation details and not be exposed > to clients. OK. Makes sense. > - Please use hashtables or alists instead of plists. There are many > more functions to operate on hashtables and alists, Maybe. Hmm, I really really don't know about this. Have you tried cl-destructuring-bind with plists? It really is the SH&T for JSON. > and json.c doesn't support plists. That's the superior argument. I didn't know about json.c until very recently. I'll play along, but can you help me rewrite jsonrpc-lambda to something less consing perhaps? (cl-defmacro jsonrpc-lambda (cl-lambda-list &body body) (declare (indent 1) (debug (sexp &rest form))) (let ((jsonobj (gensym "jsonrpc-lambda-elem"))) `(lambda (,jsonobj) (apply (cl-function (lambda ,cl-lambda-list ,@body)) (let (plist) (maphash (lambda (k v) (push v plist) (push (if (keywordp k) k (intern (format ":%s" k))) plist)) ,jsonobj) plist))))) > - The HTTP-style enveloping (using HTTP headers to separate responses) > is technically not part of JSONRPC. You could either add support for > other enveloping methods (though I'm not aware of any), or at least > call this out in the documentation. OK. Yes, it's most definetly not JSONRPC. It's an LSP thing. This is the reason I need to rework the "process" object into a proper defclass and make generic functions that only apply this enveloping to the proper LSPlike objects (which could/should be the default object types returned by jsonrpc-connect). > - I think `string-bytes' isn't guaranteed to return the number of > UTF-8 bytes. Rather you should encode the string explicitly to a > unibyte string using `encode-coding-string', and then use > `no-conversion' for the process coding systems. > I think Eli has already answered this authoritatively. > - Consider also sending (and parsing) a Content-Type header: > Content-Type: application/vscode-jsonrpc; charset=utf-8. OK. > - I don't quite understand how `jrpc-ready-predicates' is supposed to > be used. Are clients supposed to have some form of server-specific > sidechannel to detect when the server is ready? Yes that's precisely it (though it's entirely optional on the client part). > It might be simpler to solve this problem in Emacs core, e.g. by > adding a timeout to `process-send-string'. No, the "ready" here is for the application, not the transport. I understand it's obscure for a reader, so let me explain and then this might go into the doc. Obviously, this came out of necessity in eglot.el, but I think its good functionality to keep in jsonrpc.el refactor (and it'd be really hard to implement outside it.) The problem arises in the ordering of sending multiple asynchronous JSONRPC requests. So, for example in LSP, it doesn't make sense to send most requests if there are outstanding changes in the buffer that you haven't told the server about. But because the latter can come in through idle timers, it's useful to have this generic :deferred mechanism: you just put in `jsonrpc-ready-predicates' a function that returns nil if there are outstanding changes and then write code without worrying about whether the server is ready in that particular situation. This is better than sprinkling the code with "send-outstanding-changes-just-to-be-sure" before you make requests. RLS (Rust's Language Server) has a more complicated limitation, so eglot.el puts an additional predicate there for rust-mode.el > - I'd call the library jsonrpc.el (or json-rpc.el or jsonrpc2.el > or...) if that name isn't already taken. OK. I don't think it is. Done João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 15:43 ` New jrpc.el JSONRPC library João Távora @ 2018-05-20 16:06 ` Eli Zaretskii 2018-05-20 16:18 ` João Távora 2018-05-21 13:30 ` Aaron Ecay 2018-05-24 10:02 ` Philipp Stephani 2 siblings, 1 reply; 38+ messages in thread From: Eli Zaretskii @ 2018-05-20 16:06 UTC (permalink / raw) To: João Távora; +Cc: p.stephani2, monnier, vibhavp, emacs-devel > From: joaotavora@gmail.com (João Távora) > Date: Sun, 20 May 2018 16:43:59 +0100 > Cc: Eli Zaretskii <eliz@gnu.org>, monnier@iro.umontreal.ca, vibhavp@gmail.com, > emacs-devel@gnu.org > > > - I think `string-bytes' isn't guaranteed to return the number of > > UTF-8 bytes. Rather you should encode the string explicitly to a > > unibyte string using `encode-coding-string', and then use > > `no-conversion' for the process coding systems. > > > I think Eli has already answered this authoritatively. Note that my response did assume you will bind coding-system-for-write to utf-8 around the call to process-send-string, because relying on it being so by default is asking for trouble. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 16:06 ` Eli Zaretskii @ 2018-05-20 16:18 ` João Távora 0 siblings, 0 replies; 38+ messages in thread From: João Távora @ 2018-05-20 16:18 UTC (permalink / raw) To: Eli Zaretskii; +Cc: p.stephani2, Stefan Monnier, vibhavp, emacs-devel [-- Attachment #1: Type: text/plain, Size: 829 bytes --] Ok, will do. On Sun, May 20, 2018, 17:06 Eli Zaretskii <eliz@gnu.org> wrote: > > From: joaotavora@gmail.com (João Távora) > > Date: Sun, 20 May 2018 16:43:59 +0100 > > Cc: Eli Zaretskii <eliz@gnu.org>, monnier@iro.umontreal.ca, > vibhavp@gmail.com, > > emacs-devel@gnu.org > > > > > - I think `string-bytes' isn't guaranteed to return the number of > > > UTF-8 bytes. Rather you should encode the string explicitly to a > > > unibyte string using `encode-coding-string', and then use > > > `no-conversion' for the process coding systems. > > > > > I think Eli has already answered this authoritatively. > > Note that my response did assume you will bind coding-system-for-write > to utf-8 around the call to process-send-string, because relying on it > being so by default is asking for trouble. > [-- Attachment #2: Type: text/html, Size: 1529 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 15:43 ` New jrpc.el JSONRPC library João Távora 2018-05-20 16:06 ` Eli Zaretskii @ 2018-05-21 13:30 ` Aaron Ecay 2018-05-21 13:43 ` João Távora 2018-05-24 10:02 ` Philipp Stephani 2 siblings, 1 reply; 38+ messages in thread From: Aaron Ecay @ 2018-05-21 13:30 UTC (permalink / raw) To: João Távora, Philipp Stephani Cc: Eli Zaretskii, monnier, vibhavp, emacs-devel Hi João, One small remark: 2018ko maiatzak 20an, João Távora-ek idatzi zuen: [...] >> - Please use hashtables or alists instead of plists. There are many >> more functions to operate on hashtables and alists, > > Maybe. Hmm, I really really don't know about this. Have you tried > cl-destructuring-bind with plists? It really is the SH&T for JSON. You can use pcase-let* and the ‘map’ pcase pattern (from the built-in map.el) as a substitute when working with alists and hash tables. IOW, the following two code snippets are equivalent: (pcase-let* (((map foo bar) '((foo . 1) (bar . 2)))) (+ foo bar)) (cl-destructuring-bind (&key foo bar) '(:foo 1 :bar 2) (+ foo bar)) -- Aaron Ecay ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-21 13:30 ` Aaron Ecay @ 2018-05-21 13:43 ` João Távora 2018-05-21 14:37 ` Aaron Ecay 2018-05-21 14:42 ` Stefan Monnier 0 siblings, 2 replies; 38+ messages in thread From: João Távora @ 2018-05-21 13:43 UTC (permalink / raw) To: Aaron Ecay; +Cc: Philipp Stephani, emacs-devel, Eli Zaretskii, vibhavp, monnier [-- Attachment #1: Type: text/plain, Size: 966 bytes --] Hi Aaron, Nice, i didn't know about that. Does it have an equivalent to &allow-other-keys and &rest? João On Mon, May 21, 2018, 14:31 Aaron Ecay <aaronecay@gmail.com> wrote: > Hi João, > > One small remark: > > 2018ko maiatzak 20an, João Távora-ek idatzi zuen: > > [...] > > >> - Please use hashtables or alists instead of plists. There are many > >> more functions to operate on hashtables and alists, > > > > Maybe. Hmm, I really really don't know about this. Have you tried > > cl-destructuring-bind with plists? It really is the SH&T for JSON. > > You can use pcase-let* and the ‘map’ pcase pattern (from the built-in > map.el) as a substitute when working with alists and hash tables. IOW, > the following two code snippets are equivalent: > > (pcase-let* (((map foo bar) '((foo . 1) (bar . 2)))) > (+ foo bar)) > > (cl-destructuring-bind (&key foo bar) '(:foo 1 :bar 2) > (+ foo bar)) > > -- > Aaron Ecay > [-- Attachment #2: Type: text/html, Size: 1431 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-21 13:43 ` João Távora @ 2018-05-21 14:37 ` Aaron Ecay 2018-05-21 19:06 ` João Távora 2018-05-21 14:42 ` Stefan Monnier 1 sibling, 1 reply; 38+ messages in thread From: Aaron Ecay @ 2018-05-21 14:37 UTC (permalink / raw) To: João Távora; +Cc: emacs-devel Hi João, 2018ko maiatzak 21an, João Távora-ek idatzi zuen: > > Hi Aaron, > > Nice, i didn't know about that. Does it have an equivalent to > &allow-other-keys and &rest? &allow-other-keys is always true for the pcase version. As I understand what &rest does, it lets you bind certain keys, and then bind the map minus those keys to another variable. Thereʼs nothing like that out of the box, but it can easily be implemented: (pcase-defmacro map-and-rest (rest-var &rest keys) `(and (map ,@keys) (app (map-filter (lambda (key _val) (not (memq key ',keys)))) ,rest-var))) (pcase-let* (((map-and-rest rest foo bar) '((foo . 1) (bar . 2) (baz . 3)))) (list foo bar rest)) ;; -> (1 2 ((baz . 3))) -- Aaron Ecay ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-21 14:37 ` Aaron Ecay @ 2018-05-21 19:06 ` João Távora 2018-05-23 17:57 ` Aaron Ecay 0 siblings, 1 reply; 38+ messages in thread From: João Távora @ 2018-05-21 19:06 UTC (permalink / raw) To: Aaron Ecay; +Cc: emacs-devel Aaron Ecay <aaronecay@gmail.com> writes: > Hi João, > > 2018ko maiatzak 21an, João Távora-ek idatzi zuen: >> >> Hi Aaron, >> >> Nice, i didn't know about that. Does it have an equivalent to >> &allow-other-keys and &rest? > > &allow-other-keys is always true for the pcase version. > As I understand what &rest does, it lets you bind certain keys, and > then bind the map minus those keys to another variable. No, not "minus those keys", those keys are included in the &rest var too. > Thereʼs nothing like that out of > the box, but it can easily be implemented: > > (pcase-defmacro map-and-rest (rest-var &rest keys) > `(and (map ,@keys) > (app (map-filter (lambda (key _val) (not (memq key ',keys)))) ,rest-var))) > > (pcase-let* (((map-and-rest rest foo bar) '((foo . 1) (bar . 2) (baz . 3)))) > (list foo bar rest)) > ;; -> (1 2 ((baz . 3))) Hmm: 1. Not particularly readable for someone that encounters the second form. Can one M-. from map-and-rest into the defmacro to learn what it is doing? 2. The absence of control over &allow-other-keys is a big minus. It's very useful to a) check that the object isn't providing an unknown key, or to check that you haven't mistyped a key name. Can you code it into the pcase-defmacro? 3. Also can you code the more complex capabilities of &key into there? ;; default values (cl-destructuring-bind (&rest rest &key (foo "default") bar) nil (list foo bar)) ;; -> ("default" nil) ;; provided-p (cl-destructuring-bind (&rest rest &key (foo nil foo-provided-p) bar) '(:bar nil :foo nil) (list foo bar foo-provided-p)) ;; -> (nil nil t) ;; alias (cl-destructuring-bind (&rest rest &key ((:extremely-long-foo-with-the-airplane foo)) bar) '(:extremely-long-foo-with-the-airplane 42) (list foo bar)) ;; -> (42 nil) I think clients of jsonrpc.el should be able to use whatever they want to destructure JSON (though I recommend plists and I think it should be the default datatype passed to the dispatcher). Internally, in jsonrpc.el there isn't an awlful lot of destructuring going on, so if someone can provide a superior argument (performance?) backed up by some measurements, I don't mind switching. It's just that the readability argument isn't really holding up against cl lambda lists. João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-21 19:06 ` João Távora @ 2018-05-23 17:57 ` Aaron Ecay 2018-05-23 18:02 ` Stefan Monnier 2018-05-23 20:40 ` João Távora 0 siblings, 2 replies; 38+ messages in thread From: Aaron Ecay @ 2018-05-23 17:57 UTC (permalink / raw) To: João Távora; +Cc: emacs-devel Hi João, 2018ko maiatzak 21an, João Távora-ek idatzi zuen: > No, not "minus those keys", those keys are included in the &rest var > too. In that case, Stefanʼs reply tells you how to get the equivalent. > > > Hmm: > > 1. Not particularly readable for someone that encounters the second > form. Can one M-. from map-and-rest into the defmacro to learn what > it is doing? No, but the docstrings of all the pcase-defmacros are automatically included in the docstring of pcase (the main entry point). Of course, the pcase-defmacro I wrote didnʼt have a docstring because it was just a quick proof of concept. > > 2. The absence of control over &allow-other-keys is a big minus. It's > very useful to a) check that the object isn't providing an unknown > key, or to check that you haven't mistyped a key name. Can you > code it into the pcase-defmacro? Itʼs possible to do so, but I think it would be more perspicuous to have the validation and destructuring separate. One might want to assert that the record contains some field X, without caring what Xʼs value is (and thus, not needing to bind X to a lisp variable). > 3. Also can you code the more complex capabilities of &key into there? > > ;; default values > (cl-destructuring-bind (&rest rest &key (foo "default") bar) nil > (list foo bar)) ;; -> ("default" nil) > > ;; provided-p > (cl-destructuring-bind (&rest rest &key (foo nil foo-provided-p) bar) > '(:bar nil :foo nil) > (list foo bar foo-provided-p)) ;; -> (nil nil t) > > ;; alias > (cl-destructuring-bind (&rest rest &key > ((:extremely-long-foo-with-the-airplane foo)) bar) > '(:extremely-long-foo-with-the-airplane 42) > (list foo bar)) ;; -> (42 nil) Again one could, but I personally donʼt see the advantage of (foo nil foo-provided-p) over (memq 'foo (map-keys my-alist)). Anyway, I first pointed out the possibility of using pcase because I understood you to be saying that the features of cl-destructuring-bind+plist were not easily replicated by anything+alists. I hope to have shown that those features are indeed available. Itʼs still up to you if you are satisfied by the minutiae; I just didnʼt want you to labor under the misconception that there are no alternatives. :) > > I think clients of jsonrpc.el should be able to use whatever they want > to destructure JSON (though I recommend plists and I think it should be > the default datatype passed to the dispatcher). Indeed, json.el in emacs core has the json-object-type variable to control the plist-vs-alist-ness of decoded JSON. Maybe your library can also work with that variable? -- Aaron Ecay ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-23 17:57 ` Aaron Ecay @ 2018-05-23 18:02 ` Stefan Monnier 2018-05-23 20:40 ` João Távora 1 sibling, 0 replies; 38+ messages in thread From: Stefan Monnier @ 2018-05-23 18:02 UTC (permalink / raw) To: emacs-devel >> 1. Not particularly readable for someone that encounters the second >> form. Can one M-. from map-and-rest into the defmacro to learn what >> it is doing? > No, but the docstrings of all the pcase-defmacros are automatically Also the "No" above could/should be changed via `describe-symbol-backends`. Stefan ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-23 17:57 ` Aaron Ecay 2018-05-23 18:02 ` Stefan Monnier @ 2018-05-23 20:40 ` João Távora 2018-05-24 2:07 ` Stefan Monnier 1 sibling, 1 reply; 38+ messages in thread From: João Távora @ 2018-05-23 20:40 UTC (permalink / raw) To: Aaron Ecay; +Cc: emacs-devel Aaron Ecay <aaronecay@gmail.com> writes: > Itʼs possible to do so, but I think it would be more perspicuous to have > the validation and destructuring separate. So that if you change one you have to change the other? And type the key over and over? > One might want to assert that the record contains some field X, > without caring what Xʼs value is (and thus, not needing to bind X to a > lisp variable). Just use '_' (cl-destructuring-bind (&key _boring-key very-important) '(:very-important 42 :boring-key 92) very-important) > Again one could, but I personally donʼt see the advantage of (foo nil > foo-provided-p) over (memq 'foo (map-keys my-alist)). You're comparing apples to oranges. Here's the comparison: with this form, where "foo" appears once. (cl-destructuring-bind (&rest all &key (foo 'default provided-p)) a-plist-map ...) You are, to the best of my pcase.el knowledge so far, doing the same as: (pcase-let* ((`(. ,(and all (map foo))) any-kind-of-map) (provided-p (and (memq 'foo (map-keys all)) t)) (foo (if provided-p foo 'default))) (some (code (to-check (for-invalid-keys-in) all) '(foo))) ... ) Where foo appears 5 times. And I didn't even mention the fact that you can use the cl-dbind lambda list in a cl-defmethod, for example. If you `apply' it to a JSON object you get all the args ready to use + the error reporting when you botch the funcall because you botched the object (or the spec). You could do similar stuff with pcase-lambda (though, again, much more verbosely). But then again that isn't as versatile and pervasive as cl-defgeneric. > Anyway, I first pointed out the possibility of using pcase because I > understood you to be saying that the features of > cl-destructuring-bind+plist were not easily replicated by > anything+alists. I hope to have shown that those features are indeed > available. Sorry, but they aren't: you have to program them (which is what I asked you to demonstrate). ... just like cl-dbind doesn't have the main feature of map.el, which is a uniform interface to different map types. But we were discussing specifically the merits of plists. > Itʼs still up to you if you are satisfied by the minutiae; You know what they say: "the devil is in the minutiae!" :-) > Indeed, json.el in emacs core has the json-object-type variable to > control the plist-vs-alist-ness of decoded JSON. Maybe your library can > also work with that variable? Yes, maybe. It must work with json.c above all (and it doesn't support plists apparently). João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-23 20:40 ` João Távora @ 2018-05-24 2:07 ` Stefan Monnier 0 siblings, 0 replies; 38+ messages in thread From: Stefan Monnier @ 2018-05-24 2:07 UTC (permalink / raw) To: emacs-devel > (cl-destructuring-bind (&key _boring-key very-important) > '(:very-important 42 :boring-key 92) > very-important) FWIW, we could of course do a (pcase-defmacro cl (...) ...) such that (cl-destructuring-bind PAT VAL EXP) can be written (pcase-let (((cl PAT) VAL)) EXP) This is the only significant advantage of pcase patterns: they're extensible, whereas CL's destructuring patterns are not. CL's destructuring patterns in return have the advantage of being available in cl-defmethod whereas there's no pcase-defmethod (yet?). With respect to the support for "default" and "provided-p" thingies, as recently mentioned elsewhere I prefer designs where we avoid distinguishing nil from "absent", so I rarely if ever need them. But of course, distinguishing nil from "absent" also comes with advantages, so it's just the usual style tradeoffs: If you're used to one style the other looks inconvenient. IOW, it's another bikeshed color. Stefan ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-21 13:43 ` João Távora 2018-05-21 14:37 ` Aaron Ecay @ 2018-05-21 14:42 ` Stefan Monnier 1 sibling, 0 replies; 38+ messages in thread From: Stefan Monnier @ 2018-05-21 14:42 UTC (permalink / raw) To: João Távora Cc: Aaron Ecay, Philipp Stephani, Eli Zaretskii, vibhavp, emacs-devel > Nice, i didn't know about that. Does it have an equivalent to > &allow-other-keys and &rest? It behaves as if &allow-other-keys is always given currently. As for &rest you can do (pcase-let* ((`(,first . ,(and rest (map foo bar))) '(0 (foo . 1) (bar . 2)))) (list first rest foo bar)) => (0 ((foo . 1) (bar . 2)) 1 2) At that point, the readability suffers, tho. -- Stefan ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-20 15:43 ` New jrpc.el JSONRPC library João Távora 2018-05-20 16:06 ` Eli Zaretskii 2018-05-21 13:30 ` Aaron Ecay @ 2018-05-24 10:02 ` Philipp Stephani 2018-05-24 17:25 ` João Távora 2 siblings, 1 reply; 38+ messages in thread From: Philipp Stephani @ 2018-05-24 10:02 UTC (permalink / raw) To: João Távora; +Cc: Eli Zaretskii, monnier, vibhavp, emacs-devel [-- Attachment #1: Type: text/plain, Size: 12955 bytes --] Thanks! João Távora <joaotavora@gmail.com> schrieb am So., 20. Mai 2018 um 17:44 Uhr: > Philipp Stephani <p.stephani2@gmail.com> writes: > > > João Távora <joaotavora@gmail.com> schrieb am Fr., 18. Mai 2018 um > 18:28 Uhr: > > > >> I propose this library, jrpc.el, for Emacs core (or GNU ELPA, or > > > Thanks. I think that separating out JSONRPC from LSP and having a > > JSONRPC library in core are highly valuable additions that I'd > > strongly support. Some review comments follow; sorry that there are > > so many, but I think as a foundational library jrpc.el should be as > > high-quality as possible: > > Apology *not accepted* :-). Having reviewers provide so many insightful > comments is a previlige and a pleasure. The high standards of the Emacs > project are one of the reasons I like to contribute to it. > > I'll triaged your comments into "No", "Maybe", "Ok". The former require > some more convincing on your part :-) > > I've already applied some of your suggestions (and some that Stefan sent > off-list, too). See the jsonrpc-refactor branch of Eglot's github > repository. The latest version of jsonrpc.el (I changed the name) lives > in: > > > https://raw.githubusercontent.com/joaotavora/eglot/jsonrpc-refactor/jsonrpc.el Thanks! Could you maybe use GitHub PRs for further changes? I find it much easier to comment on them instead of via mail. > > > > - Please add extensive unit and integration tests. There are lots of > > corner cases (what happens when the server times out, sends an invalid > > or incomplete response, sends responses in the wrong order, etc.) that > > should all be tested. > > OK. It's in the plans, if not in the stars. Help appreciated. :-) We can > convert some of those examples to tests. Eglot's jsonrpc-refactor > branch, where I'm currently developing jsonrpc.el, already has > integration tests, so at least a part of jsonrpc.el is already covered. > It's OK to add the tests once the API stabilizes. It would be great to have tests e.g. for - server process crashes - server takes too long to respond - invalid responses - mismatched IDs - very large responses etc., just the usual failure modes. > > > - Please remove all defcustoms and defvars. jrpc.el is a low-level > > library, it doesn't make sense for it to maintain global state or be > > globally customizable. Rather, the timeout could either be a mandatory > > argument, or a reasonable constant default could be selected, and the > > other variables should be per-process. > > OK. Makes sense. Done. > > > - Also please remove `jrpc-current-process'. There is no general > > notion of a "current" process; > > Maybe. Not convinced. It's opininated, sure. It supposes that just most > JSONRPC applications would have the notion of a "session". So jsonrpc.el > makes this suggestion to its users, allowing them to configure what a > session means for them. They would also get access to M-x > jsonrpc-events-buffer. This is entirely optional on the client's part, > but I see no harm in JSONRPC providing this convenience. Perhaps the > name is off and should mention "session". > When reading the code, I was a bit confused to see the notion of a "current process". Later, I realized that it's only used for some debug helpers and not for the core API. I think it would make sense to separate the debug helpers from the core API (connect, send, receive) more cleanly. I'm even wondering whether the debug helpers are needed at all. Probably you saw a need for them, otherwise you wouldn't have added them. But maybe at least in the beginning Emacs's builtin debug functionality (edebug etc.) is already good enough? If at all, please separate the debug helpers from the main API (with a comment block or similar) and move them to the end, as they are not essential. > > > - Please don't use the `error' symbol directly. Instead, define your > > own error symbols for each error category using `define-error'. Only > > then clients have the chance to catch and handle errors. > > OK. Done. Though technically they can already catch and handle > them. What they can't do is catch *only* JSONRPC errors and let the > others thru. I happen to think that's a bad idea because a JSONRPC error > is an error in your program. But there's no reason to deny this, too. > When designing a general-purpose API, it's often unknown how it will be used; often it's used in unpredicted and surprising way. Even if we don't see a good use case to catch JSON-RPC errors now, it's quite likely that such a use case will show up in the future once the library is more widely used, and then the very minor cost of defining specific error symbols pays off. Please also use specific error symbols in `jsonrpc-error'. > > > - `jrpc-define-process-var' isn't JSONRPC-specific, it should be moved > > somewhere else (subr.el)? > > Yes, but if we do this it'll be harder for clients like eglot.el to keep > supporting Emacs 26.1 (assuming jsonrpc.el would be in GNU ELPA and > master simultaneously). > > > - Instead of defining process variables, consider using a structure or > > EIEIO class to encapsulate all needed state for the process client. > > > Maybe. Structures is a bad idea, but defclass isn't at all. > I'm not so much opposed to structures, it's more of an implementation detail and a matter of taste whether you prefer structures or classes. However, there's a pretty strong argument that I forgot about: for robustness you need a way to restart process and even have them restarted automatically. Let's say a server process crashes or the user kills it; you definitely don't want LSP/JSON-RPC to be unusable until the user restarts Emacs. Therefore, instead of having the 'connect' function create the process, the send and receive functions should ensure that it's running; i.e. shift the paradigm from "edge-triggered" (process is created once the user calls a function) to "level-triggered" (all functions that need a process make sure the process is available). That is at the same time more robust and easier to understand. However, it practically requires using a structure or class as main data type, because the process object needs to be replaced after creating the client. > > > - Please make it possible to run servers remotely. Unfortunately this > > isn't possible with `make-process', so you have to use > > `start-file-process' instead if `default-directory' is remote. As an > > alternative to using `default-directory', consider passing a > > REMOTE-DIRECTORY argument to `jrpc-connect' so that the behavior > > doesn't depend on ambient context. > > Maybe. I don't fully understand the example. But jsonrpc-connect allows > you to > pass any pre-connected process object. It will replace its buffer, > sentinel and filter. In the "autostart-server" branch of eglot.el, for > example, there is a function to I start a local process with special > arguments to that it open a server, then connect to it, then return that > connected process. This is for Windows LSP servers that can't to stdio > comms. > It's good to have such an extensibility mechanism, and I indeed overlooked that you can pass a process object to the connect function. I'm wondering whether that should indeed be the *only* option: leave the process creation business entirely to the client, and only accept a process object (or rather, process factory function, because you want to restart processes as necessary, see above). That is, have the following interface instead of the 'connect': (defun jsonrpc-make-client (process-factory) "PROCESS-FACTORY gets called an unspecified number of times and has to return a new live process object each time it's called." ...) Optionally you could provide convenience functions to return such factories for `make-process' etc., but you could also leave that to the users entirely. > > > - I think many of the names can be internal (i.e. contain a double > > dash) unless they are explicitly part of the public API. > > Maybe. What names? The names that I want to make public are already > public, and all the others aren't. > I'm thinking about things like jsonrpc-warn, jsonrpc-outstanding-request-ids, etc. These seem like implementation details that clients shouldn't care about. > > > - Probably jrpc-async-request should just return nil. Or is there a > > situation where callers have to know about IDs or timers? I think > > those should be treated as implementation details and not be exposed > > to clients. > > OK. Makes sense. > > > - Please use hashtables or alists instead of plists. There are many > > more functions to operate on hashtables and alists, > > Maybe. Hmm, I really really don't know about this. Have you tried > cl-destructuring-bind with plists? It really is the SH&T for JSON. > Not really, how would it work? Typically I use plain gethash, assq, etc. Or `let-alist'. > > > and json.c doesn't support plists. > > That's the superior argument. I didn't know about json.c until very > recently. I'll play along, but can you help me rewrite jsonrpc-lambda to > something less consing perhaps? > > (cl-defmacro jsonrpc-lambda (cl-lambda-list &body body) > (declare (indent 1) (debug (sexp &rest form))) > (let ((jsonobj (gensym "jsonrpc-lambda-elem"))) > `(lambda (,jsonobj) > (apply (cl-function (lambda ,cl-lambda-list ,@body)) > (let (plist) > (maphash (lambda (k v) > (push v plist) > (push (if (keywordp k) k > (intern (format ":%s" k))) > plist)) > ,jsonobj) > plist))))) > Why do we need jsonrpc-lambda? Writing a normal lambda and some gethash or assq calls doesn't sound overly bad to me. I'd just remove jsonrpc-lambda entirely; it's the kind of syntactic sugar that I think doesn't make code significantly more readable. > > > - The HTTP-style enveloping (using HTTP headers to separate responses) > > is technically not part of JSONRPC. You could either add support for > > other enveloping methods (though I'm not aware of any), or at least > > call this out in the documentation. > > OK. Yes, it's most definetly not JSONRPC. It's an LSP thing. This is the > reason I need to rework the "process" object into a proper defclass and > make generic functions that only apply this enveloping to the proper > LSPlike objects (which could/should be the default object types returned > by jsonrpc-connect). > > > - I think `string-bytes' isn't guaranteed to return the number of > > UTF-8 bytes. Rather you should encode the string explicitly to a > > unibyte string using `encode-coding-string', and then use > > `no-conversion' for the process coding systems. > > > I think Eli has already answered this authoritatively. > > > - Consider also sending (and parsing) a Content-Type header: > > Content-Type: application/vscode-jsonrpc; charset=utf-8. > > OK. > > > - I don't quite understand how `jrpc-ready-predicates' is supposed to > > be used. Are clients supposed to have some form of server-specific > > sidechannel to detect when the server is ready? > > Yes that's precisely it (though it's entirely optional on the client > part). > > > It might be simpler to solve this problem in Emacs core, e.g. by > > adding a timeout to `process-send-string'. > > No, the "ready" here is for the application, not the transport. > > I understand it's obscure for a reader, so let me explain and then this > might go into the doc. Obviously, this came out of necessity in > eglot.el, but I think its good functionality to keep in jsonrpc.el > refactor (and it'd be really hard to implement outside it.) > If it should stay in jsonrpc.el, then it should be clear without referring to eglot.el. > > The problem arises in the ordering of sending multiple asynchronous > JSONRPC requests. So, for example in LSP, it doesn't make sense to send > most requests if there are outstanding changes in the buffer that you > haven't told the server about. But because the latter can come in > through idle timers, it's useful to have this generic :deferred > mechanism: you just put in `jsonrpc-ready-predicates' a function that > returns nil if there are outstanding changes and then write code without > worrying about whether the server is ready in that particular > situation. This is better than sprinkling the code with > "send-outstanding-changes-just-to-be-sure" before you make requests. > Hmm. I need to think a bit more about this. I guess it makes sense, but please make it a property of the client, not a global variable for now. [-- Attachment #2: Type: text/html, Size: 16201 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: New jrpc.el JSONRPC library 2018-05-24 10:02 ` Philipp Stephani @ 2018-05-24 17:25 ` João Távora 0 siblings, 0 replies; 38+ messages in thread From: João Távora @ 2018-05-24 17:25 UTC (permalink / raw) To: Philipp Stephani; +Cc: Eli Zaretskii, monnier, vibhavp, emacs-devel Philipp Stephani <p.stephani2@gmail.com> writes: > https://raw.githubusercontent.com/joaotavora/eglot/jsonrpc-refactor/jsonrpc.el > > Thanks! Could you maybe use GitHub PRs for further changes? I find it > much easier to comment on them instead of via mail. Well, the idea was to hold the discussion here since I'm proposing this as a core library. But I guess we can do both, why not... Make your pull requests against the `jsonrpc-refactor' branch. > It's OK to add the tests once the API stabilizes. It would be great to have tests e.g. for > - server process crashes > - server takes too long to respond > - invalid responses > - mismatched IDs > - very large responses > etc., just the usual failure modes. Have a look at the eglot-tests.el, it's testing some of these things (crashes, reconnections, timeouts, etc...) though from an LSP standpoint. > > - Also please remove `jrpc-current-process'. There is no general > > notion of a "current" process; > > Maybe. Not convinced. It's opininated, sure. It supposes that just most > JSONRPC applications would have the notion of a "session". So jsonrpc.el > makes this suggestion to its users, allowing them to configure what a > session means for them. They would also get access to M-x > jsonrpc-events-buffer. This is entirely optional on the client's part, > but I see no harm in JSONRPC providing this convenience. Perhaps the > name is off and should mention "session". > > When reading the code, I was a bit confused to see the notion of a > "current process". Later, I realized that it's only used for some > debug helpers and not for the core API. I think it would make sense > to separate the debug helpers from the core API (connect, send, > receive) more cleanly. I'm even wondering whether the debug helpers > are needed at all. Probably you saw a need for them, otherwise you > wouldn't have added them. But maybe at least in the beginning Emacs's > builtin debug functionality (edebug etc.) is already good enough? If > at all, please separate the debug helpers from the main API (with a > comment block or similar) and move them to the end, as they are not > essential. jsonrpc-events-buffer is a logging facility specially apt at printing JSON objects and edebug is not really practical to watch sessions of JSONRPC at a glance. I guess I can extract that stuff into jsonrpc-debug.el, but lets make that one of the last things. > > - Please don't use the `error' symbol directly. Instead, define your > > own error symbols for each error category using `define-error'. Only > > then clients have the chance to catch and handle errors. > > OK. Done. Though technically they can already catch and handle > them. What they can't do is catch *only* JSONRPC errors and let the > others thru. I happen to think that's a bad idea because a JSONRPC error > is an error in your program. But there's no reason to deny this, too. > > When designing a general-purpose API, it's often unknown how it will > be used; often it's used in unpredicted and surprising way. Yeah, but it's the API's responsibility to guide the user. Common things easy, complicate things possible. I just made it possible. > Please also use specific error symbols in `jsonrpc-error'. I think I already did, jsonrpc-error-message and jsonrpc-error-code > > - `jrpc-define-process-var' isn't JSONRPC-specific, it should be moved > > somewhere else (subr.el)? > > Yes, but if we do this it'll be harder for clients like eglot.el to keep > supporting Emacs 26.1 (assuming jsonrpc.el would be in GNU ELPA and > master simultaneously). Never mind, this is going away completely once I use classes to represent JSON endpoints. > > > - Instead of defining process variables, consider using a structure or > > EIEIO class to encapsulate all needed state for the process client. > > > Maybe. Structures is a bad idea, but defclass isn't at all. > > I'm not so much opposed to structures, it's more of an implementation > detail and a matter of taste whether you prefer structures or classes. It's most definitly *not* an implementation detail *when using them for an API*, because if you change the implementation of you structure-based library, all the code compiled against it will break. You can think of structures as just an array with fancy accessor names that all expand to aref's. they're only useful when confined to the same compilation unit. iow, speed comes at a price. For a real-life example read the comments in this emacs-lsp issue: https://github.com/emacs-lsp/lsp-mode/pull/289 > However, there's a pretty strong argument that I forgot about: for > robustness you need a way to restart process and even have them > restarted automatically. Let's say a server process crashes or the > user kills it; you definitely don't want LSP/JSON-RPC to be unusable > until the user restarts Emacs. Therefore, instead of having the > 'connect' function create the process, the send and receive functions > should ensure that it's running; i.e. shift the paradigm from > "edge-triggered" (process is created once the user calls a function) > to "level-triggered" (all functions that need a process make sure the > process is available). That is at the same time more robust and easier > to understand. Auto-restarting on send isn't a spectacular idea . You should leave it to the client to do this imo. eglot.el, the only existing jsonrpc.el client today, does something that is similar and that you may think is sufficient: when the process-based server crashes, jsonrpc.el catches it in the sentinel and calls an eglot.el hook that reconnects automatically. Obviously, this only happens if the previous session lasted more than x seconds, to prevent infinite reconnections. If this doesn't suit you, in a hypothetical stephani.el client, you should be able to do the "ensure-connected" dance without much effort using the same jsonrpc.el API. > > - Please make it possible to run servers remotely. Unfortunately this > > isn't possible with `make-process', so you have to use > > `start-file-process' instead if `default-directory' is remote. As an > > alternative to using `default-directory', consider passing a > > REMOTE-DIRECTORY argument to `jrpc-connect' so that the behavior > > doesn't depend on ambient context. > > Maybe. I don't fully understand the example. But jsonrpc-connect allows you to > pass any pre-connected process object. It will replace its buffer, > sentinel and filter. In the "autostart-server" branch of eglot.el, for > example, there is a function to I start a local process with special > arguments to that it open a server, then connect to it, then return that > connected process. This is for Windows LSP servers that can't to stdio > comms. > > It's good to have such an extensibility mechanism, and I indeed > overlooked that you can pass a process object to the connect function. > I'm wondering whether that should indeed be the *only* option: leave > the process creation business entirely to the client, and only accept > a process object (or rather, process factory function, because you > want to restart processes as necessary, see above). That is, have the > following interface instead of the 'connect': > > (defun jsonrpc-make-client (process-factory) "PROCESS-FACTORY gets > called an unspecified number of times and has to return a new live > process object each time it's called." ...) Factories are so 1995 :-) But think I understand what you want more or less. In master eglot.el, I'm already using classes. If you don't want the stdio/tcp process-based defaults (or if you want them with some added twist), you can pass a class symbol to the connect function and jsonrpc.el will makes the object with nothing but fundamental JSONRPC stuff (hidden in the base class). There's your factory, I guess. Then generic functions are called whenever behaviour can be customized. Figuring out what these generic functions and where the default methods lie will be the meat of desining the API. > Optionally you could provide convenience functions to return such > factories for `make-process' etc., but you could also leave that to > the users entirely. Yeah default connection/endpoint class should probably give you a process similar to what is used for talking LSP with clients. > > - I think many of the names can be internal (i.e. contain a double > > dash) unless they are explicitly part of the public API. > > Maybe. What names? The names that I want to make public are already > public, and all the others aren't. > > I'm thinking about things like jsonrpc-warn, > jsonrpc-outstanding-request-ids, etc. jsonrpc-outstanding-request-ids is an unused leftover that should be deleted. jsonrpc-warn is supposed is a convenience function for leting the client signal warnings specific to jsonrpc. I see no wrong in making it public, but I guess you can argue that clients shoudn't introduce in the jsonrpc log at all. Any more problems? > Maybe. Hmm, I really really don't know about this. Have you tried > cl-destructuring-bind with plists? It really is the SH&T for JSON. > > Not really, how would it work? Typically I use plain gethash, assq, > etc. Or `let-alist'. See my answers in the other thread where I go into length about this. In short, cl-destructuring-bind gives you destructuring, validation, setting defaults, and consistency with generic function arguments lists. > > and json.c doesn't support plists. > > That's the superior argument. I didn't know about json.c until very > recently. I'll play along, but can you help me rewrite jsonrpc-lambda to > something less consing perhaps? > > (cl-defmacro jsonrpc-lambda (cl-lambda-list &body body) > (declare (indent 1) (debug (sexp &rest form))) > (let ((jsonobj (gensym "jsonrpc-lambda-elem"))) > `(lambda (,jsonobj) > (apply (cl-function (lambda ,cl-lambda-list ,@body)) > (let (plist) > (maphash (lambda (k v) > (push v plist) > (push (if (keywordp k) k > (intern (format ":%s" k))) > plist)) > ,jsonobj) > plist))))) > > Why do we need jsonrpc-lambda? Writing a normal lambda and some > gethash or assq calls doesn't sound overly bad to me. I'd just remove > jsonrpc-lambda entirely; it's the kind of syntactic sugar that I think > doesn't make code significantly more readable. Here's an example: Suppose you're implementing a spec that requires you to iterate an array of JSON objects that - *must* have the key :foo; - *may* have the key :bar, defaulting to "baz"; - *must not* have any other key. I do this with: (mapc (jsonrpc-lambda (&key (foo nil foo-provided-p) (bar "baz")) (unless foo-provided-p (error "Gotta have foo!")) (frobnicate foo bar)) json-objects) Can you come up with something more concise in elisp? Come to think of it, what about in any other language? You could have used a cl-defmethod/cl-defun instead of the jsonrpc-lambda anonymous function. > I understand it's obscure for a reader, so let me explain and then this > might go into the doc. Obviously, this came out of necessity in > eglot.el, but I think its good functionality to keep in jsonrpc.el > refactor (and it'd be really hard to implement outside it.) > > If it should stay in jsonrpc.el, then it should be clear without > referring to eglot.el. It's not a problem specific to eglot.el, but to LSP in general and other applications that process events asynchronously but need order in their communications. > Hmm. I need to think a bit more about this. I guess it makes sense, > but please make it a property of the client, not a global variable for > now. It's not exactly a "global variable", but a hook variable. And it's going to be a generic function with a default "send immediately" behaviour. It can't easily be implemented entirely in the client, since it requires that the client cannot tell if the request or notificaiton sent really went through immediately. Let me try to reword generally: a jsonrpc-client in an order-sensitive JSONRPC-based protocol (such as LSP) *can* indicate to jsonrpc.el that it *may* defer certain requests to the future. The decision is not jsonrpc's to make: the client must customize an API point (used to be the hook, now is a cl-method) that the jsonrpc.el server probes from time to time. This enables the client to write code as if it weren't order-sensitive at all. João ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [ELPA] New package: eglot 2018-05-10 22:34 [ELPA] New package: eglot João Távora 2018-05-11 6:19 ` Eli Zaretskii @ 2018-05-12 15:47 ` Stefan Monnier 2018-05-14 10:55 ` Bozhidar Batsov 2018-05-14 14:14 ` João Távora 1 sibling, 2 replies; 38+ messages in thread From: Stefan Monnier @ 2018-05-12 15:47 UTC (permalink / raw) To: emacs-devel > If the package is accepted, I think I already have commit rights to the > GNU ELPA repo and would like to develop this as a Git subtree, just like > yasnippet. Nowadays I recommend ":external"s to ":subtree"s, but it's your call. I'd welcome this package into GNU ELPA, so feel free to push it there. Stefan ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [ELPA] New package: eglot 2018-05-12 15:47 ` [ELPA] New package: eglot Stefan Monnier @ 2018-05-14 10:55 ` Bozhidar Batsov 2018-05-14 14:14 ` João Távora 1 sibling, 0 replies; 38+ messages in thread From: Bozhidar Batsov @ 2018-05-14 10:55 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 619 bytes --] I think that until a package matures it's better for it to live in ELPA, so it can evolve at its own pace. I agree that eventually Emacs should probably feature some built-in support for LSP, though. On 12 May 2018 at 18:47, Stefan Monnier <monnier@iro.umontreal.ca> wrote: > > If the package is accepted, I think I already have commit rights to the > > GNU ELPA repo and would like to develop this as a Git subtree, just like > > yasnippet. > > Nowadays I recommend ":external"s to ":subtree"s, but it's your call. > I'd welcome this package into GNU ELPA, so feel free to push it there. > > > Stefan > > > [-- Attachment #2: Type: text/html, Size: 1104 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [ELPA] New package: eglot 2018-05-12 15:47 ` [ELPA] New package: eglot Stefan Monnier 2018-05-14 10:55 ` Bozhidar Batsov @ 2018-05-14 14:14 ` João Távora 1 sibling, 0 replies; 38+ messages in thread From: João Távora @ 2018-05-14 14:14 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >> If the package is accepted, I think I already have commit rights to the >> GNU ELPA repo and would like to develop this as a Git subtree, just like >> yasnippet. > > Nowadays I recommend ":external"s to ":subtree"s, but it's your call. > I'd welcome this package into GNU ELPA, so feel free to push it there. I just did, with :external, which does indeed sound simpler. (I did mess up slightly and pushed an "externals/elpa" branch to the repo instead of the intended "externals/eglot". I deleted the wrong branch, so apart from a few extra emails, everything should be fine). My package builds fine, but "make" for some of the others didn't go so well. Anyway, here's hoping to see it built tomorrow, João ^ permalink raw reply [flat|nested] 38+ messages in thread
end of thread, other threads:[~2018-06-28 13:40 UTC | newest] Thread overview: 38+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2018-05-10 22:34 [ELPA] New package: eglot João Távora 2018-05-11 6:19 ` Eli Zaretskii 2018-05-11 11:29 ` João Távora 2018-05-11 12:15 ` Eli Zaretskii 2018-05-18 16:27 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) João Távora 2018-05-19 8:46 ` Eli Zaretskii 2018-05-20 15:54 ` New jrpc.el JSONRPC library João Távora 2018-05-20 16:02 ` Eli Zaretskii 2018-05-29 1:08 ` João Távora 2018-06-28 12:13 ` [PATCH] jsonrpc.el João Távora 2018-06-28 13:18 ` Eli Zaretskii 2018-06-28 13:23 ` João Távora 2018-06-28 13:40 ` Eli Zaretskii 2018-05-20 19:13 ` New jrpc.el JSONRPC library Clément Pit-Claudel 2018-05-20 20:35 ` Josh Elsasser 2018-05-20 23:22 ` João Távora 2018-05-21 3:32 ` Josh Elsasser 2018-05-20 23:11 ` João Távora 2018-05-21 0:14 ` Clément Pit-Claudel 2018-05-19 11:34 ` New jrpc.el JSONRPC library (Was: [ELPA] New package: eglot) Philipp Stephani 2018-05-19 14:11 ` Eli Zaretskii 2018-05-20 15:43 ` New jrpc.el JSONRPC library João Távora 2018-05-20 16:06 ` Eli Zaretskii 2018-05-20 16:18 ` João Távora 2018-05-21 13:30 ` Aaron Ecay 2018-05-21 13:43 ` João Távora 2018-05-21 14:37 ` Aaron Ecay 2018-05-21 19:06 ` João Távora 2018-05-23 17:57 ` Aaron Ecay 2018-05-23 18:02 ` Stefan Monnier 2018-05-23 20:40 ` João Távora 2018-05-24 2:07 ` Stefan Monnier 2018-05-21 14:42 ` Stefan Monnier 2018-05-24 10:02 ` Philipp Stephani 2018-05-24 17:25 ` João Távora 2018-05-12 15:47 ` [ELPA] New package: eglot Stefan Monnier 2018-05-14 10:55 ` Bozhidar Batsov 2018-05-14 14:14 ` João Távora
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.