|
5 | 5 | ;; Author: Alvaro Ramirez https://xenodium.com |
6 | 6 | ;; URL: https://github.com/xenodium/agent-shell |
7 | 7 | ;; Version: 0.50.1 |
8 | | -;; Package-Requires: ((emacs "29.1") (shell-maker "0.90.1") (acp "0.11.1")) |
| 8 | +;; Package-Requires: ((emacs "29.1") (shell-maker "0.90.1") (acp "0.11.1") (markdown-mode "2.5")) |
9 | 9 |
|
10 | 10 | (defconst agent-shell--version "0.50.1") |
11 | 11 |
|
|
46 | 46 | (require 'diff) |
47 | 47 | (require 'json) |
48 | 48 | (require 'map) |
| 49 | +(require 'markdown-mode) |
49 | 50 | (unless (require 'markdown-overlays nil 'noerror) |
50 | 51 | (error "Please update 'shell-maker' to v0.90.1 or newer")) |
51 | 52 | (require 'agent-shell-invariants) |
@@ -7282,22 +7283,28 @@ Remove: M-x agent-shell-remove-pending-request |
7282 | 7283 | (append pending (list prompt))) |
7283 | 7284 | (message "Request queued (%d pending)" (length (map-elt agent-shell--state :pending-requests))))) |
7284 | 7285 |
|
7285 | | -(defun agent-shell-queue-request (prompt) |
| 7286 | +(defun agent-shell-queue-request (&optional prompt) |
7286 | 7287 | "Queue or immediately send a request depending on shell busy state. |
7287 | 7288 |
|
7288 | | -Read PROMPT from the minibuffer. If the shell is busy, add it to the pending |
7289 | | -requests queue. Otherwise, submit it immediately. Queued requests will be |
7290 | | -automatically sent when the current request completes." |
7291 | | - (interactive |
7292 | | - (progn |
7293 | | - (unless (derived-mode-p 'agent-shell-mode) |
7294 | | - (error "Not in a shell")) |
7295 | | - (list (read-string (or (map-nested-elt (agent-shell--state) '(:agent-config :shell-prompt)) |
7296 | | - "Enqueue request: "))))) |
7297 | | - (agent-shell--idle-notification-cancel) |
7298 | | - (if (shell-maker-busy) |
7299 | | - (agent-shell--enqueue-request :prompt prompt) |
7300 | | - (agent-shell--insert-to-shell-buffer :text prompt :submit t :no-focus t))) |
| 7289 | +Interactively, pop a `gfm-mode' compose buffer; submit on |
| 7290 | +\\<agent-shell-queue-compose-mode-map>\\[agent-shell-queue-compose-submit]. |
| 7291 | +If the shell is busy when submitted, add to the pending requests |
| 7292 | +queue; otherwise submit immediately. Queued requests will be |
| 7293 | +automatically sent when the current request completes. |
| 7294 | + |
| 7295 | +When called non-interactively with PROMPT, submit or queue |
| 7296 | +PROMPT directly, bypassing the compose buffer." |
| 7297 | + (declare (modes agent-shell-mode)) |
| 7298 | + (interactive) |
| 7299 | + (unless (derived-mode-p 'agent-shell-mode) |
| 7300 | + (user-error "Not in a shell")) |
| 7301 | + (cond |
| 7302 | + ((not prompt) |
| 7303 | + (agent-shell-queue-compose-pop (current-buffer))) |
| 7304 | + ((string-empty-p (string-trim prompt)) |
| 7305 | + (user-error "PROMPT is empty")) |
| 7306 | + (t |
| 7307 | + (agent-shell--queue-or-submit prompt (current-buffer))))) |
7301 | 7308 |
|
7302 | 7309 | (defun agent-shell-resume-pending-requests () |
7303 | 7310 | "Resume processing pending requests in the queue." |
@@ -7348,6 +7355,133 @@ or select a specific request to remove." |
7348 | 7355 | (map-put! agent-shell--state :pending-requests nil) |
7349 | 7356 | (message "Removed all pending requests")))) |
7350 | 7357 |
|
| 7358 | +;;; Queue compose |
| 7359 | + |
| 7360 | +(defvar-local agent-shell-queue-compose--shell-buffer nil |
| 7361 | + "Originating agent-shell buffer this compose buffer submits to.") |
| 7362 | + |
| 7363 | +(defvar-local agent-shell--queue-compose-buffer nil |
| 7364 | + "Compose buffer associated with this shell buffer, if any.") |
| 7365 | + |
| 7366 | +(defvar agent-shell-queue-compose-mode-map |
| 7367 | + (let ((map (make-sparse-keymap))) |
| 7368 | + (define-key map (kbd "C-c C-c") #'agent-shell-queue-compose-submit) |
| 7369 | + (define-key map (kbd "C-c C-k") #'agent-shell-queue-compose-cancel) |
| 7370 | + map) |
| 7371 | + "Keymap for `agent-shell-queue-compose-mode'.") |
| 7372 | + |
| 7373 | +(define-minor-mode agent-shell-queue-compose-mode |
| 7374 | + "Minor mode for the agent-shell queue-request compose buffer. |
| 7375 | + |
| 7376 | +Layered on top of `gfm-mode' so its keymap takes precedence over the |
| 7377 | +major-mode bindings. |
| 7378 | + |
| 7379 | +\\{agent-shell-queue-compose-mode-map}" |
| 7380 | + :lighter " ASQCompose" |
| 7381 | + :keymap agent-shell-queue-compose-mode-map) |
| 7382 | + |
| 7383 | +(defun agent-shell--queue-or-submit (prompt shell-buffer) |
| 7384 | + "Queue PROMPT or submit it to SHELL-BUFFER depending on busy state. |
| 7385 | + |
| 7386 | +The submit branch's `agent-shell--insert-to-shell-buffer' cancels |
| 7387 | +any active idle notification itself; the busy branch can't have |
| 7388 | +one active because the notification only fires when the shell is |
| 7389 | +idle." |
| 7390 | + (unless (buffer-live-p shell-buffer) |
| 7391 | + (user-error "Shell buffer is not live")) |
| 7392 | + (with-current-buffer shell-buffer |
| 7393 | + (if (shell-maker-busy) |
| 7394 | + (agent-shell--enqueue-request :prompt prompt) |
| 7395 | + (agent-shell--insert-to-shell-buffer |
| 7396 | + :shell-buffer shell-buffer :text prompt :submit t :no-focus t)))) |
| 7397 | + |
| 7398 | +(defun agent-shell-queue-compose-pop (shell-buffer) |
| 7399 | + "Pop a `gfm-mode' compose buffer bound to SHELL-BUFFER. |
| 7400 | + |
| 7401 | +Reuses the shell's existing compose buffer when alive so an |
| 7402 | +in-progress draft survives a re-invocation." |
| 7403 | + (unless (buffer-live-p shell-buffer) |
| 7404 | + (user-error "Shell buffer is not live")) |
| 7405 | + (let* ((existing (buffer-local-value 'agent-shell--queue-compose-buffer |
| 7406 | + shell-buffer)) |
| 7407 | + ;; Strip leading/trailing asterisks from the shell name so the |
| 7408 | + ;; compose buffer doesn't render as `*…: *shell**`. |
| 7409 | + (shell-stem (string-trim (buffer-name shell-buffer) "\\*+" "\\*+")) |
| 7410 | + (buffer (if (buffer-live-p existing) |
| 7411 | + (with-current-buffer existing |
| 7412 | + ;; Reset modified flag on reuse so the modeline |
| 7413 | + ;; doesn't carry the `**' indicator from a kept |
| 7414 | + ;; draft into the next session. |
| 7415 | + (set-buffer-modified-p nil) |
| 7416 | + existing) |
| 7417 | + (let ((new (generate-new-buffer |
| 7418 | + (format "*agent-shell-queue-compose: %s*" shell-stem)))) |
| 7419 | + (with-current-buffer new |
| 7420 | + (gfm-mode) |
| 7421 | + (agent-shell-queue-compose-mode 1) |
| 7422 | + (setq agent-shell-queue-compose--shell-buffer shell-buffer) |
| 7423 | + (add-hook 'kill-buffer-hook |
| 7424 | + #'agent-shell-queue-compose--clear-shell-ref |
| 7425 | + nil t) |
| 7426 | + (setq header-line-format |
| 7427 | + (substitute-command-keys |
| 7428 | + "Compose request — \\[agent-shell-queue-compose-submit] queue/submit · \\[agent-shell-queue-compose-cancel] cancel")) |
| 7429 | + (set-buffer-modified-p nil)) |
| 7430 | + (with-current-buffer shell-buffer |
| 7431 | + (setq agent-shell--queue-compose-buffer new)) |
| 7432 | + new)))) |
| 7433 | + (pop-to-buffer buffer) |
| 7434 | + buffer)) |
| 7435 | + |
| 7436 | +(defun agent-shell-queue-compose--clear-shell-ref () |
| 7437 | + "Clear the originating shell's compose-buffer pointer. |
| 7438 | +Run from `kill-buffer-hook' on the compose buffer so a killed |
| 7439 | +buffer doesn't leave a stale reference behind." |
| 7440 | + (let ((shell-buffer agent-shell-queue-compose--shell-buffer) |
| 7441 | + (this-buffer (current-buffer))) |
| 7442 | + (when (buffer-live-p shell-buffer) |
| 7443 | + (with-current-buffer shell-buffer |
| 7444 | + (when (eq agent-shell--queue-compose-buffer this-buffer) |
| 7445 | + (setq agent-shell--queue-compose-buffer nil)))))) |
| 7446 | + |
| 7447 | +(defun agent-shell-queue-compose--quit-or-kill () |
| 7448 | + "Kill the compose buffer, also quitting its window when displayed. |
| 7449 | +`quit-window t' kills the buffer and restores the previous window |
| 7450 | +state; if the buffer isn't currently displayed (e.g. invoked via |
| 7451 | +\\[execute-extended-command] after switching away), `quit-window' |
| 7452 | +would pick an arbitrary window — fall back to plain `kill-buffer'." |
| 7453 | + (if (get-buffer-window (current-buffer) t) |
| 7454 | + (quit-window t) |
| 7455 | + (kill-buffer (current-buffer)))) |
| 7456 | + |
| 7457 | +(defun agent-shell-queue-compose-submit () |
| 7458 | + "Submit (or queue) the contents of the compose buffer." |
| 7459 | + (interactive) |
| 7460 | + (unless agent-shell-queue-compose-mode |
| 7461 | + (user-error "Not in an agent-shell compose buffer")) |
| 7462 | + (let ((shell-buffer agent-shell-queue-compose--shell-buffer) |
| 7463 | + (prompt (buffer-string))) |
| 7464 | + (when (string-empty-p (string-trim prompt)) |
| 7465 | + (user-error "Compose buffer is empty")) |
| 7466 | + (unless (buffer-live-p shell-buffer) |
| 7467 | + (user-error "Originating shell buffer is no longer live")) |
| 7468 | + (agent-shell--queue-or-submit prompt shell-buffer) |
| 7469 | + (agent-shell-queue-compose--quit-or-kill))) |
| 7470 | + |
| 7471 | +(defun agent-shell-queue-compose-cancel () |
| 7472 | + "Cancel the compose buffer. |
| 7473 | + |
| 7474 | +Kills silently when the buffer is empty and unmodified; otherwise asks |
| 7475 | +for confirmation." |
| 7476 | + (interactive) |
| 7477 | + (unless agent-shell-queue-compose-mode |
| 7478 | + (user-error "Not in an agent-shell compose buffer")) |
| 7479 | + (if (or (and (zerop (buffer-size)) |
| 7480 | + (not (buffer-modified-p))) |
| 7481 | + (y-or-n-p "Discard compose buffer? ")) |
| 7482 | + (agent-shell-queue-compose--quit-or-kill) |
| 7483 | + (message "Kept draft"))) |
| 7484 | + |
7351 | 7485 | (provide 'agent-shell) |
7352 | 7486 |
|
7353 | 7487 | ;;; agent-shell.el ends here |
0 commit comments