emacs-elpa-diffs
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]