Files
sydnix/modules/home/users/crumb/emacs/lib/syd-handle-eval.el
2025-03-13 13:59:07 -06:00

259 lines
9.8 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;;; syd-handle-eval.el -*- lexical-binding: t; -*-
(defvar syd-eval-runners '())
;; Remove ellipsis when printing sexps in message buffer.
(setq eval-expression-print-length nil
eval-expression-print-level nil)
(set-popup-rule!
"*eval*"
:size 0.2)
;; Packages
(use-package quickrun
:defer t)
(use-package eros
:hook (emacs-lisp-mode . eros-mode))
;; (with-eval-after-load quickrun
;; (setq quickrun-focus-p nil)
;; (set-popup-rule! "^\\*quickrun" :size 0.3 :ttl 0)
;; (defadvice! +eval--quickrun-fix-evil-visual-region-a ()
;; "Make `quickrun-replace-region' recognize evil visual selections."
;; :override #'quickrun--outputter-replace-region
;; (let ((output (buffer-substring-no-properties (point-min) (point-max))))
;; (with-current-buffer quickrun--original-buffer
;; (cl-destructuring-bind (beg . end)
;; ;; Because `deactivate-mark', the function, was used in
;; ;; `quickrun--region-command-common' instead of `deactivate-mark',
;; ;; the variable, the selection is disabled by this point.
;; (if (bound-and-true-p evil-local-mode)
;; (cons evil-visual-beginning evil-visual-end)
;; (cons (region-beginning) (region-end)))
;; (delete-region beg end)
;; (insert output))
;; (setq quickrun-option-outputter quickrun--original-outputter))))
;; (defadvice! +eval--quickrun-auto-close-a (&rest _)
;; "Silently re-create the quickrun popup when re-evaluating."
;; :before '(quickrun quickrun-region)
;; (when-let (win (get-buffer-window quickrun--buffer-name))
;; (let ((inhibit-message t))
;; (quickrun--kill-running-process)
;; (message ""))
;; (delete-window win)))
;; (add-hook! 'quickrun-after-run-hook
;; (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)
;; (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."
;; (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.
;; (when (modulep! +overlay)
;; (defadvice! +eval--show-output-in-overlay-a (fn)
;; :filter-return #'quickrun--make-sentinel
;; (lambda (process event)
;; (funcall fn process event)
;; (with-current-buffer quickrun--buffer-name
;; (when (> (buffer-size) 0)
;; (+eval-display-results
;; (string-trim (buffer-string))
;; quickrun--original-buffer)))))
;; ;; Suppress quickrun's popup window 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))
;; (save-window-excursion
;; (with-current-buffer (pop-to-buffer buf)
;; (setq quickrun-option-outputter #'ignore)
;; (funcall cb))))
;; ;; HACK Without this, `+eval--inhibit-quickrun-popup-a' throws a
;; ;; window-live-p error because no window exists to be recentered!
;; (advice-add #'quickrun--recenter :override #'ignore)))
;;;###autoload
(cl-defun syd-eval-region-as-major-mode
(beg end &key (runner-major-mode 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."
(if-let* ((runner (alist-get runner-major-mode syd-eval-runners)))
(funcall runner beg end)
(and (require 'quickrun nil t)
(let ((quickrun-option-cmdkey
(quickrun--command-key
(buffer-file-name (buffer-base-buffer)))))
(quickrun-region beg end)))))
;;;###autoload
(defun syd-eval-region (beg end)
(interactive "r")
;; (message "syd: %s" (pp-to-string
;; (buffer-substring-no-properties (syd-region-beginning)
;; (syd-region-end))))
;; (message "r: %s" (pp-to-string (buffer-substring-no-properties beg end)))
(syd-eval-region-as-major-mode beg end))
(with-eval-after-load 'evil
(evil-define-operator syd-eval-operator (beg end)
"Evaluate selection."
:move-point nil
(interactive "<r>")
(syd-eval-region beg end))
(general-def
:states 'normal
"gR" #'syd-eval-buffer)
(general-def
:states '(normal visual)
"gr" #'syd-eval-operator))
;;;###autoload
(defun set-eval-handler! (modes command)
"Define a code evaluator for major mode MODES with `quickrun'.
MODES can be list of major mode symbols, or a single one.
1. If MODE is a string and COMMAND is the string, MODE is a file regexp and
COMMAND is a string key for an entry in `quickrun-file-alist'.
2. If MODE is not a string and COMMAND is a string, MODE is a major-mode symbol
and COMMAND is a key (for `quickrun--language-alist'), and will be registered
in `quickrun--major-mode-alist'.
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
`syd-eval-runners', which is used by `syd-eval-region'."
(declare (indent defun))
(dolist (mode (ensure-list modes))
(cond ((symbolp command)
(push (cons mode command) syd-eval-runners))
((stringp command)
(with-eval-after-load 'quickrun
(push (cons mode command)
(if (stringp mode)
quickrun-file-alist
quickrun--major-mode-alist))))
((listp command)
(with-eval-after-load 'quickrun
(quickrun-add-command
(or (cdr (assq mode quickrun--major-mode-alist))
(string-remove-suffix "-mode" (symbol-name mode)))
command :mode mode))))))
(defvar syd-eval-overlay-max-lines 4
"The maximum number of lines allowed to be displayed in an eval overlay; any
more and a popup buffer will be used instead.")
;;;###autoload
(defun syd-eval-display-results-in-popup (output)
"Display OUTPUT in a popup buffer."
(let ((output-buffer (get-buffer-create "*eval*"))
(origin (selected-window)))
(with-current-buffer output-buffer
(setq-local scroll-margin 0)
(erase-buffer)
(insert output)
(goto-char (point-min))
;; (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)
output-buffer))
;;;###autoload
(defun syd-eval-buffer ()
"Evaluate the entire buffer."
(interactive)
(syd-eval-region (point-min) (point-max)))
;;;###autoload
(defun syd-eval-buffer-or-region ()
"Evaluate the region if it is active, or the entire buffer, or not."
(if (use-region-p)
(call-interactively #'syd-eval-region)
(call-interactively #'syd-eval-buffer)))
;;;###autoload
(cl-defun syd-eval-display-results-in-overlay (output &key source-buffer)
"Display OUTPUT in a floating overlay next to the cursor."
(require 'eros)
(with-current-buffer (or source-buffer (current-buffer))
(let* ((this-command #'syd-eval/buffer-or-region)
(prefix eros-eval-result-prefix)
(lines (split-string output "\n"))
(prefixlen (length prefix))
(len (+ (apply #'max (mapcar #'length lines))
prefixlen))
(next-line? (or (cdr lines)
(< (- (window-width)
(save-excursion (goto-char (line-end-position))
(- (current-column)
(window-hscroll))))
len)))
(pad (if next-line?
(+ (window-hscroll) prefixlen)
0))
eros-overlays-use-font-lock)
(eros--make-result-overlay
(concat (make-string (max 0 (- pad prefixlen)) ?\s)
prefix
(string-join lines (concat hard-newline (make-string pad ?\s))))
:where (if next-line?
(line-beginning-position 2)
(line-end-position))
:duration eros-eval-result-duration
:format "%s"))))
;;;###autoload
(cl-defun syd-eval-display-results (output &key source-buffer force-popup)
"Display OUTPUT in an overlay, or if it's too long, a popup buffer."
(if (or force-popup
;; EROS is used to display overlays. Without it, just use a popup.
(not (require 'eros nil t))
(with-temp-buffer
(insert output)
(or
;; Too tall!
(<= syd-eval-overlay-max-lines
(count-lines (point-min) (point-max)))
;; Too wide!
(<= (window-width)
(string-width
(buffer-substring (point-min)
(save-excursion
(goto-char (point-min))
(line-end-position))))))))
(syd-eval-display-results-in-popup output)
(syd-eval-display-results-in-overlay output :source-buffer source-buffer))
output)
(provide 'syd-handle-eval)