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

[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



reply via email to

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