From c05b7f456d65c64898a0a49c96d06679eadb9333 Mon Sep 17 00:00:00 2001 From: Madeleine Sydney Date: Thu, 30 Jan 2025 05:51:25 -0700 Subject: [PATCH] fix: Repls finally respect popup rules TwT - Required patching on.el to run their hooks at the correct time. --- users/crumb/programs/emacs/init.el | 20 +- .../programs/emacs/lib/syd-handle-repl.el | 198 ++++++++++++------ users/crumb/programs/emacs/lib/syd-prelude.el | 5 - .../modules/doom-popup/doom-popup-config.el | 32 +-- .../modules/doom-popup/doom-popup-settings.el | 2 +- .../emacs/modules/doom-popup/doom-popup.el | 9 - .../programs/emacs/modules/syd-popups.el | 24 +++ 7 files changed, 191 insertions(+), 99 deletions(-) create mode 100644 users/crumb/programs/emacs/modules/syd-popups.el diff --git a/users/crumb/programs/emacs/init.el b/users/crumb/programs/emacs/init.el index 53d56bb..1d45df2 100755 --- a/users/crumb/programs/emacs/init.el +++ b/users/crumb/programs/emacs/init.el @@ -13,16 +13,16 @@ ;; Must come before the rest! (require 'syd-use-package) -(eval-when-compile - (add-to-list 'load-path (file-name-concat user-emacs-directory "modules" "syd-popup"))) -(use-package doom-popup - ;; :defer t - :load-path "/persist/dots/users/crumb/programs/emacs/modules/doom-popup" - :straight nil - ;; :straight - ;; (:type nil - ;; :local-repo "/persist/dots/users/crumb/programs/emacs/modules/doom-popup") - ) +;; `on.el' provies a collection of utility hooks and functions ported from Doom +;; Emacs. The hooks make it easier to speed up Emacs startup by providing +;; finer-grained control of the timing at which packages are loaded. +(use-package on + :straight (:type git + :host gitlab + :repo "crumbtoo/on.el")) + +;; Used in many other modules, so it comes first. +(require 'syd-popups) (require 'syd-age) (require 'syd-autosave) diff --git a/users/crumb/programs/emacs/lib/syd-handle-repl.el b/users/crumb/programs/emacs/lib/syd-handle-repl.el index 3a6e8b6..a320576 100644 --- a/users/crumb/programs/emacs/lib/syd-handle-repl.el +++ b/users/crumb/programs/emacs/lib/syd-handle-repl.el @@ -4,6 +4,28 @@ (require 'syd-prelude) (require 'syd-project) + + +(defun syd--set-popup-rules-for-repls-h () + (require 'doom-popup) + (set-popup-rule! + (lambda (bufname _) + (when (boundp 'syd-repl-mode) + (buffer-local-value 'syd-repl-mode (get-buffer bufname)))) + :ttl (lambda (buf) + (unless (plist-get syd-repl-plist :persist) + (when-let (process (get-buffer-process buf)) + (set-process-query-on-exit-flag process nil) + (kill-process process) + (kill-buffer buf)))) + :size 0.25 + :quit nil)) + +(add-hook 'on-init-ui-hook #'syd--set-popup-rules-for-repls-h 'append) + + +;;; State & settings + (defvar +syd-major-mode-repl-alist '() "TODO: An alist pairing major-modes (symbols) with plists describing REPLs.") @@ -12,7 +34,41 @@ buffers. Indeed, this implies a single REPL per language per project. Is this limitation worth overcoming? I'm not sure! I've yet to butt heads with it.") -(defvar-local syd-repl-plist nil) +(defvar-local syd-repl-plist nil + "A plist describing the repl associated with the current buffer. + +This is little more than a cache. Its value can almost always be equivalently +derived from `+syd-major-mode-repl-alist'.") + +(defun set-repl-handler! (modes command &rest plist) + "Defines a REPL for MODES. + +MODES is either a single major mode symbol or a list of them. COMMAND is a +function that creates and returns the REPL buffer. + +COMMAND can either be a function that takes no arguments, or an interactive +command that will be called interactively. COMMANDS must return either the repl +buffer or a function that takes no arguments and returns the repl buffer. + +PLIST is a property list that map special attributes to this repl. These are +recognized: + + :persist BOOL + If non-nil, this REPL won't be killed when its window is closed. + :send-region FUNC + 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'." + (declare (indent defun)) + (dolist (mode (ensure-list modes)) + (setf (alist-get mode +eval-repls) + (cons command plist)))) + + +;;; Repls ;;;###autoload (define-minor-mode syd-repl-mode @@ -36,36 +92,8 @@ not-alive buffer." (defun syd--get-repl-key () (cons major-mode (syd-project-root))) -(defun syd--call-repl-handler (repl-handler &optional plist maybe-repl-buffer) - "Call REPL-HANDLER, and error out if it does not return a buffer. - -REPL-HANDLER will be called interactively if supported." - (let ((repl-buffer - (if (buffer-live-p maybe-repl-buffer) - maybe-repl-buffer - (let ((repl-buffer* - (save-window-excursion - (if (commandp repl-handler) - (call-interactively repl-handler) - (funcall repl-handler))))) - (cond ((null repl-buffer*) - (error "REPL handler %S couldn't open the REPL buffer" repl-handler)) - ((not (bufferp repl-buffer*)) - (error "REPL handler %S failed to return a buffer" repl-handler)) - (t repl-buffer*)))))) - ;; Repl buffers are to be saved in `+syd-repl-buffers'; we've just - ;; opened one, so do so. - (puthash (syd--get-repl-key) repl-buffer +syd-repl-buffers) - ;; If it isn't a buffer, we return nil. - (when (bufferp repl-buffer) - (with-current-buffer repl-buffer - (when plist - (setq syd-repl-plist plist)) - (syd-repl-mode 1)) - repl-buffer))) - (defun syd--goto-end-of-repl () - "Move point to the last comint prompt or the end of the buffer." + "Try to move point to the last comint prompt or the end of the 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) @@ -73,10 +101,62 @@ REPL-HANDLER will be called interactively if supported." (cdr comint-last-prompt) (point-max))))) +(cl-defun syd--call-repl-handler (repl-handler &key plist repl-key) + "Spawn a new repl buffer using REPL-HANDLER. REPL-HANDLER's return value will +be returned. + +If REPL-HANDLER fails to return a buffer, this `syd--call-repl-handler' will +throw an error. `syd-repl-mode' will be enabled in the new buffer, and the +buffer will be cached in `+syd-repl-buffers'. + +REPL-HANDLER will be called interactively if supported." + (let ((repl-buffer (save-window-excursion + (if (commandp repl-handler) + (call-interactively repl-handler) + (funcall repl-handler))))) + (unless repl-buffer + (error "REPL handler %S couldn't open the REPL buffer" fn)) + (unless (bufferp repl-buffer) + (error "REPL handler %S failed to return a buffer" fn)) + (with-current-buffer repl-buffer + ;; It is important that `syd-repl-mode' is enabled before the buffer is + ;; displayed by `display-fn'. + (syd-repl-mode 1) + (when plist + ;; Cache the plist at `syd-repl-plist'. + (setq syd-repl-plist plist))) + (puthash repl-key repl-buffer +syd-repl-buffers) + repl-buffer)) + +;; #+begin_src dot :file /tmp/repl.svg :results file graphics +;; digraph { +;; bgcolor="transparent" +;; +;; node [ +;; fillcolor=gray95 +;; color=black +;; shape=record +;; ] +;; +;; "Start" [shape=diamond] +;; "Start" -> x1 +;; x1 [label="Is the user currently in the repl buffer,\nOR has a repl handler NOT been provided?"] +;; +;; x1 -> x2 [label="Yes"] +;; x1 -> x3 [label="No"] +;; x2 [label="Find entry in +syd-repl-buffers;\nis it a live buffer?"] +;; x2 -> x4 [label="Yes"] +;; x4 [label="Call display-fn on the\n+syd-repl-buffers entry, and use the result"] +;; x2 -> x5 [label="No"] +;; x5 [label="Call the provided repl-handler. Ensure it returns\na valid buffer, and pass the resultto display-fn.\nSet the plist, enable repl mode, update\n+syd-repl-buffers. Use the buffer returned by display-fn"] +;; x3 [label="Use entry found in +syd-repl-buffers"] +;; } +;; #+end_src + (cl-defun syd--ensure-in-repl-buffer (&key repl-handler plist (display-fn #'get-buffer-create)) - "TODO: Display the repl buffer associated with the current major mode and -project. A repl buffer will be created (using REPL-HANDLER) if necessary. + "Display the repl buffer associated with the current major mode and project. +A repl buffer will be created (using REPL-HANDLER) if necessary. If an active repl buffer is found in `+syd-repl-buffers', it will be displayed by the given DISPLAY-FN. @@ -86,16 +166,28 @@ PLIST is a plist of repl-specific options." (let* ((repl-key (syd--get-repl-key)) (maybe-repl-buffer (gethash repl-key +syd-repl-buffers))) (cl-check-type maybe-repl-buffer (or buffer null)) - (unless (or (eq maybe-repl-buffer (current-buffer)) - (null repl-handler)) - (let* ((repl-buffer (syd--call-repl-handler repl-handler - plist - maybe-repl-buffer)) - (displayed-repl-buffer (funcall display-fn repl-buffer))) - (when (bufferp displayed-repl-buffer) - (with-current-buffer displayed-repl-buffer - (syd--goto-end-of-repl)) - displayed-repl-buffer))))) + (let ((repl-buffer + (if (or (eq maybe-repl-buffer (current-buffer)) + (null repl-handler)) + ;; * If the current buffer is the repl buffer, we can be sure + ;; that it is not nil and can be returned as-is. + ;; * If we were not given a repl-handler, there's nothing else we + ;; can do. Return what was found in `+syd-repl-buffers', and + ;; hope it's the right thing. + maybe-repl-buffer + ;; If the repl buffer found in `+syd-repl-buffers' is live and + ;; well, we can return that. If not, we're going to have to spawn + ;; a new repl buffer with `repl-handler' and `display-fn'. + (if (buffer-live-p maybe-repl-buffer) + (funcall display-fn maybe-repl-buffer) + (funcall display-fn + (syd--call-repl-handler repl-handler + :plist plist + :repl-key repl-key)))))) + (when (bufferp repl-buffer) + (with-current-buffer repl-buffer + (syd--goto-end-of-repl)) + repl-buffer)))) (defun syd--known-repls () "Return a list of all known mode-repl pairs, each as a two-element list. @@ -133,6 +225,11 @@ function." (user-error "Aborting")))) (cadr (assoc choice repls)))) +(defun syd-send-region-to-repl (beg end) + (interactive "r") + (let ((selection (buffer-substring-no-properties beg end)) + (buffer (syd--ensure-in-repl-buffer))))) + (cl-defun +syd-open-repl (&key prompt-p display-fn) "TODO: Open a repl via DISPLAY-FN. When PROMPT-P, the user will be unconditionally prompted for a repl choice. @@ -168,21 +265,4 @@ prefix argument is given, in which case the user will be prompted for a repl." -(set-popup-rule! - (lambda (bufname _) - (pp bufname) - (with-current-buffer bufname - (pp (boundp 'syd-repl-mode)) - (pp syd-repl-mode)) - (when (boundp 'syd-repl-mode) - (buffer-local-value 'syd-repl-mode (get-buffer bufname)))) - :ttl (lambda (buf) - (unless (plist-get syd-repl-plist :persist) - (when-let (process (get-buffer-process buf)) - (set-process-query-on-exit-flag process nil) - (kill-process process) - (kill-buffer buf)))) - :size 0.25 - :quit nil) - (provide 'syd-handle-repl) diff --git a/users/crumb/programs/emacs/lib/syd-prelude.el b/users/crumb/programs/emacs/lib/syd-prelude.el index 8c5aac2..11f4de6 100755 --- a/users/crumb/programs/emacs/lib/syd-prelude.el +++ b/users/crumb/programs/emacs/lib/syd-prelude.el @@ -12,9 +12,4 @@ ,todo (error ,todo)))) -;; `on.el' provies a collection of utility hooks and functions ported from Doom -;; Emacs. The hooks make it easier to speed up Emacs startup by providing -;; finer-grained control of the timing at which packages are loaded. -(use-package on) - (provide 'syd-prelude) diff --git a/users/crumb/programs/emacs/modules/doom-popup/doom-popup-config.el b/users/crumb/programs/emacs/modules/doom-popup/doom-popup-config.el index 35c168e..fa3dab2 100644 --- a/users/crumb/programs/emacs/modules/doom-popup/doom-popup-config.el +++ b/users/crumb/programs/emacs/modules/doom-popup/doom-popup-config.el @@ -76,21 +76,23 @@ adjustment.") :init-value nil :global t :keymap doom-popup-mode-map - (cond (doom-popup-mode - (add-hook 'doom-escape-hook #'doom-popup-close-on-escape-h 'append) - (setq doom-popup--old-display-buffer-alist display-buffer-alist - display-buffer-alist doom-popup--display-buffer-alist - window--sides-inhibit-check t) - (dolist (prop doom-popup-window-parameters) - (push (cons prop 'writable) window-persistent-parameters))) - (t - (remove-hook 'doom-escape-hook #'doom-popup-close-on-escape-h) - (setq display-buffer-alist doom-popup--old-display-buffer-alist - window--sides-inhibit-check nil) - (doom-popup-cleanup-rules-h) - (dolist (prop doom-popup-window-parameters) - (delq (assq prop window-persistent-parameters) - window-persistent-parameters))))) + (progn + (message "doom-popup-mode: %S" doom-popup-mode) + (cond (doom-popup-mode + (add-hook 'doom-escape-hook #'doom-popup-close-on-escape-h 'append) + (setq doom-popup--old-display-buffer-alist display-buffer-alist + display-buffer-alist doom-popup--display-buffer-alist + window--sides-inhibit-check t) + (dolist (prop doom-popup-window-parameters) + (push (cons prop 'writable) window-persistent-parameters))) + (t + (remove-hook 'doom-escape-hook #'doom-popup-close-on-escape-h) + (setq display-buffer-alist doom-popup--old-display-buffer-alist + window--sides-inhibit-check nil) + (doom-popup-cleanup-rules-h) + (dolist (prop doom-popup-window-parameters) + (delq (assq prop window-persistent-parameters) + window-persistent-parameters)))))) (define-minor-mode doom-popup-buffer-mode "Minor mode for individual popup windows. diff --git a/users/crumb/programs/emacs/modules/doom-popup/doom-popup-settings.el b/users/crumb/programs/emacs/modules/doom-popup/doom-popup-settings.el index 4c74352..47633e7 100644 --- a/users/crumb/programs/emacs/modules/doom-popup/doom-popup-settings.el +++ b/users/crumb/programs/emacs/modules/doom-popup/doom-popup-settings.el @@ -191,7 +191,7 @@ used. (setq display-buffer-alist doom-popup--display-buffer-alist)) doom-popup--display-buffer-alist) -;;;###autodef +;;;###autoload (defun set-popup-rules! (&rest rulesets) "Defines multiple popup rules. diff --git a/users/crumb/programs/emacs/modules/doom-popup/doom-popup.el b/users/crumb/programs/emacs/modules/doom-popup/doom-popup.el index 239b934..b23841c 100644 --- a/users/crumb/programs/emacs/modules/doom-popup/doom-popup.el +++ b/users/crumb/programs/emacs/modules/doom-popup/doom-popup.el @@ -25,15 +25,6 @@ (require 'doom-popup-settings) -(use-package popper - :custom ((popper-display-control nil) - (popper-reference-buffers - (list (lambda (buf) - (with-current-buffer buf - (bound-and-true-p doom-popup-mode)))))) - :config - (popper-mode 1)) - (defvar doom-popup--internal nil) (defun doom-popup--remember (windows) diff --git a/users/crumb/programs/emacs/modules/syd-popups.el b/users/crumb/programs/emacs/modules/syd-popups.el new file mode 100644 index 0000000..f24111d --- /dev/null +++ b/users/crumb/programs/emacs/modules/syd-popups.el @@ -0,0 +1,24 @@ +;; syd-popups.el -*- lexical-binding: t; -*- + +(use-package popper + :disabled + :init + (setq popper-display-control nil + popper-reference-buffers + (list (lambda (buf) + (with-current-buffer buf + (bound-and-true-p doom-popup-mode))))) + :config + (popper-mode 1)) + +(use-package doom-popup + ;; :after popper + :load-path "/persist/dots/users/crumb/programs/emacs/modules/doom-popup" + :straight nil + ;; :straight + ;; (:type nil + ;; :local-repo "/persist/dots/users/crumb/programs/emacs/modules/doom-popup") + ) + +(provide 'syd-popups) +;;; syd-popups.el ends here