fix: Repls finally respect popup rules TwT

- Required patching on.el to run their hooks at the correct time.
This commit is contained in:
Madeleine Sydney
2025-01-30 05:51:25 -07:00
parent d4c686ab65
commit c05b7f456d
7 changed files with 191 additions and 99 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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