[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/drepl eaced7db3b 01/10: Initial commit
|
From: |
ELPA Syncer |
|
Subject: |
[elpa] externals/drepl eaced7db3b 01/10: Initial commit |
|
Date: |
Tue, 7 Nov 2023 03:58:04 -0500 (EST) |
branch: externals/drepl
commit eaced7db3b3e2f74f95fe31901399743513fd7e3
Author: Augusto Stoffel <arstoffel@gmail.com>
Commit: Augusto Stoffel <arstoffel@gmail.com>
Initial commit
---
drepl-ipython.el | 93 ++++++++++++++++++
drepl-ipython.py | 167 ++++++++++++++++++++++++++++++++
drepl-lua.el | 88 +++++++++++++++++
drepl-lua.lua | 100 ++++++++++++++++++++
drepl.el | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 731 insertions(+)
diff --git a/drepl-ipython.el b/drepl-ipython.el
new file mode 100644
index 0000000000..ea84083ccb
--- /dev/null
+++ b/drepl-ipython.el
@@ -0,0 +1,93 @@
+;;; drepl.el --- REPL protocol for the dumb terminal -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2023 Augusto Stoffel
+
+;; Author: Augusto Stoffel <arstoffel@gmail.com>
+;; Keywords: languages, processes
+
+;; 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:
+
+;;
+
+;;; Code:
+
+;;; Customization options
+(require 'comint-mime)
+(require 'drepl)
+(require 'python)
+
+(defgroup drepl-ipython nil
+ "IPython shell implemented via dREPL"
+ :group 'drepl
+ :group 'python
+ :link '(url-link "https://github.com/astoff/drepl"))
+
+(defcustom drepl-ipython-buffer-name "*IPython*"
+ "Name of IPython shell buffer."
+ :type 'string)
+
+(defvar drepl-ipython--start-file
+ (expand-file-name "drepl-ipython.py"
+ (if load-file-name
+ (file-name-directory load-file-name)
+ default-directory))
+ "File name of the startup script.")
+
+(cl-defstruct (drepl-ipython (:include drepl)))
+
+(cl-defmethod drepl--restart :around ((repl drepl-ipython))
+ (if (eq (drepl-status repl) 'ready)
+ (with-current-buffer (drepl-buffer repl)
+ (save-excursion)
+ (goto-char (process-mark (drepl-process repl)))
+ (insert-before-markers "%reset -f")
+ (drepl--eval repl "%reset -f"))
+ (cl-call-next-method)
+ (drepl-ipython)))
+
+(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))
+
+;;;###autoload
+(defun drepl-ipython ()
+ "Run the IPython interpreter in an inferior process."
+ (interactive)
+ (let* ((buffer (get-buffer-create drepl-ipython-buffer-name)))
+ (unless (comint-check-proc buffer)
+ (make-comint-in-buffer
+ (buffer-name buffer)
+ buffer
+ python-interpreter nil
+ "-c"
+ "import sys; exec(''.join(sys.stdin)); DRepl.instance().mainloop()")
+ (with-current-buffer buffer
+ (drepl-ipython-mode)
+ (setq-local drepl--current (make-drepl-ipython :buffer buffer))
+ (with-temp-buffer
+ (insert-file-contents drepl-ipython--start-file)
+ (process-send-string buffer (buffer-string))
+ (process-send-eof buffer))))
+ (pop-to-buffer buffer display-comint-buffer-action)))
+
+(provide 'drepl-ipython)
+
+;;; drepl-ipython.el ends here
diff --git a/drepl-ipython.py b/drepl-ipython.py
new file mode 100644
index 0000000000..9450f1e3a8
--- /dev/null
+++ b/drepl-ipython.py
@@ -0,0 +1,167 @@
+"""IPython interface for dREPL."""
+
+import base64
+import json
+import sys
+
+from IPython.core.completer import provisionalcompleter
+from IPython.core.displayhook import DisplayHook
+from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
+from IPython.utils.tokenutil import token_at_cursor
+
+
+def encoding_workaround(data):
+ if isinstance(data, str):
+ return base64.decodebytes(data.encode())
+ return data
+
+
+MIME_TYPES = {
+ "image/png": encoding_workaround,
+ "image/jpeg": encoding_workaround,
+ "text/latex": str.encode,
+ "text/html": str.encode,
+ "application/json": lambda d: json.dumps(d).encode(),
+}
+
+
+def reply(**data):
+ print(f"\033]5161;{json.dumps(data)}\033\\", end="")
+
+
+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:
+ print(outprompt, end="")
+
+ def write_format_data(self, format_dict, md_dict=None) -> None:
+ for mime, handler in self.shell.mime_renderers.items():
+ if mime in format_dict:
+ handler(format_dict[mime], None)
+ return
+ super().write_format_data(format_dict, md_dict)
+
+
+@InteractiveShellABC.register
+class DRepl(InteractiveShell):
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ self.current_ps1 = None
+ self.keep_running = True
+ self.confirm_exit = True
+ try:
+ self.enable_matplotlib("inline")
+ except ModuleNotFoundError:
+ pass
+ self.display_formatter.active_types = list(MIME_TYPES.keys())
+ self.mime_size_limit = 4000
+ self.mime_renderers = {
+ t: self.make_mime_renderer(t, MIME_TYPES[t]) for t in MIME_TYPES
+ }
+ self.enable_mime_rendering()
+ # TODO: disable history
+ print(self.banner)
+
+ system = InteractiveShell.system_raw
+ displayhook_class = DReplDisplayHook
+
+ def make_mime_renderer(self, type, encoder):
+ def renderer(data, meta=None):
+ if encoder:
+ 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)
+ payload = "tmp" + Path(fname).as_uri()
+ else:
+ payload = base64.encodebytes(data).decode()
+ print(f"\033]5151;{header}\n{payload}\033\\")
+
+ return renderer
+
+ def enable_mime_rendering(self, mime_types=None):
+ """Enable rendering of the given mime types; if None, enable all."""
+ if mime_types is None:
+ mime_types = MIME_TYPES
+ for t in mime_types:
+ if t in MIME_TYPES:
+ self.display_formatter.formatters[t].enabled = True
+
+ def ask_exit(self):
+ self.keep_running = False
+
+ def enable_gui(self, gui=None):
+ if gui != "inline":
+ print("Can't enable this GUI: {}".format(gui))
+
+ def mainloop(self):
+ while self.keep_running:
+ try:
+ if self.current_ps1 is not None:
+ print(self.separate_in, end="")
+ self.current_ps1 = sys.ps1.format(self.execution_count)
+ reply(op="status", status="ready")
+ line = input(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 == "eval":
+ break # Allow execution count to increment.
+ reply(op="status", status="ready")
+ line = input()
+ else:
+ print("Invalid input")
+ except KeyboardInterrupt as e:
+ print(type(e).__name__)
+ 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()
+
+ 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}
+ for c in self.Completer.completions(code, offset)
+ ]
+ reply(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).ljust(len(self.current_ps1))
+ reply(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)
+ text = self.object_inspect_text(name)
+ reply(
+ id=id,
+ name=info["name"],
+ type=info["type_name"],
+ file=info["file"],
+ text=text,
+ )
+ except Exception:
+ reply(id=id)
diff --git a/drepl-lua.el b/drepl-lua.el
new file mode 100644
index 0000000000..acfbf8ee68
--- /dev/null
+++ b/drepl-lua.el
@@ -0,0 +1,88 @@
+;;; drepl.el --- REPL protocol for the dumb terminal -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2023 Augusto Stoffel
+
+;; Author: Augusto Stoffel <arstoffel@gmail.com>
+;; Keywords: languages, processes
+
+;; 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:
+
+;;
+
+;;; Code:
+
+;;; Customization options
+(require 'drepl)
+(require 'lua-mode)
+
+(defgroup drepl-lua nil
+ "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
+ (file-name-directory load-file-name)
+ default-directory))
+ "File name of the startup script.")
+
+(cl-defstruct (drepl-lua (:include drepl)))
+
+(cl-defmethod drepl--restart :after ((_repl drepl-lua))
+ (drepl-lua))
+
+(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))
+
+;;; User commands
+
+;;;###autoload
+(defun drepl-lua ()
+ "Run the Lua interpreter in an inferior process."
+ (interactive)
+ (cl-letf* ((buffer (get-buffer-create drepl-lua-buffer-name))
+ ((default-value 'process-environment) process-environment)
+ ((default-value 'exec-path) exec-path))
+ (unless (comint-check-proc buffer)
+ (make-comint-in-buffer
+ (buffer-name buffer)
+ buffer
+ "lua" nil "-v" "-e" "loadfile()():main()")
+ (with-current-buffer buffer
+ (drepl-lua-mode)
+ (setq-local process-environment process-environment) ;FIXME
+ (setq-local exec-path exec-path)
+ (setq-local drepl--current (make-drepl-lua :buffer buffer))
+ (with-temp-buffer
+ (insert-file-contents drepl-lua--start-file)
+ (process-send-string buffer (buffer-string))
+ (process-send-eof buffer))))
+ (pop-to-buffer buffer display-comint-buffer-action)))
+
+(provide 'drepl-lua)
+
+;;; drepl-lua.el ends here
diff --git a/drepl-lua.lua b/drepl-lua.lua
new file mode 100644
index 0000000000..51bcdb461f
--- /dev/null
+++ b/drepl-lua.lua
@@ -0,0 +1,100 @@
+local json = require 'dkjson'
+local repl = require 'repl'
+local stdout = io.stdout
+local stdin = io.stdin
+local print = print
+local format = string.format
+
+local drepl = repl:clone()
+drepl.keep_running = true
+drepl:loadplugin 'autoreturn'
+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)
+end
+
+local function reply(data)
+ stdout:write(format("\x1b]5161;%s\x1b\\", json.encode(data)))
+ stdout:flush()
+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
+ 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))
+ end
+ method(self, message)
+end
+
+function drepl:drepl_eval(args)
+ self._buffer = ""
+ local v = self:handleline(args.code)
+ if v == 2 then error("Incomplete input!") end
+ self:prompt(1)
+end
+
+function drepl:drepl_checkinput(args)
+ local _, err = self:compilechunk(args.code)
+ local cont = err and self:detectcontinue(err)
+ -- 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{
+ id=args.id,
+ status=not err and "complete" or cont and "incomplete" or "invalid",
+ prompt=err and self:getprompt(2) .. " " or nil,
+ indent=""
+ }
+end
+
+function drepl:drepl_complete(args)
+ local prefix = args.code:sub(1, args.offset)
+ local cands = {}
+ self:complete(
+ prefix,
+ function(line)
+ local suffix = line:sub(-1, -1)
+ local type = (suffix == "." and "table") or (suffix=="(" and "function")
or nil
+ local _, _, cand = line:find("([%a%d_]+)[.(]?$")
+ cands[#cands+1] = {text=cand, type=type}
+ end
+ )
+ reply{
+ id=args.id,
+ candidates=cands
+ }
+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
+ end
+end
+
+return drepl
diff --git a/drepl.el b/drepl.el
new file mode 100644
index 0000000000..07aad97cfe
--- /dev/null
+++ b/drepl.el
@@ -0,0 +1,283 @@
+;;; drepl.el --- REPL protocol for the dumb terminal -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2023 Augusto Stoffel
+
+;; Author: Augusto Stoffel <arstoffel@gmail.com>
+;; Keywords: languages, processes
+
+;; 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:
+
+;;
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'comint)
+(eval-when-compile (require 'subr-x))
+
+;;; Variables and customization options
+
+(defgroup drepl nil
+ "REPL protocol for the dumb terminal."
+ :group 'comint
+ :link '(url-link "https://github.com/astoff/drepl"))
+
+(defface drepl-prompt-incomplete '((t :inherit comint-highlight-prompt))
+ "Face for continuation prompts when current input is valid.")
+
+(defface drepl-prompt-invalid '((t :inherit error))
+ "Face for continuation prompts when current input is invalid.")
+
+(defvar-local drepl--current nil
+ "dREPL associated to the current buffer.")
+
+(defvar drepl--verbose t)
+
+;;; Basic definitions
+
+(cl-defstruct drepl
+ "Base dREPL object."
+ buffer
+ status
+ (last-request-id 0)
+ requests
+ pending)
+
+(cl-defgeneric drepl-process (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)))
+
+;;; Communication protocol
+
+(defalias 'drepl--json-decode
+ (if (json-available-p)
+ (lambda (s)
+ (json-parse-string s :object-type 'alist))
+ (error "Not implemented")))
+
+(defalias 'drepl--json-encode
+ (if (json-available-p)
+ (lambda (s) (json-serialize s :null-object nil))
+ (error "Not implemented")))
+
+(cl-defgeneric drepl--send-request (repl data)
+ (drepl--message "OUT: %s" (json-serialize data))
+ (setf (drepl-status repl) 'busy)
+ (process-send-string (drepl-process repl)
+ (format "\e%%%s\n" (json-serialize data))))
+
+(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)
+ (cb (lambda (data) (setq result data))))
+ (apply #'drepl--communicate repl cb op args)
+ (while (eq result :none) (accept-process-output))
+ result))
+ (let* ((id (cl-incf (drepl-last-request-id repl)))
+ (data `(:id ,id :op ,(symbol-name op) ,@args)))
+ (push (cons id callback) (if-let ((reqs (drepl-requests repl)))
+ (cdr reqs)
+ (drepl-requests repl)))
+ (if (eq 'ready (drepl-status repl))
+ (drepl--send-request repl data)
+ (push (cons id data) (drepl-pending repl)))
+ id)))
+
+(cl-defgeneric drepl--handle-notification (repl data)
+ (pcase (alist-get 'op data)
+ ("status" (setf (drepl-status repl)
+ (intern (alist-get 'status data))))
+ ("log" (drepl--message "dREPL buffer %s: %s"
+ (buffer-name)
+ (alist-get 'text data)))))
+
+(defun drepl--osc-handler (_cmd text)
+ (drepl--message " IN: %s" text)
+ (let* ((data (drepl--json-decode text))
+ (id (alist-get 'id data))
+ (callback (if id
+ (prog1
+ (alist-get id (drepl-requests drepl--current))
+ (setf (alist-get id (drepl-requests drepl--current)
+ nil 'remove)
+ nil))
+ (apply-partially #'drepl--handle-notification
+ drepl--current))))
+ (when-let ((nextreq (and (eq (drepl-status drepl--current) 'ready)
+ (pop (drepl-pending drepl--current)))))
+ (drepl--send-request drepl--current nextreq))
+ (when callback
+ (funcall callback data))))
+
+;;; Complete operation
+
+(defun drepl--capf-annotate (cand)
+ "Return an annotation for completion candidate CAND."
+ (get-text-property 0 'drepl--annot cand))
+
+(cl-defgeneric drepl--completion-bounds (_repl)
+ (bounds-of-thing-at-point 'symbol))
+
+(cl-defgeneric drepl--completion-cadidates (repl code offset)
+ (let ((response (while-no-input
+ (drepl--communicate repl 'sync 'complete
+ :code code
+ :offset offset))))
+ (mapcar (lambda (c)
+ (let-alist c
+ (propertize .text 'drepl--annot .annotation)))
+ (alist-get 'candidates response))))
+
+(defun drepl--complete ()
+ (when-let ((repl (when (derived-mode-p 'drepl-mode)
+ (drepl--get-repl 'ready)))
+ (bounds (drepl--completion-bounds repl))
+ (code (buffer-substring-no-properties
+ (cdr comint-last-prompt)
+ (point-max)))
+ (offset (- (point) (cdr comint-last-prompt)))
+ (cands (when (>= offset 0)
+ (drepl--completion-cadidates repl code offset)))
+ (metadata '(metadata
+ (category . drepl)
+ (annotation-function . drepl--capf-annotate)))
+ (coll (lambda (string predicate action)
+ (if (eq action 'metadata)
+ metadata
+ (complete-with-action action cands string predicate)))))
+ `(,(car bounds) ,(cdr bounds) ,coll)))
+
+;;; Eval operation
+
+(cl-defgeneric drepl--eval (repl code)
+ (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'.
+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)
+ (comint-simple-send proc string))))
+
+(defun drepl-eval (code)
+ (interactive (list (read-from-minibuffer "Evaluate: ")))
+ (drepl--eval (drepl--get-repl) code))
+
+(defun drepl-send-input-maybe (&optional force) ;Change this to `newline',
with opposite logic
+ "Like `comint-send-input', but first check if input is complete.
+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."))
+ (let-alist (when-let ((repl (unless force (drepl--get-repl 'ready)))
+ (pmark (process-mark (drepl-process repl)))
+ (code (and (>= (point) pmark)
+ (buffer-substring-no-properties
+ pmark (field-end)))))
+ (drepl--communicate drepl--current 'sync 'checkinput
+ :code code))
+ (pcase-exhaustive .status
+ ((or (and "incomplete" (let face 'drepl-prompt-incomplete))
+ (and "invalid" (let face 'drepl-prompt-invalid)))
+ (let* ((prompt (thread-first
+ .prompt
+ (or "")
+ (propertize 'font-lock-face face))))
+ (insert (propertize "\n" 'display (concat " \n" prompt))
+ .indent)))
+ ((or "complete" 'nil)
+ (comint-send-input)))))
+
+;;; Describe operation
+
+(cl-defgeneric drepl--describe (repl callback)
+ (when-let ((offset (- (point) (cdr comint-last-prompt)))
+ (code (when (>= offset 0)
+ (buffer-substring-no-properties
+ (cdr comint-last-prompt)
+ (point-max)))))
+ (drepl--communicate repl callback 'describe :code code :offset offset)))
+
+(defun drepl--make-help-buffer (data &optional interactive)
+ (let-alist data
+ (help-setup-xref (list #'drepl--make-help-buffer data) interactive)
+ (with-help-window (help-buffer)
+ (with-current-buffer standard-output
+ (when (stringp .name)
+ (insert .name)
+ (when (stringp .type) (insert " is a " .type))
+ (when (stringp .file) (insert " defined in " (buttonize .file
#'find-file .file)))
+ (insert ".\n\n"))
+ (when (stringp .text)
+ (insert (ansi-color-apply .text)))))))
+
+(defun drepl-describe-thing-at-point ()
+ (interactive)
+ (when-let ((repl (when (derived-mode-p 'drepl-mode)
+ (drepl--get-repl 'ready))))
+ (drepl--describe repl #'drepl--make-help-buffer)))
+
+;;; Initialization and restart
+
+(cl-defgeneric drepl--restart (drepl)
+ "Restart the REPL."
+ (with-current-buffer (drepl-buffer drepl)
+ (kill-process (drepl-process drepl--current))
+ (while (accept-process-output (drepl-process drepl--current)))))
+
+(defun drepl-restart ()
+ (interactive)
+ (when-let ((repl (drepl--get-repl)))
+ (drepl--restart repl)))
+
+;;; Base major mode
+
+(defvar-keymap drepl-mode-map
+ :doc "Keymap for `drepl-mode'."
+ :parent comint-mode-map
+ "<remap> <comint-send-input>" #'drepl-send-input-maybe
+ "<remap> <display-local-help>" #'drepl-describe-thing-at-point
+ "C-c C-n" #'drepl-restart)
+
+(define-derived-mode drepl-mode comint-mode "dREPL"
+ "Major mode for the dREPL buffers."
+ :interactive nil
+ (push '("5161" . drepl--osc-handler) ansi-osc-handlers)
+ (setq-local comint-input-sender #'drepl--send-string)
+ (setq-local indent-line-function #'comint-indent-input-line-default)
+ (add-hook 'completion-at-point-functions 'drepl--complete nil t))
+
+(provide '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 <=
- [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, 2023/11/07
- [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