[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/drepl 033ada13c0 2/2: Split long messages when sent thr
From: |
ELPA Syncer |
Subject: |
[elpa] externals/drepl 033ada13c0 2/2: Split long messages when sent through PTY |
Date: |
Thu, 23 Nov 2023 12:57:48 -0500 (EST) |
branch: externals/drepl
commit 033ada13c0377f2c13853368c1c16efbb83ac6cf
Author: Augusto Stoffel <arstoffel@gmail.com>
Commit: Augusto Stoffel <arstoffel@gmail.com>
Split long messages when sent through PTY
---
README.org | 6 +++++-
drepl-ipython.py | 46 ++++++++++++++++++++++++++--------------------
drepl-lua.lua | 50 +++++++++++++++++++++++++++-----------------------
drepl.el | 15 +++++++++++----
4 files changed, 69 insertions(+), 48 deletions(-)
diff --git a/README.org b/README.org
index 4ad6a69739..ce8e6acf04 100644
--- a/README.org
+++ b/README.org
@@ -60,7 +60,11 @@ how to multiplex control messages and regular IO.
- From the subprocess to Emacs, control messages travel in JSON
objects inside an OSC escape sequence (code 5161).
- From Emacs to the subprocess, control messages are passed as lines
- of the form =ESC % <JSON object> LF=.
+ of the form =ESC = <JSON object> LF=. If the subprocess
+ communicates over a PTY and the encoded message is too long to fit a
+ line (this is an OS-dependent limit), then the message payload is
+ split into fragments. All fragments except the last are transmitted
+ as =ESC + <JSON fragment> LF=.
At any given point in time, the subprocess expects either a framed
messages like this or regular IO. Emacs keeps track of the state of
diff --git a/drepl-ipython.py b/drepl-ipython.py
index 038880fa71..c5b3e52643 100644
--- a/drepl-ipython.py
+++ b/drepl-ipython.py
@@ -27,10 +27,22 @@ MIME_TYPES = {
}
-def reply(**data):
+def sendmsg(**data):
print(f"\033]5161;{json.dumps(data)}\033\\", end="")
+def readmsg():
+ sendmsg(op="status", status="ready")
+ buffer = []
+ while True:
+ line = input()
+ buffer.append(line[2:])
+ if line.startswith("\033="):
+ return json.loads("".join(buffer))
+ if not line.startswith("\033+"):
+ raise DreplError("Invalid input")
+
+
class DreplError(Exception):
pass
@@ -107,9 +119,9 @@ class Drepl(InteractiveShell):
def mainloop(self):
while self.keep_running:
try:
- self.run_repl()
+ self.run_once()
except EOFError:
- reply(op="status", status="busy")
+ sendmsg(op="status", status="busy")
if (not self.confirm_exit) or self.ask_yes_no(
"Do you really want to exit ([y]/n)?", "y", "n"
):
@@ -117,20 +129,17 @@ class Drepl(InteractiveShell):
except (DreplError, KeyboardInterrupt) as e:
print(str(e) or e.__class__.__name__)
- def run_repl(self):
+ def run_once(self):
"Print prompt, run REPL until a new prompt is needed."
if self.current_ps1 is None:
- reply(op="getoptions")
+ sendmsg(op="getoptions")
self.current_ps1, separate_in = "", ""
else:
- reply(op="status", status="ready")
separate_in = self.separate_in if self.current_ps1 else ""
self.current_ps1 = sys.ps1.format(self.execution_count)
- line = input(separate_in + self.current_ps1)
+ print(separate_in + self.current_ps1, end="")
while True:
- if not line.startswith("\033%"):
- raise DreplError("Invalid input")
- data = json.loads(line[2:])
+ data = readmsg()
op = data.pop("op")
fun = getattr(self, "drepl_{}".format(op), None)
if fun is None:
@@ -139,15 +148,12 @@ class Drepl(InteractiveShell):
if op == "eval":
self.execution_count += 1
break
- elif op == "setoptions":
+ if op == "setoptions":
break
- else:
- reply(op="status", status="ready")
- line = input()
def drepl_eval(self, id, code):
r = self.run_cell(code)
- reply(id=id)
+ sendmsg(id=id)
def drepl_complete(self, id, code, offset):
with provisionalcompleter():
@@ -155,19 +161,19 @@ class Drepl(InteractiveShell):
{"text": c.text, "annot": c.signature}
for c in self.Completer.completions(code, offset)
]
- reply(id=id, candidates=r or None)
+ sendmsg(id=id, candidates=r or None)
def drepl_checkinput(self, id, code):
status, indent = self.check_complete(code)
prompt =
sys.ps2.format(self.execution_count).rjust(len(self.current_ps1))
- reply(id=id, status=status, indent=indent, prompt=prompt)
+ sendmsg(id=id, status=status, indent=indent, prompt=prompt)
def drepl_describe(self, id, code, offset):
name = token_at_cursor(code, offset)
try:
info = self.object_inspect(name)
defn = info["definition"]
- reply(
+ sendmsg(
id=id,
name=info["name"],
type=" ".join(defn.split()) if defn else info["type_name"],
@@ -175,9 +181,9 @@ class Drepl(InteractiveShell):
text=self.object_inspect_text(name),
)
except Exception:
- reply(id=id)
+ sendmsg(id=id)
def drepl_setoptions(self, id, prompts=None):
if prompts:
sys.ps1, sys.ps2, sys.ps3, self.separate_in, self.separate_out =
prompts
- reply(id=id)
+ sendmsg(id=id)
diff --git a/drepl-lua.lua b/drepl-lua.lua
index 51bcdb461f..524a5e5c70 100644
--- a/drepl-lua.lua
+++ b/drepl-lua.lua
@@ -1,9 +1,10 @@
local json = require 'dkjson'
local repl = require 'repl'
-local stdout = io.stdout
-local stdin = io.stdin
-local print = print
+
+local concat = table.concat
+local error = error
local format = string.format
+local stdin, stdout = io.stdin, io.stdout
local drepl = repl:clone()
drepl.keep_running = true
@@ -12,36 +13,39 @@ drepl:loadplugin 'completion'
drepl._features.console = true -- Lie about this to make pretty_print happy.
drepl:loadplugin 'pretty_print'
--- function drepl:displayresults(results)
--- if results.n == 0 then return end
--- print(compat.unpack(results, 1, results.n))
--- end
-
function drepl:displayerror(err)
- print(err)
+ stdout:write(err, "\n")
+ stdout:flush()
end
-local function reply(data)
+local function sendmsg(data)
stdout:write(format("\x1b]5161;%s\x1b\\", json.encode(data)))
stdout:flush()
end
+local function readmsg()
+ sendmsg{op="status", status="ready"}
+ local buffer, c = {}, nil
+ while c ~= "=" do
+ local line = stdin:read()
+ if line == nil then return end
+ c = line:match("^\x1b([+=])") or error(format("Unexpected input: %q",
line))
+ buffer[#buffer+1] = line:sub(3)
+ end
+ return json.decode(concat(buffer))
+end
+
function drepl:showprompt(prompt)
stdout:write(prompt .. ' ')
stdout:flush()
end
-function drepl:process_line()
- reply{op="status", status="ready"}
- local line = stdin:read()
- if line == nil then -- Got an EOF signal
+function drepl:process_message()
+ local message = readmsg()
+ if message == nil then -- Got an EOF signal
self.keep_running = false
return
end
- if line:sub(1, 2) ~= "\x1b%" then
- error(format("Unexpected input: %q", line))
- end
- local message = json.decode(line, 3)
local method = self["drepl_" .. message.op]
if not method then
error(format("Unknown op: %s", message.op))
@@ -62,8 +66,8 @@ function drepl:drepl_checkinput(args)
-- FIXME: the following avoids a race condition, but we should
-- prescribe in the protocol which methods switch from ready to
-- other states.
- reply{op="status", status="ready"}
- reply{
+ sendmsg{op="status", status="ready"}
+ sendmsg{
id=args.id,
status=not err and "complete" or cont and "incomplete" or "invalid",
prompt=err and self:getprompt(2) .. " " or nil,
@@ -83,7 +87,7 @@ function drepl:drepl_complete(args)
cands[#cands+1] = {text=cand, type=type}
end
)
- reply{
+ sendmsg{
id=args.id,
candidates=cands
}
@@ -92,8 +96,8 @@ end
function drepl:main()
self:prompt(1)
while self.keep_running do
- local ok, err = pcall(self.process_line, self)
- if not ok then reply{op="log", text=err} end
+ local ok, err = pcall(self.process_message, self)
+ if not ok then sendmsg{op="log", text=err} end
end
end
diff --git a/drepl.el b/drepl.el
index dcddeea0d4..fa07e3fd32 100644
--- a/drepl.el
+++ b/drepl.el
@@ -159,10 +159,17 @@ REPL must be in `ready' state and transitions to `busy'
state.
DATA is a plist containing the request arguments, as well as :op
and :id entries."
(setf (drepl--status repl) 'busy)
- (let ((encoded (drepl--json-encode data)))
- (drepl--log-message "send %s" encoded)
- (process-send-string (drepl--process repl)
- (format "\e%%%s\n" encoded))))
+ (let* ((proc (drepl--process repl))
+ (maxlen (when (process-tty-name proc)
+ (- comint-max-line-length 3))))
+ (named-let recur ((last t)
+ (s (drepl--json-encode data)))
+ (if (and maxlen (< maxlen (string-bytes s)))
+ (let ((i (/ (length s) 2)))
+ (recur nil (substring s 0 i))
+ (recur last (substring s i)))
+ (drepl--log-message "send %s" s)
+ (process-send-string proc (format "\e%s%s\n" (if last "=" "+") s))))))
(defun drepl--communicate (repl callback op &rest args)
"Send a request to REPL.