[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/drepl b79b71d39b 06/10: Refactoring and documentation
From: |
ELPA Syncer |
Subject: |
[elpa] externals/drepl b79b71d39b 06/10: Refactoring and documentation |
Date: |
Tue, 7 Nov 2023 03:58:04 -0500 (EST) |
branch: externals/drepl
commit b79b71d39be7a24798a624aadba6070741fdf3f3
Author: Augusto Stoffel <arstoffel@gmail.com>
Commit: Augusto Stoffel <arstoffel@gmail.com>
Refactoring and documentation
---
drepl-ipython.el | 19 ++++-------
drepl-ipython.py | 74 +++++++++++++++++++++++-------------------
drepl-lua.el | 19 +++--------
drepl.el | 98 +++++++++++++++++++++++++++++++++++++-------------------
4 files changed, 118 insertions(+), 92 deletions(-)
diff --git a/drepl-ipython.el b/drepl-ipython.el
index a825a0827d..7faabcdd52 100644
--- a/drepl-ipython.el
+++ b/drepl-ipython.el
@@ -31,7 +31,7 @@
;;; Customization options
(defgroup drepl-ipython nil
- "IPython shell implemented via dREPL"
+ "IPython shell implemented via dREPL."
:group 'drepl
:group 'python
:link '(url-link "https://github.com/astoff/drepl";))
@@ -56,28 +56,23 @@ substring \"{}\" is replaced by the execution count."
default-directory))
"File name of the startup script.")
-(define-derived-mode drepl-ipython-mode drepl-mode "IPython"
- "Major mode for the IPython shell.
-
-\\<drepl-ipython-mode-map>"
- :syntax-table python-mode-syntax-table
- :interactive nil
- (setq-local comint-indirect-setup-function #'python-mode)
- (push '("5151" . comint-mime-osc-handler) ansi-osc-handlers))
-
(defclass drepl-ipython (drepl-base) nil)
+(put 'drepl-ipython 'drepl--buffer-name "IPython")
;;;###autoload
(defun drepl-run-ipython ()
+ "Start the IPython interpreter."
(interactive)
(drepl--run 'drepl-ipython t))
(cl-defmethod drepl--command ((_ drepl-ipython))
`(,python-interpreter "-c"
- "import sys; exec(''.join(sys.stdin)); DRepl.instance().mainloop()"))
+ "import sys; exec(''.join(sys.stdin)); Drepl.instance().mainloop()"))
(cl-defmethod drepl--init ((_ drepl-ipython))
- (drepl-ipython-mode)
+ (drepl-mode)
+ (setq-local comint-indirect-setup-function #'python-mode)
+ (push '("5151" . comint-mime-osc-handler) ansi-osc-handlers)
(let ((buffer (current-buffer)))
(with-temp-buffer
(insert-file-contents drepl-ipython--start-file)
diff --git a/drepl-ipython.py b/drepl-ipython.py
index cb90028db3..a4895c4c1f 100644
--- a/drepl-ipython.py
+++ b/drepl-ipython.py
@@ -3,6 +3,8 @@
import base64
import json
import sys
+from pathlib import Path
+from tempfile import mkstemp
from IPython.core.completer import provisionalcompleter
from IPython.core.displayhook import DisplayHook
@@ -29,9 +31,12 @@ def reply(**data):
print(f"\033]5161;{json.dumps(data)}\033\\", end="")
-class DReplDisplayHook(DisplayHook):
+class DreplError(Exception):
+ pass
+
+
+class DreplDisplayHook(DisplayHook):
def write_output_prompt(self):
- """Write the output prompt."""
print(self.shell.separate_out, end="")
outprompt = sys.ps3.format(self.shell.execution_count)
if self.do_full_cache:
@@ -46,7 +51,7 @@ class DReplDisplayHook(DisplayHook):
@InteractiveShellABC.register
-class DRepl(InteractiveShell):
+class Drepl(InteractiveShell):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.current_ps1 = None
@@ -66,7 +71,7 @@ class DRepl(InteractiveShell):
print(self.banner)
system = InteractiveShell.system_raw
- displayhook_class = DReplDisplayHook
+ displayhook_class = DreplDisplayHook
def make_mime_renderer(self, type, encoder):
def renderer(data, meta=None):
@@ -74,9 +79,6 @@ class DRepl(InteractiveShell):
data = encoder(data)
header = json.dumps({**(meta or {}), "type": type})
if len(data) > self.mime_size_limit:
- from pathlib import Path
- from tempfile import mkstemp
-
fdesc, fname = mkstemp()
with open(fdesc, "wb") as f:
f.write(data)
@@ -105,46 +107,52 @@ class DRepl(InteractiveShell):
def mainloop(self):
while self.keep_running:
try:
- if self.current_ps1 is None:
- reply(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)
- while line.startswith("\033%"):
- data = json.loads(line[2:])
- op = data.pop("op")
- fun = getattr(self, "drepl_{}".format(op), None)
- if fun is None:
- print("Invalid op: {}".format(op))
- continue
- fun(**data)
- if op in ("eval", "setoptions"):
- break # Allow execution count to increment.
- reply(op="status", status="ready")
- line = input()
- else:
- print("Invalid input")
- except KeyboardInterrupt as e:
- print(e.__class__.__name__)
+ self.run_repl()
except EOFError:
reply(op="status", status="busy")
if (not self.confirm_exit) or self.ask_yes_no(
"Do you really want to exit ([y]/n)?", "y", "n"
):
self.ask_exit()
+ except (DreplError, KeyboardInterrupt) as e:
+ print(str(e) or e.__class__.__name__)
+
+ def run_repl(self):
+ "Print prompt, run REPL until a new prompt is needed."
+ if self.current_ps1 is None:
+ reply(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)
+ while True:
+ if not line.startswith("\033%"):
+ raise DreplError("Invalid input")
+ data = json.loads(line[2:])
+ op = data.pop("op")
+ fun = getattr(self, "drepl_{}".format(op), None)
+ if fun is None:
+ raise DreplError("Invalid op: {}".format(op))
+ fun(**data)
+ if op == "eval":
+ self.execution_count += 1
+ break
+ elif 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, result=repr(r.result))
reply(id=id)
def drepl_complete(self, id, code, offset):
with provisionalcompleter():
r = [
- {"text": c.text, "annotation": c.signature}
+ {"text": c.text, "annot": c.signature}
for c in self.Completer.completions(code, offset)
]
reply(id=id, candidates=r or None)
diff --git a/drepl-lua.el b/drepl-lua.el
index e13e13c755..855db81c1e 100644
--- a/drepl-lua.el
+++ b/drepl-lua.el
@@ -29,15 +29,11 @@
(require 'lua-mode)
(defgroup drepl-lua nil
- "Lua shell implemented via dREPL"
+ "Lua shell implemented via dREPL."
:group 'drepl
:group 'lua
:link '(url-link "https://github.com/astoff/drepl";))
-(defcustom drepl-lua-buffer-name "*Lua*"
- "Name of Lua shell buffer."
- :type 'string)
-
(defvar drepl-lua--start-file
(expand-file-name "drepl-lua.lua"
(if load-file-name
@@ -46,25 +42,20 @@
"File name of the startup script.")
(defclass drepl-lua (drepl-base) nil)
+(put 'drepl-lua 'drepl--buffer-name "Lua")
;;;###autoload
(defun drepl-run-lua ()
+ "Start the Lua interpreter."
(interactive)
(drepl--run 'drepl-lua t))
-(define-derived-mode drepl-lua-mode drepl-mode "Lua"
- "Major mode for the Lua shell.
-
-\\<drepl-lua-mode-map>"
- :syntax-table lua-mode-syntax-table
- :interactive nil
- (setq-local comint-indirect-setup-function #'lua-mode))
-
(cl-defmethod drepl--command ((_ drepl-lua))
'("lua" "-v" "-e" "loadfile()():main()"))
(cl-defmethod drepl--init ((_ drepl-lua))
- (drepl-lua-mode)
+ (drepl-mode)
+ (setq-local comint-indirect-setup-function #'lua-mode)
(let ((buffer (current-buffer)))
(with-temp-buffer
(insert-file-contents drepl-lua--start-file)
diff --git a/drepl.el b/drepl.el
index 9d7a2b464c..6b9b1325eb 100644
--- a/drepl.el
+++ b/drepl.el
@@ -51,9 +51,11 @@ which determines whether to ask or return nil when in doubt."
:type '(choice string function))
(defvar-local drepl--current nil
- "dREPL associated to the current buffer.")
+ "The dREPL associated to the current buffer.
+In dREPL buffers, this is the dREPL object itself. In all other
+buffers, this is the dREPL buffer or nil.")
-(defvar drepl--verbose t)
+(defvar drepl--verbose nil)
;;; Basic definitions
@@ -67,25 +69,28 @@ which determines whether to ask or return nil when in
doubt."
:documentation "Base dREPL class.")
(defun drepl--process (repl)
+ "The underlying process of dREPL object REPL."
(get-buffer-process (drepl--buffer repl)))
-(defun drepl--get-repl (&optional status)
- (let ((repl drepl--current)) ;TODO: choose one interactively, maybe
- (when (or (not status)
- (and (memq (process-status (drepl--process repl)) '(run open))
- (eq status (drepl--status repl))))
- repl)))
-
-(defsubst drepl--message (format-string &rest args)
- (when drepl--verbose
- (apply #'message format-string args)))
+(defmacro drepl--message (fmtstring &rest args)
+ "Write a message to *drepl-log* buffer if `drepl--verbose' is non-nil.
+The message is formed by calling `format' with FMTSTRING and ARGS."
+ (let ((msg (lambda (&rest args)
+ (with-current-buffer (get-buffer-create "*drepl-log*")
+ (goto-char (point-max))
+ (when-let ((w (get-buffer-window)))
+ (set-window-point w (point)))
+ (insert (propertize (format-time-string "[%T] ") 'face 'error)
+ (apply #'format args)
+ ?\n)))))
+ `(when drepl--verbose (funcall ,msg ,fmtstring ,@args))))
;;; Communication protocol
(defalias 'drepl--json-decode
(if (json-available-p)
(lambda (s)
- (json-parse-string s :object-type 'alist))
+ (json-parse-string s :object-type 'alist :null-object nil))
(error "Not implemented")))
(defalias 'drepl--json-encode
@@ -94,19 +99,24 @@ which determines whether to ask or return nil when in
doubt."
(error "Not implemented")))
(cl-defgeneric drepl--send-request (repl data)
- (drepl--message "OUT: %s" (json-serialize data))
+ "Send request data to REPL.
+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)
- (process-send-string (drepl--process repl)
- (format "\e%%%s\n" (json-serialize data))))
+ (let ((encoded (drepl--json-encode data)))
+ (drepl--message "send %s" encoded)
+ (process-send-string (drepl--process repl)
+ (format "\e%%%s\n" encoded))))
(cl-defgeneric drepl--communicate (repl callback op &rest args)
(if (eq callback 'sync)
(progn (unless (eq (drepl--status repl) 'ready)
(user-error "%s is busy" repl))
- (let* ((result :none)
+ (let* ((result :pending)
(cb (lambda (data) (setq result data))))
(apply #'drepl--communicate repl cb op args)
- (while (eq result :none) (accept-process-output))
+ (while (eq result :pending) (accept-process-output))
result))
(let* ((id (cl-incf (oref repl last-request-id)))
(data `(:id ,id :op ,(symbol-name op) ,@args)))
@@ -122,15 +132,15 @@ which determines whether to ask or return nil when in
doubt."
(pcase (alist-get 'op data)
("status" (setf (drepl--status repl)
(intern (alist-get 'status data))))
- ("log" (drepl--message "dREPL buffer %s: %s"
("getoptions"
(setf (drepl--status repl) 'ready)
(drepl--set-options repl data))
+ ("log" (drepl--message "log:%s: %s"
(buffer-name)
(alist-get 'text data)))))
(defun drepl--osc-handler (_cmd text)
- (drepl--message " IN: %s" text)
+ (drepl--message "read %s" text)
(let* ((data (drepl--json-decode text))
(id (alist-get 'id data))
(callback (if id
@@ -163,7 +173,7 @@ which determines whether to ask or return nil when in
doubt."
:offset offset))))
(mapcar (lambda (c)
(let-alist c
- (propertize .text 'drepl--annot .annotation)))
+ (propertize .text 'drepl--annot .annot)))
(alist-get 'candidates response))))
(defun drepl--complete ()
@@ -191,18 +201,19 @@ which determines whether to ask or return nil when in
doubt."
(drepl--communicate repl #'ignore 'eval :code code))
(defun drepl--send-string (proc string)
- "Like `comint-send-string', but checks whether PROC's status is `ready'.
+ "Like `comint-send-string', but check whether PROC's status is `ready'.
If it is, then make an eval request, otherwise just send the raw
STRING to the process."
- (drepl--message "SND: %s" string)
(let ((repl (with-current-buffer
(if proc (process-buffer proc) (current-buffer))
(drepl--get-repl 'ready))))
(if repl
(drepl--eval repl string)
+ (drepl--message "send %s" string)
(comint-simple-send proc string))))
(defun drepl-eval (code)
+ "Evaluate CODE string in the current buffer's REPL."
(interactive (list (read-from-minibuffer "Evaluate: ")))
(drepl--eval (drepl--get-repl) code))
@@ -212,7 +223,7 @@ If the input is incomplete or invalid code and FORCE is nil,
insert start a continuation line instead."
(interactive "P")
(unless (derived-mode-p 'drepl-mode)
- (user-error "Can't run this command here."))
+ (user-error "Can't run this command here"))
(let-alist (when-let ((repl (unless force (drepl--get-repl 'ready)))
(pmark (process-mark (drepl--process repl)))
(code (and (>= (point) pmark)
@@ -256,6 +267,7 @@ insert start a continuation line instead."
(insert (ansi-color-apply .text)))))))
(defun drepl-describe-thing-at-point ()
+ "Pop up help on the thing at point."
(interactive)
(when-let ((repl (when (derived-mode-p 'drepl-mode)
(drepl--get-repl 'ready))))
@@ -271,16 +283,17 @@ otherwise fall back to `default-directory'."
(project-root proj)
default-directory))
-(defun drepl--buffer-name (class dir)
+(defun drepl--buffer-name (class directory)
+ "Buffer name for a REPL of the given CLASS running in DIRECTORY."
(format "%s/*%s*"
(file-name-nondirectory
- (directory-file-name dir))
- class))
+ (directory-file-name directory))
+ (or (get class 'drepl--buffer-name) class)))
(defun drepl--get-buffer-create (class ask)
"Get or create a dREPL buffer of the given CLASS.
The directory of the buffer is determined by
-`drepl-directory'. If ASK is non-nil, allow an interactive query
+`drepl-directory'. If ASK is non-nil, allow an interactive query
if needed."
(if (eq (type-of drepl--current) class)
(drepl--buffer drepl--current)
@@ -290,29 +303,46 @@ if needed."
(get-buffer-create (drepl--buffer-name class default-directory)))))
(cl-defgeneric drepl--command (repl)
+ "The command to start the REPL interpreter as a list of strings."
(ignore repl)
(error "This needs an implementation"))
(cl-defgeneric drepl--init (repl)
+ "Initialization code for REPL.
+This function is called in the REPL buffer after a Comint has
+been started in it. It should call `drepl-mode' or a derived
+mode and perform all other desired initialization procedures."
(ignore repl)
(error "This needs an implementation"))
(cl-defgeneric drepl--set-options (repl data)
+ "Implementation of the `setoptions' operation.
+This method is called when the REPL sends a `getoptions'
+notification. The REPL is in `ready' state when this happens.
+The notification message is passed as DATA."
(ignore repl data)
(error "This needs an implementation"))
-(cl-defgeneric drepl--restart (repl _hard)
- "Restart the REPL."
+(cl-defgeneric drepl--restart (repl hard)
+ "Generic method to restart a REPL.
+HARD should be as described in `drepl-restart', but it not used
+in the default implementation."
+ (ignore hard)
(with-current-buffer (drepl--buffer repl)
(kill-process (drepl--process repl))
(while (accept-process-output (drepl--process repl)))
(drepl--run (type-of repl) nil)))
(defun drepl-restart (&optional hard)
+ "Restart the current REPL.
+Some REPLs by default perform a soft reset by deleting all user
+variables without killing the interpreter. In those cases, a
+prefix argument or non-nil HARD argument can be used to force a
+hard reset."
(interactive "P")
- (if-let ((repl (drepl--get-repl)))
- (drepl--restart repl hard)
- (user-error "No REPL to restart")))
+ (drepl--restart (or (drepl--get-repl)
+ (user-error "No REPL"))
+ hard))
(defun drepl--run (class may-prompt)
(let ((buffer (drepl--get-buffer-create class may-prompt)))
@@ -322,6 +352,7 @@ if needed."
(repl (make-instance class :buffer buffer))
(command (drepl--command repl)))
(with-current-buffer buffer
+ (drepl--message "starting %s" buffer)
(apply #'make-comint-in-buffer
(buffer-name buffer) buffer
(car command) nil
@@ -351,4 +382,5 @@ if needed."
(provide 'drepl)
+; LocalWords: dREPL
;;; drepl.el ends here
- [elpa] branch externals/drepl created (now aeba86f820), ELPA Syncer, 2023/11/07
- [elpa] externals/drepl 5650fef65d 08/10: Buffer association logic, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl eaced7db3b 01/10: Initial commit, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl 936bbe1c27 03/10: Add OSC to comint-output-filter-functions, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl f0e87367b3 02/10: Make prompt faces inherit from default, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl b30295f6f9 04/10: Use defclass instead of defstruct, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl b6d3bd9183 05/10: Add setoptions method and getoptions notification, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl b79b71d39b 06/10: Refactoring and documentation,
ELPA Syncer <=
- [elpa] externals/drepl 8f96ca981a 07/10: Add commands to evaluate a region and a buffer, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl 61d907e463 09/10: Use Eldoc to display documentation, ELPA Syncer, 2023/11/07
- [elpa] externals/drepl aeba86f820 10/10: Add readme and commentary, ELPA Syncer, 2023/11/07