mirror of
https://github.com/doomemacs/doomemacs.git
synced 2026-05-11 14:27:15 -07:00
refactor(eval): rewrite module
This commit is contained in:
parent
f8c3decb0a
commit
04ef44d9c8
5 changed files with 302 additions and 259 deletions
|
|
@ -23,7 +23,7 @@
|
|||
(cond ((and (string-prefix-p "jupyter-" lang)
|
||||
(require 'jupyter nil t))
|
||||
(jupyter-eval-region beg end))
|
||||
((+eval-region-as-major-mode beg end (org-src-get-lang-mode lang))))))))
|
||||
((+eval-with-mode-handler-fn beg end nil (org-src-get-lang-mode lang))))))))
|
||||
|
||||
|
||||
;;;###autoload
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
;;; tools/eval/autoload/eval.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar quickrun-option-cmdkey)
|
||||
(defvar eros-overlays-use-font-lock)
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-display-results-in-popup (output &optional _source-buffer)
|
||||
"Display OUTPUT in a popup buffer."
|
||||
(let ((output-buffer (get-buffer-create "*doom eval*"))
|
||||
(origin (selected-window)))
|
||||
"Display OUTPUT in a popup buffer at the bottom of the screen."
|
||||
(let ((output-buffer (get-buffer-create "*doom eval*")))
|
||||
(with-current-buffer output-buffer
|
||||
(setq-local scroll-margin 0)
|
||||
(erase-buffer)
|
||||
(insert output)
|
||||
(goto-char (point-min))
|
||||
(save-excursion (insert output))
|
||||
(if (fboundp '+word-wrap-mode)
|
||||
(+word-wrap-mode +1)
|
||||
(visual-line-mode +1)))
|
||||
(when-let (win (display-buffer output-buffer))
|
||||
(fit-window-to-buffer
|
||||
win (/ (frame-height) 2)
|
||||
nil (/ (frame-width) 2)))
|
||||
(select-window origin)
|
||||
(when-let* ((win (display-buffer output-buffer)))
|
||||
(fit-window-to-buffer win (/ (frame-height) 2)
|
||||
nil (/ (frame-width) 2)))
|
||||
output-buffer))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-display-results-in-overlay (output &optional source-buffer)
|
||||
"Display OUTPUT in a floating overlay next to the cursor."
|
||||
"Display OUTPUT in a floating overlay next to or below the cursor."
|
||||
(require 'eros)
|
||||
(with-current-buffer (or source-buffer (current-buffer))
|
||||
(let* ((this-command #'+eval/buffer-or-region)
|
||||
|
|
@ -31,7 +34,6 @@
|
|||
(prefixlen (length prefix))
|
||||
(len (+ (apply #'max (mapcar #'length lines))
|
||||
prefixlen))
|
||||
(col (- (current-column) (window-hscroll)))
|
||||
(next-line? (or (cdr lines)
|
||||
(< (- (window-width)
|
||||
(save-excursion (goto-char (line-end-position))
|
||||
|
|
@ -72,108 +74,90 @@
|
|||
output source-buffer)
|
||||
output)
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-region-as-major-mode (beg end &optional runner-major-mode)
|
||||
"Evaluate a region between BEG and END and display the output.
|
||||
|
||||
Evaluate as in RUNNER-MAJOR-MODE. If RUNNER-MAJOR-MODE is nil, use major-mode
|
||||
of the buffer instead."
|
||||
(let ((load-file-name buffer-file-name)
|
||||
(load-true-file-name
|
||||
(or buffer-file-truename
|
||||
(if buffer-file-name
|
||||
(file-truename buffer-file-name))))
|
||||
(runner-major-mode (or runner-major-mode major-mode)))
|
||||
(cond ((if (fboundp '+eval--ensure-in-repl-buffer)
|
||||
(ignore-errors
|
||||
(get-buffer-window (or (+eval--ensure-in-repl-buffer)
|
||||
t))))
|
||||
(funcall (or (plist-get (cdr (alist-get runner-major-mode +eval-repls)) :send-region)
|
||||
#'+eval/send-region-to-repl)
|
||||
beg end))
|
||||
((let (lang)
|
||||
(if-let* ((runner
|
||||
(or (alist-get runner-major-mode +eval-runners)
|
||||
(and (require 'quickrun nil t)
|
||||
(equal (setq
|
||||
lang (quickrun--command-key
|
||||
(buffer-file-name (buffer-base-buffer))))
|
||||
"emacs")
|
||||
(alist-get 'emacs-lisp-mode +eval-runners)))))
|
||||
(funcall runner beg end)
|
||||
(let ((quickrun-option-cmdkey lang))
|
||||
(quickrun-region beg end))))))))
|
||||
;;
|
||||
;;; Eval handlers
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-with-mode-handler-fn (beg end &optional _type mode)
|
||||
"Evaluate the selection/buffer using a mode appropriate handler.
|
||||
|
||||
Uses whatever handler's been registered for MODE (or the current major-mode)
|
||||
with `set-eval-handler!'."
|
||||
(when-let* ((fn (alist-get (or mode major-mode) +eval-handler-alist)))
|
||||
(funcall fn beg end)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-with-quickrun-fn (beg end &optional type)
|
||||
"Evaluate the region or buffer with `quickrun'."
|
||||
(when (require 'quickrun nil t)
|
||||
(pcase type
|
||||
(`buffer (quickrun))
|
||||
(`region (quickrun-region beg end))
|
||||
(`replace (quickrun-replace-region beg end)))
|
||||
t))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defvar quickrun-option-cmdkey)
|
||||
;;;###autoload
|
||||
(defun +eval/buffer ()
|
||||
"Evaluate the whole buffer."
|
||||
"Evaluate the whole buffer and display the output.
|
||||
|
||||
See `+eval-handler-functions' for order of backends this uses. By default, falls
|
||||
back to `quickrun'."
|
||||
(interactive)
|
||||
(let ((quickrun-option-cmdkey (bound-and-true-p quickrun-option-cmdkey)))
|
||||
(if (or (assq major-mode +eval-runners)
|
||||
(and (fboundp '+eval--ensure-in-repl-buffer)
|
||||
(ignore-errors
|
||||
(get-buffer-window (or (+eval--ensure-in-repl-buffer)
|
||||
t))))
|
||||
(and (require 'quickrun nil t)
|
||||
(equal (setq
|
||||
quickrun-option-cmdkey
|
||||
(quickrun--command-key
|
||||
(buffer-file-name (buffer-base-buffer))))
|
||||
"emacs")
|
||||
(alist-get 'emacs-lisp-mode +eval-runners)))
|
||||
(if-let* ((buffer-handler (plist-get (cdr (alist-get major-mode +eval-repls)) :send-buffer)))
|
||||
(funcall buffer-handler)
|
||||
(+eval/region (point-min) (point-max)))
|
||||
(quickrun))))
|
||||
(run-hook-with-args-until-success
|
||||
'+eval-handler-functions (point-min) (point-max) 'buffer))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/region (beg end)
|
||||
"Evaluate a region between BEG and END and display the output."
|
||||
"Evaluate a region between BEG and END and display the output.
|
||||
|
||||
If a REPL is open, code will be executed there, otherwise mode-specific handlers
|
||||
will be used, falling back to `quickrun' otherwise.
|
||||
|
||||
See `+eval-handler-functions' for order of backends this uses. By default, falls
|
||||
back to `quickrun'."
|
||||
(interactive "r")
|
||||
(+eval-region-as-major-mode beg end))
|
||||
(run-hook-with-args-until-success
|
||||
'+eval-handler-functions beg end 'region))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/line-or-region ()
|
||||
"Evaluate the current line or selected region."
|
||||
"Evaluate the current line or selected region.
|
||||
|
||||
If a REPL is open, code will be executed there, otherwise mode-specific handlers
|
||||
will be used, falling back to `quickrun' otherwise."
|
||||
(interactive)
|
||||
(if (use-region-p)
|
||||
(call-interactively #'+eval/region)
|
||||
(+eval/region (line-beginning-position) (line-end-position))))
|
||||
(+eval/region (pos-bol) (pos-eol))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/buffer-or-region ()
|
||||
"Evaluate the region if it's active, otherwise evaluate the whole buffer.
|
||||
|
||||
If a REPL is open the code will be evaluated in it, otherwise a quickrun
|
||||
runner will be used."
|
||||
"Execute `+eval/region' if a selection is active, otherwise `+eval/buffer'."
|
||||
(interactive)
|
||||
(call-interactively
|
||||
(if (use-region-p)
|
||||
(if (doom-region-active-p)
|
||||
#'+eval/region
|
||||
#'+eval/buffer)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/region-and-replace (beg end)
|
||||
"Evaluation a region between BEG and END, and replace it with the result."
|
||||
"Evaluate a region between BEG and END, and replace it with the result.
|
||||
|
||||
Uses `quickrun', unless in an `emacs-lisp-mode' buffer, in which case uses the
|
||||
return value of `eval'."
|
||||
(interactive "r")
|
||||
(let (lang)
|
||||
(cond
|
||||
((or (eq major-mode 'emacs-lisp-mode)
|
||||
(and (require 'quickrun nil t)
|
||||
(equal (setq
|
||||
lang (quickrun--command-key
|
||||
(buffer-file-name (buffer-base-buffer))))
|
||||
"emacs")))
|
||||
(kill-region beg end)
|
||||
(condition-case nil
|
||||
(prin1 (eval (read (current-kill 0)))
|
||||
(current-buffer))
|
||||
(error (message "Invalid expression")
|
||||
(insert (current-kill 0)))))
|
||||
((let ((quickrun-option-cmdkey lang))
|
||||
(quickrun-replace-region beg end))))))
|
||||
(if (not (derived-mode-p 'emacs-lisp-mode))
|
||||
(quickrun-replace-region beg end)
|
||||
(kill-region beg end)
|
||||
(condition-case nil
|
||||
(prin1 (eval (read (current-kill 0)))
|
||||
(current-buffer))
|
||||
(error (message "Invalid expression")
|
||||
(insert (current-kill 0))))))
|
||||
|
||||
;;; eval.el ends here
|
||||
|
|
|
|||
|
|
@ -1,24 +1,84 @@
|
|||
;;; tools/eval/autoload/repl.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar evil-move-cursor-back)
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar +eval-repl-buffers (make-hash-table :test 'equal)
|
||||
"The buffer of the last open repl.")
|
||||
|
||||
(defvar-local +eval-repl-plist nil)
|
||||
(defvar +eval-repl-plist nil)
|
||||
|
||||
(define-minor-mode +eval-repl-mode
|
||||
"A minor mode for REPL buffers.")
|
||||
|
||||
(defun +eval--ensure-in-repl-buffer (&optional fn plist displayfn)
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-current-repl-buffer (&optional mode)
|
||||
"Return the last active REPL buffer associated with this buffer's major mode.
|
||||
|
||||
Returns nil if none is known. If multiple are known, it returns the last
|
||||
accessed buffer."
|
||||
(when-let* ((project-root (doom-project-root))
|
||||
(key (cons (or mode major-mode) project-root))
|
||||
(buffer (gethash key +eval-repl-buffers)))
|
||||
(and (bufferp buffer)
|
||||
(buffer-live-p buffer)
|
||||
(buffer-local-value '+eval-repl-plist buffer)
|
||||
buffer)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval-repl-select (prompt)
|
||||
"Prompt the user to select a REPL.
|
||||
|
||||
Prompt with PROMPT, which should be a string ending with a colon and a space.
|
||||
Scans `+eval-repl-handler-alist' and all known symbols that look like
|
||||
*/open-*repl and returns (NAME COMMAND), where NAME is a string label and
|
||||
COMMAND is a symbol for an interactive function."
|
||||
(let* ((knowns
|
||||
(mapcar
|
||||
(lambda (spec)
|
||||
(unless (fboundp (car spec))
|
||||
(error "Given string/symbol is not a major mode: %s" (car spec)))
|
||||
(list (string-join
|
||||
(split-string
|
||||
(capitalize (string-remove-suffix "-mode" (symbol-name (car spec))))
|
||||
"-")
|
||||
" ")
|
||||
(cadr spec)))
|
||||
+eval-repl-handler-alist))
|
||||
(founds
|
||||
(mapcar
|
||||
(lambda (spec)
|
||||
(list (string-join (split-string (capitalize (cadr spec)) "-") " ")
|
||||
(car spec)))
|
||||
(cl-loop for sym being the symbols
|
||||
for sym-name = (symbol-name sym)
|
||||
if (string-match "^\\(?:\\+\\)?\\([^/]+\\)/open-\\(?:\\(.+\\)-\\)?repl$" sym-name)
|
||||
collect (list sym (match-string-no-properties 1 sym-name)))))
|
||||
(repls (cl-delete-duplicates (append knowns founds) :test #'equal)))
|
||||
(or (assoc (or (completing-read (or prompt "Open a REPL for: ")
|
||||
(mapcar #'car repls))
|
||||
(user-error "aborting"))
|
||||
repls)
|
||||
(error "couldn't find a valid repl for %s" major-mode))))
|
||||
|
||||
(defun +eval--repl-open (spec &optional displayfn input)
|
||||
"open a repl via the given displayfn. if prompt-p, the user will be
|
||||
prompted for a repl choice, even if the major mode they're in
|
||||
already has a known one."
|
||||
(maphash (lambda (key buffer)
|
||||
(unless (buffer-live-p buffer)
|
||||
(remhash key +eval-repl-buffers)))
|
||||
+eval-repl-buffers)
|
||||
(let* ((project-root (doom-project-root))
|
||||
(key (cons major-mode project-root))
|
||||
(buffer (gethash key +eval-repl-buffers)))
|
||||
(cl-check-type buffer (or buffer null))
|
||||
(unless (or (eq buffer (current-buffer))
|
||||
(null fn))
|
||||
(pcase-let ((`(_ ,fn . ,plist) spec))
|
||||
(unless (commandp fn)
|
||||
(error "couldn't find a valid REPL handler for %s" major-mode))
|
||||
(let* ((project-root (doom-project-root))
|
||||
(key (cons major-mode project-root))
|
||||
buffer)
|
||||
(setq buffer
|
||||
(funcall (or displayfn #'get-buffer-create)
|
||||
(if (buffer-live-p buffer)
|
||||
|
|
@ -28,147 +88,121 @@
|
|||
(if (commandp fn)
|
||||
(call-interactively fn)
|
||||
(funcall fn))))
|
||||
(cond ((null buffer)
|
||||
(error "REPL handler %S couldn't open the REPL buffer" fn))
|
||||
((not (bufferp buffer))
|
||||
(error "REPL handler %S failed to return a buffer" fn)))
|
||||
(unless buffer
|
||||
(error "REPL handler %S couldn't open the REPL buffer" fn))
|
||||
(unless (bufferp buffer)
|
||||
(error "REPL handler %S failed to return a buffer" fn))
|
||||
(with-current-buffer buffer
|
||||
(when plist
|
||||
(setq +eval-repl-plist plist))
|
||||
(+eval-repl-mode +1))
|
||||
(setq-local +eval-repl-plist (append (list :repl t) plist)))
|
||||
(puthash key buffer +eval-repl-buffers)
|
||||
buffer))))
|
||||
(when (bufferp buffer)
|
||||
(with-current-buffer buffer
|
||||
(unless (or (derived-mode-p 'term-mode)
|
||||
(eq (current-local-map) (bound-and-true-p term-raw-map)))
|
||||
(goto-char (if (and (derived-mode-p 'comint-mode)
|
||||
(cdr comint-last-prompt))
|
||||
(cdr comint-last-prompt)
|
||||
(point-max)))))
|
||||
buffer)))
|
||||
buffer)))
|
||||
(when (bufferp buffer)
|
||||
(with-current-buffer buffer
|
||||
(unless (or (derived-mode-p 'term-mode)
|
||||
(eq (current-local-map) (bound-and-true-p term-raw-map)))
|
||||
(goto-char (if (and (derived-mode-p 'comint-mode)
|
||||
(cdr comint-last-prompt))
|
||||
(cdr comint-last-prompt)
|
||||
(point-max))))
|
||||
(when (bound-and-true-p evil-local-mode)
|
||||
(call-interactively #'evil-append-line))
|
||||
(when input
|
||||
(insert input))
|
||||
t)))))
|
||||
|
||||
(defun +eval-repl-known-repls ()
|
||||
"Yield the available repl functions as a list of symbols."
|
||||
(cl-delete-duplicates
|
||||
(mapcar (lambda! ((mode fn &rest _)) (list mode fn))
|
||||
+eval-repls)))
|
||||
(defun +eval--repl-sender-for (mode &optional beg end)
|
||||
(when-let*
|
||||
((plist (cdr (alist-get mode +eval-repl-handler-alist)))
|
||||
(fn (or (plist-get plist (if (and beg end) :send-region :send-buffer))
|
||||
(unless (and beg end) (plist-get plist :send-region)))))
|
||||
(if (and beg end)
|
||||
(lambda () (funcall fn beg end))
|
||||
fn)))
|
||||
|
||||
(defun +doom-pretty-mode-name (mode)
|
||||
"Convert a mode name into a variant nicer for human eyes."
|
||||
(let ((mode (if (symbolp mode) (symbol-name mode) mode)))
|
||||
(if (not (string-match "^\\([a-z-]+\\)-mode$" mode))
|
||||
(error "Given string/symbol is not a major mode: %s" mode)
|
||||
(string-join (split-string (capitalize (match-string-no-properties 1 mode))
|
||||
"-")
|
||||
" "))))
|
||||
|
||||
(defun +eval-repl-found-repls ()
|
||||
"Search the interned symbol list for functions that looks like
|
||||
repl openers."
|
||||
(cl-loop for sym being the symbols
|
||||
for sym-name = (symbol-name sym)
|
||||
if (string-match "^\\(?:\\+\\)?\\([^/]+\\)/open-\\(?:\\(.+\\)-\\)?repl$" sym-name)
|
||||
collect
|
||||
sym))
|
||||
|
||||
(defun +eval-pretty-mode-name-from-fn (fn)
|
||||
"Given a symbol name of a repl-opening function, extract a
|
||||
human-readable variant of its associated major mode name."
|
||||
(let ((name (symbol-name fn)))
|
||||
(if (not (string-match "^\\(?:\\+\\)?\\([^/]+\\)/open-\\(?:\\(.+\\)-\\)?repl$" name))
|
||||
(error "Given symbol is not a repl function: %s" name)
|
||||
(string-join (split-string (capitalize (match-string-no-properties 1 name))
|
||||
"-")
|
||||
" "))))
|
||||
|
||||
(defun +eval-repl-prompt ()
|
||||
"Prompt the user for the choice of a repl to open."
|
||||
(let* ((knowns (mapcar (lambda! ((mode fn)) (list (+doom-pretty-mode-name mode) fn))
|
||||
(+eval-repl-known-repls)))
|
||||
(founds (mapcar (lambda (fn) (list (+eval-pretty-mode-name-from-fn fn) fn))
|
||||
(+eval-repl-found-repls)))
|
||||
(repls (cl-delete-duplicates (append knowns founds) :test #'equal))
|
||||
(names (mapcar #'car repls))
|
||||
(choice (or (completing-read "Open a REPL for: " names)
|
||||
(user-error "Aborting"))))
|
||||
(cadr (assoc choice repls))))
|
||||
|
||||
(defun +eval-repl-from-major-mode ()
|
||||
"Fetch the repl associated with the current major mode, if there
|
||||
is one."
|
||||
(pcase-let ((`(_ ,fn . ,plist) (assq major-mode +eval-repls)))
|
||||
(list fn plist)))
|
||||
|
||||
(defun +eval-open-repl (prompt-p &optional displayfn)
|
||||
"Open a repl via the given DISPLAYFN. If PROMPT-P, the user will be
|
||||
prompted for a repl choice, even if the major mode they're in
|
||||
already has a known one."
|
||||
(pcase-let* ((`(,fn ,plist) (+eval-repl-from-major-mode))
|
||||
(fn (if (or prompt-p (not fn)) (+eval-repl-prompt) fn))
|
||||
(region (when (use-region-p)
|
||||
(buffer-substring-no-properties (region-beginning)
|
||||
(region-end)))))
|
||||
(unless (commandp fn)
|
||||
(error "Couldn't find a valid REPL for %s" major-mode))
|
||||
(with-current-buffer (+eval--ensure-in-repl-buffer fn plist displayfn)
|
||||
(when (bound-and-true-p evil-mode)
|
||||
(call-interactively #'evil-append-line))
|
||||
(when region
|
||||
(insert region))
|
||||
t)))
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
;;; Eval handlers
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/open-repl-same-window (&optional arg)
|
||||
"Opens (or reopens) the REPL associated with the current major-mode and place
|
||||
the cursor at the prompt.
|
||||
(defun +eval-with-repl-fn (beg end &optional type)
|
||||
"Evaluate the region between BEG and END (inclusive) in an open REPL.
|
||||
|
||||
If ARG (universal argument), prompt for a specific REPL to open."
|
||||
(interactive "P")
|
||||
(+eval-open-repl arg #'switch-to-buffer))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/open-repl-other-window (&optional arg)
|
||||
"Does `+eval/open-repl', but in a popup window.
|
||||
|
||||
If ARG (universal argument), prompt for a specific REPL to open."
|
||||
(interactive "P")
|
||||
(+eval-open-repl arg #'pop-to-buffer))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/send-region-to-repl (beg end &optional inhibit-auto-execute-p)
|
||||
"Execute the selected region in the REPL.
|
||||
Opens a REPL if one isn't already open. If AUTO-EXECUTE-P, then execute it
|
||||
immediately after."
|
||||
(interactive "rP")
|
||||
(let ((buffer (+eval--ensure-in-repl-buffer)))
|
||||
(unless buffer
|
||||
(error "No REPL open"))
|
||||
(let* ((region (buffer-substring-no-properties beg end))
|
||||
(region
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert region))
|
||||
(when (> (skip-chars-forward "\n") 0)
|
||||
(delete-region (point-min) (point)))
|
||||
(indent-rigidly (point-min) (point-max) (- (current-indentation)))
|
||||
(buffer-string))))
|
||||
(with-selected-window (get-buffer-window buffer)
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-max))
|
||||
(dolist (line (split-string region "\n"))
|
||||
(insert line)
|
||||
(if inhibit-auto-execute-p
|
||||
(insert "\n")
|
||||
;; Can't use `comint-send-input' b/c there's no guarantee the
|
||||
;; current REPL uses comint. Even if it did, no telling if they
|
||||
;; have their own `comint-send-input' wrapper, so to be safe, I
|
||||
;; simply emulate the keypress.
|
||||
If no REPL is open, do nothing. TYPE can be `buffer' or `region' to determine
|
||||
what sender to use, if one's been registered with the repl for the current major
|
||||
mode."
|
||||
(when-let* ((buf (+eval-current-repl-buffer))
|
||||
((get-buffer-window buf)))
|
||||
(if-let* ((fn (if (eq type 'buffer)
|
||||
(+eval--repl-sender-for major-mode)
|
||||
(+eval--repl-sender-for major-mode beg end))))
|
||||
(funcall fn)
|
||||
;; Manually feed selection line-by-line if this repl has no
|
||||
;; :send-buffer/:send-region properties for its `set-repl-handler!'
|
||||
;; handler. This is a last resort and may be rife with edge cases.
|
||||
(let* ((region (buffer-substring-no-properties beg end))
|
||||
(region
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert region))
|
||||
(when (> (skip-chars-forward "\n") 0)
|
||||
(delete-region (point-min) (point)))
|
||||
(indent-rigidly (point-min) (point-max) (- (current-indentation)))
|
||||
(buffer-string))))
|
||||
(with-selected-window (get-buffer-window buf)
|
||||
(with-current-buffer buf
|
||||
(goto-char (point-max))
|
||||
(dolist (line (split-string region "\n"))
|
||||
(insert line)
|
||||
;; HACK: Can't use `comint-send-input' b/c there's no guarantee
|
||||
;; the current REPL uses comint. Even if it did, no telling if
|
||||
;; they have their own `comint-send-input' wrapper, so to be
|
||||
;; safe, I simply emulate the keypress.
|
||||
(if (bound-and-true-p evil-local-mode)
|
||||
(let (evil-move-cursor-back)
|
||||
(evil-save-state
|
||||
(evil-append-line 1)
|
||||
(call-interactively (doom-lookup-key (kbd "RET")))))
|
||||
(call-interactively (doom-lookup-key (kbd "RET")))))))))))
|
||||
(call-interactively (doom-lookup-key (kbd "RET"))))))))
|
||||
t)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/open-repl-same-window (&optional spec input)
|
||||
"Opens (or reopens) the REPL associated with the current major-mode and place
|
||||
the cursor at the prompt.
|
||||
|
||||
If ARG (universal argument), prompt for a specific REPL to open."
|
||||
(interactive
|
||||
(list (or (unless current-prefix-arg
|
||||
(assq major-mode +eval-repl-handler-alist))
|
||||
(+eval-repl-select "Open REPL in this window: "))
|
||||
(doom-region)))
|
||||
(+eval--repl-open spec #'switch-to-buffer input))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/open-repl-other-window (&optional spec input)
|
||||
"Does `+eval/open-repl', but in a popup window.
|
||||
|
||||
If ARG (universal argument), prompt for a specific REPL to open."
|
||||
(interactive
|
||||
(list (or (unless current-prefix-arg
|
||||
(assq major-mode +eval-repl-handler-alist))
|
||||
(+eval-repl-select "Open REPL in popup: "))
|
||||
(doom-region)))
|
||||
(+eval--repl-open spec #'pop-to-buffer input))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eval/buffer-or-region-in-repl (&optional beg end buffer?)
|
||||
"Execute the selected region or whole buffer in the REPL."
|
||||
(interactive "rP")
|
||||
(unless (+eval-current-repl-buffer)
|
||||
(call-interactively #'+eval/open-repl-other-window))
|
||||
(let* ((region? (and (not buffer?) (doom-region-active-p)))
|
||||
(type (if region? 'region 'buffer))
|
||||
(beg (if region? beg (point-min)))
|
||||
(end (if region? end (point-max))))
|
||||
(+eval-with-repl-fn beg end type)))
|
||||
|
||||
;;; repl.el ends here
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
;;; tools/eval/autoload/settings.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;; REPLs
|
||||
;;; REPLs
|
||||
|
||||
(defvar +eval-repls nil
|
||||
;;;###autoload
|
||||
(defvar +eval-repl-handler-alist nil
|
||||
"An alist mapping major modes to plists that describe REPLs. Used by
|
||||
`+eval/open-repl-other-window' and filled with the `:repl' setting.")
|
||||
|
||||
|
|
@ -27,20 +28,20 @@ recognized:
|
|||
A function that accepts a BEG and END, and sends the contents of the region
|
||||
to the REPL. Defaults to `+eval/send-region-to-repl'.
|
||||
:send-buffer FUNC
|
||||
A function of no arguments that sends the contents of the buffer to the REPL.
|
||||
Defaults to `+eval/region', which will run the :send-region specified function
|
||||
or `+eval/send-region-to-repl'."
|
||||
A function of no arguments that sends the contents of the buffer to the
|
||||
REPL. Defaults to `+eval/region', which will run the :send-region specified
|
||||
function or `+eval/send-region-to-repl'."
|
||||
(declare (indent defun))
|
||||
(dolist (mode (ensure-list modes))
|
||||
(setf (alist-get mode +eval-repls)
|
||||
(setf (alist-get mode +eval-repl-handler-alist)
|
||||
(cons command plist))))
|
||||
|
||||
|
||||
;;
|
||||
;; Evaluation
|
||||
;;; Evaluation
|
||||
|
||||
;;;###autoload
|
||||
(defvar +eval-runners nil
|
||||
(defvar +eval-handler-alist nil
|
||||
"Alist mapping major modes to interactive runner functions.")
|
||||
|
||||
;;;###autodef
|
||||
|
|
@ -57,20 +58,22 @@ MODES can be list of major mode symbols, or a single one.
|
|||
3. If MODE is not a string and COMMAND is an alist, see `quickrun-add-command':
|
||||
(quickrun-add-command MODE COMMAND :mode MODE).
|
||||
4. If MODE is not a string and COMMANd is a symbol, add it to
|
||||
`+eval-runners', which is used by `+eval/region'."
|
||||
`+eval-handler-alist', which is used by `+eval/region'."
|
||||
(declare (indent defun))
|
||||
(dolist (mode (ensure-list modes))
|
||||
(cond ((symbolp command)
|
||||
(push (cons mode command) +eval-runners))
|
||||
(setf (alist-get mode +eval-handler-alist nil t)
|
||||
command))
|
||||
((stringp command)
|
||||
(after! quickrun
|
||||
(push (cons mode command)
|
||||
(if (stringp mode)
|
||||
quickrun-file-alist
|
||||
quickrun--major-mode-alist))))
|
||||
(setf (alist-get mode (if (stringp mode)
|
||||
quickrun-file-alist
|
||||
quickrun--major-mode-alist)
|
||||
nil t)
|
||||
command)))
|
||||
((listp command)
|
||||
(after! quickrun
|
||||
(quickrun-add-command
|
||||
(or (cdr (assq mode quickrun--major-mode-alist))
|
||||
(or (alist-get mode quickrun--major-mode-alist)
|
||||
(string-remove-suffix "-mode" (symbol-name mode)))
|
||||
command :mode mode))))))
|
||||
|
|
|
|||
|
|
@ -1,16 +1,37 @@
|
|||
;;; tools/eval/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +eval-popup-min-lines 4
|
||||
"The output height threshold (inclusive) before output is displayed in a popup
|
||||
buffer rather than an overlay on the line at point or the minibuffer.")
|
||||
(defgroup +eval nil
|
||||
"Tools and commands for evaluating code universally and managing REPLs."
|
||||
:group 'doom+)
|
||||
|
||||
;; remove ellipsis when printing sexps in message buffer
|
||||
(setq eval-expression-print-length nil
|
||||
eval-expression-print-level nil)
|
||||
(defcustom +eval-handler-functions
|
||||
'(+eval-with-repl-fn
|
||||
+eval-with-mode-handler-fn
|
||||
+eval-with-quickrun-fn)
|
||||
"A list of functions to execute when evaluating a region/buffer.
|
||||
|
||||
Stops at the first function to return non-nil. Each function takes three
|
||||
arguments: a beginning position (int), an end position (int), and a symbol
|
||||
(either `region' or `buffer') to hint at the scope of the evaluation.
|
||||
|
||||
Only affects `+eval/region', `+eval/buffer', and any other command that use
|
||||
these them."
|
||||
:type 'hook
|
||||
:group '+eval)
|
||||
|
||||
(defcustom +eval-popup-min-lines 4
|
||||
"The output height threshold (inclusive) before output is displayed in a popup
|
||||
buffer rather than an overlay on the line at point or the minibuffer."
|
||||
:type 'integer
|
||||
:group '+eval)
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
;;; Config
|
||||
|
||||
;; Remove ellipsis when printing sexps in message buffer
|
||||
(setq eval-expression-print-length nil
|
||||
eval-expression-print-level nil)
|
||||
|
||||
;; These commands are drop-in replacements, but present an overlay/popup to
|
||||
;; display the return value, and emit a backtrace if an error is encountered.
|
||||
|
|
@ -19,8 +40,8 @@ buffer rather than an overlay on the line at point or the minibuffer.")
|
|||
|
||||
(set-popup-rule!
|
||||
(lambda (bufname _)
|
||||
(when (boundp '+eval-repl-mode)
|
||||
(buffer-local-value '+eval-repl-mode (get-buffer bufname))))
|
||||
(and (boundp '+eval-repl-plist)
|
||||
(buffer-local-value '+eval-repl-plist (get-buffer bufname))))
|
||||
:ttl (lambda (buf)
|
||||
(unless (plist-get +eval-repl-plist :persist)
|
||||
(when-let* ((process (get-buffer-process buf)))
|
||||
|
|
@ -53,7 +74,8 @@ buffer rather than an overlay on the line at point or the minibuffer.")
|
|||
|
||||
(defadvice! +eval--quickrun-auto-close-a (&rest _)
|
||||
"Silently re-create the quickrun popup when re-evaluating."
|
||||
:before '(quickrun quickrun-region)
|
||||
:before #'quickrun
|
||||
:before #'quickrun-region
|
||||
(when-let* ((win (get-buffer-window quickrun--buffer-name)))
|
||||
(let ((inhibit-message t))
|
||||
(quickrun--kill-running-process)
|
||||
|
|
@ -64,18 +86,18 @@ buffer rather than an overlay on the line at point or the minibuffer.")
|
|||
(defun +eval-quickrun-shrink-window-h ()
|
||||
"Shrink the quickrun output window once code evaluation is complete."
|
||||
(when-let* ((win (get-buffer-window quickrun--buffer-name)))
|
||||
(with-selected-window (get-buffer-window quickrun--buffer-name)
|
||||
(with-selected-window win
|
||||
(let ((ignore-window-parameters t))
|
||||
(shrink-window-if-larger-than-buffer)))))
|
||||
(defun +eval-quickrun-scroll-to-bof-h ()
|
||||
"Ensures window is scrolled to BOF on invocation."
|
||||
"Ensures cursor is at beginning of output window when displayed."
|
||||
(when-let* ((win (get-buffer-window quickrun--buffer-name)))
|
||||
(with-selected-window win
|
||||
(goto-char (point-min))))))
|
||||
|
||||
;; Display evaluation results in an overlay at the end of the current line. If
|
||||
;; the output is more than `+eval-popup-min-lines' (4) lines long, it is
|
||||
;; displayed in a popup.
|
||||
;; HACK: Display evaluation results in an overlay at the end of the current
|
||||
;; line. If the output is more than `+eval-popup-min-lines' (4) lines long,
|
||||
;; it is displayed in a popup.
|
||||
(when (modulep! +overlay)
|
||||
(defadvice! +eval--show-output-in-overlay-a (fn)
|
||||
:filter-return #'quickrun--make-sentinel
|
||||
|
|
@ -87,7 +109,7 @@ buffer rather than an overlay on the line at point or the minibuffer.")
|
|||
(string-trim (buffer-string))
|
||||
quickrun--original-buffer)))))
|
||||
|
||||
;; Suppress quickrun's popup window because we're using an overlay instead.
|
||||
;; HACK: Suppress quickrun's popup because we're using an overlay instead.
|
||||
(defadvice! +eval--inhibit-quickrun-popup-a (buf cb)
|
||||
:override #'quickrun--pop-to-buffer
|
||||
(setq quickrun--original-buffer (current-buffer))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue