gptel-transient: Allow redirection to any buffer

* gptel.el (gptel-request): Update docstring to clarify what
BUFFER and POSITION do.  Addresses #191.

* gptel-transient.el (gptel-menu, gptel--suffix-send): Replace
"new session" and "existing session" redirection options with
"gptel session" and "any buffer", allowing for more flexibility
when redirecting.  "gptel session" can be an existing or new
session.  Fix bug where the prompt was generated from the contents
of the destination buffer instead of the current buffer when
redirecting to a gptel session.  Add comments demarcating blocks
in `gptel--suffix-send`.
This commit is contained in:
Karthik Chikmagalur 2024-01-22 17:11:26 -08:00
parent 89decb4201
commit d2f56c62a0
2 changed files with 75 additions and 66 deletions

View file

@ -127,25 +127,25 @@ which see."
("i" "Replace/Delete prompt" "i") ("i" "Replace/Delete prompt" "i")
"Response to:" "Response to:"
("m" "Minibuffer instead" "m") ("m" "Minibuffer instead" "m")
("n" "New session" "n" ("g" "gptel session" "g"
:class transient-option :class transient-option
:prompt "Name for new session: " :prompt "Existing or new gptel session: "
:reader :reader
(lambda (prompt _ history) (lambda (prompt _ _history)
(read-string (read-buffer
prompt (generate-new-buffer-name prompt (generate-new-buffer-name
(concat "*" (gptel-backend-name gptel-backend) "*")) (concat "*" (gptel-backend-name gptel-backend) "*"))
history))) nil (lambda (buf-name)
("e" "Existing session" "e" (if (consp buf-name) (setq buf-name (car buf-name)))
(let ((buf (get-buffer buf-name)))
(and (buffer-local-value 'gptel-mode buf)
(not (eq (current-buffer) buf))))))))
("b" "Any buffer" "b"
:class transient-option :class transient-option
:prompt "Existing session: " :prompt "Output to buffer: "
:reader :reader
(lambda (prompt _ history) (lambda (prompt _ _history)
(completing-read (read-buffer prompt (buffer-name (other-buffer)) nil)))
prompt (mapcar #'buffer-name (buffer-list))
(lambda (buf) (and (buffer-local-value 'gptel-mode (get-buffer buf))
(not (equal (current-buffer) buf))))
t nil history)))
("k" "Kill-ring" "k")] ("k" "Kill-ring" "k")]
[:description gptel--refactor-or-rewrite [:description gptel--refactor-or-rewrite
:if use-region-p :if use-region-p
@ -394,6 +394,7 @@ will get progressively longer!"
(backend-name (gptel-backend-name gptel-backend)) (backend-name (gptel-backend-name gptel-backend))
(buffer) (position) (buffer) (position)
(callback) (gptel-buffer-name) (callback) (gptel-buffer-name)
;; Input redirection: grab prompt from elsewhere?
(prompt (prompt
(cond (cond
((member "p" args) ((member "p" args)
@ -409,6 +410,8 @@ will get progressively longer!"
(if current-prefix-arg (if current-prefix-arg
(read-from-kill-ring "Prompt from kill-ring: ") (read-from-kill-ring "Prompt from kill-ring: ")
(current-kill 0)))))) (current-kill 0))))))
;; Output redirection: Send response elsewhere?
(cond (cond
((member "m" args) ((member "m" args)
(setq stream nil) (setq stream nil)
@ -428,45 +431,13 @@ will get progressively longer!"
backend-name backend-name
(truncate-string-to-width resp 30)))))) (truncate-string-to-width resp 30))))))
((setq gptel-buffer-name ((setq gptel-buffer-name
(cl-some (lambda (s) (and (string-prefix-p "n" s) (cl-some (lambda (s) (and (string-prefix-p "g" s)
(substring s 1))) (substring s 1)))
args)) args))
(setq buffer
(gptel gptel-buffer-name
(condition-case nil
(gptel--get-api-key)
((error user-error)
(setq gptel-api-key
(read-passwd
(format "%s API key: "
(gptel-backend-name
gptel-backend))))))
(or prompt
(if (use-region-p)
(buffer-substring-no-properties (region-beginning)
(region-end))
(buffer-substring-no-properties
(save-excursion
(text-property-search-backward
'gptel 'response
(when (get-char-property (max (point-min) (1- (point)))
'gptel)
t))
(point))
(gptel--at-word-end (point)))))))
(with-current-buffer buffer
(setq gptel-backend backend)
(setq gptel-model model)
(gptel--update-status " Waiting..." 'warning)
(setq position (point)))
(setq output-to-other-buffer-p t))
((setq gptel-buffer-name
(cl-some (lambda (s) (and (string-prefix-p "e" s)
(substring s 1)))
args))
(setq buffer (get-buffer gptel-buffer-name))
(setq output-to-other-buffer-p t) (setq output-to-other-buffer-p t)
(let ((reduced-prompt (let ((reduced-prompt ;For inserting into the gptel buffer as
;context, not the prompt used for the
;request itself
(or prompt (or prompt
(if (use-region-p) (if (use-region-p)
(buffer-substring-no-properties (region-beginning) (buffer-substring-no-properties (region-beginning)
@ -480,18 +451,51 @@ will get progressively longer!"
t)) t))
(point)) (point))
(gptel--at-word-end (point))))))) (gptel--at-word-end (point)))))))
(with-current-buffer buffer (cond
(goto-char (point-max)) ((buffer-live-p (get-buffer gptel-buffer-name))
(if (or buffer-read-only ;; Insert into existing gptel session
(get-char-property (point) 'read-only)) (progn
(setq prompt reduced-prompt) (setq buffer (get-buffer gptel-buffer-name))
(insert reduced-prompt)) (with-current-buffer buffer
(setq position (point)) (goto-char (point-max))
(when gptel-mode (unless (or buffer-read-only
(gptel--update-status " Waiting..." 'warning)))))) (get-char-property (point) 'read-only))
(insert reduced-prompt))
(setq position (point))
(when gptel-mode
(gptel--update-status " Waiting..." 'warning)))))
;; Insert into new gptel session
(t (setq buffer
(gptel gptel-buffer-name
(condition-case nil
(gptel--get-api-key)
((error user-error)
(setq gptel-api-key
(read-passwd
(format "%s API key: "
(gptel-backend-name
gptel-backend))))))
reduced-prompt))
;; Set backend and model in new session from current buffer
(with-current-buffer buffer
(setq gptel-backend backend)
(setq gptel-model model)
(gptel--update-status " Waiting..." 'warning)
(setq position (point)))))))
((setq gptel-buffer-name
(cl-some (lambda (s) (and (string-prefix-p "b" s)
(substring s 1)))
args))
(setq output-to-other-buffer-p t)
(setq buffer (get-buffer-create gptel-buffer-name))
(with-current-buffer buffer (setq position (point)))))
;; Create prompt, unless doing input-redirection above
(unless prompt
(setq prompt (gptel--create-prompt (gptel--at-word-end (point)))))
(when in-place (when in-place
(setq prompt (gptel--create-prompt (point))) ;; Kill the latest prompt
(let ((beg (let ((beg
(if (use-region-p) (if (use-region-p)
(region-beginning) (region-beginning)

View file

@ -755,8 +755,10 @@ RESPONSE is nil if there was no response or an error.
The INFO plist has (at least) the following keys: The INFO plist has (at least) the following keys:
:prompt - The full prompt that was sent with the request :prompt - The full prompt that was sent with the request
:position - marker at the point the request was sent. :position - marker at the point the request was sent, unless
:buffer - The buffer current when the request was sent. POSITION is specified.
:buffer - The buffer current when the request was sent,
unless BUFFER is specified.
:status - Short string describing the result of the request :status - Short string describing the result of the request
Example of a callback that messages the user with the response Example of a callback that messages the user with the response
@ -780,12 +782,15 @@ Or, for just the response:
If CALLBACK is omitted, the response is inserted at the point the If CALLBACK is omitted, the response is inserted at the point the
request was sent. request was sent.
BUFFER is the buffer the request belongs to. If omitted the BUFFER and POSITION are the buffer and position (integer or
current buffer is recorded. marker) at which the response is inserted. If a CALLBACK is
specified, no response is inserted and these arguments are
ignored, but they are still available in the INFO plist passed
to CALLBACK for you to use.
POSITION is a buffer position (integer or marker). If omitted, BUFFER defaults to the current buffer, and POSITION to the value
the value of (point) or (region-end) is recorded, depending on of (point) or (region-end), depending on whether the region is
whether the region is active. active.
CONTEXT is any additional data needed for the callback to run. It CONTEXT is any additional data needed for the callback to run. It
is included in the INFO argument to the callback. is included in the INFO argument to the callback.