;;; 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 "") (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)