gptel-transient: Add refactor transient

* gptel-transient.el: Add `gptel-rewrite-menu' (accessible via the
main `gptel-menu') to rewrite/refactor the selected region,
including the choice to Ediff it linewise/wordwise afterwards.
This commit is contained in:
Karthik Chikmagalur 2023-04-09 04:12:13 -07:00
parent c6abda6f0f
commit 937c754e57

View file

@ -69,6 +69,18 @@ Or is it the other way around?"
(gptel--suffix-send-existing)
(gptel--suffix-send-new)
("RET" "Send prompt" gptel-send)]])
[:description gptel--refactor-or-rewrite
:if use-region-p
("r"
;;FIXME: Transient complains if I use `gptel--refactor-or-rewrite' here. It
;;reads this function as a suffix instead of a function that returns the
;;description.
(lambda () (if (derived-mode-p 'prog-mode)
"Refactor" "Rewrite"))
gptel-rewrite-menu)]
["Send" (gptel--suffix-send)]])
;; ** Prefix for setting the system prompt.
(transient-define-prefix gptel-system-prompt ()
"Change the system prompt to send ChatGPT.
@ -108,6 +120,27 @@ Customize `gptel-directives' for task-specific prompts."
(alist-get 'chat gptel-directives)))
:transient t)])
(transient-define-prefix gptel-rewrite-menu ()
"Rewrite or refactor text region using ChatGPT."
[:description
(lambda ()
(format "Directive: %s"
(truncate-string-to-width
(or gptel--rewrite-message (gptel--rewrite-message))
(max (- (window-width) 14) 20) nil nil t)))
(gptel--infix-rewrite-prompt)]
[[:description "Diff Options"
("-w" "Wordwise diff" "-w")]
[:description
(lambda () (if (derived-mode-p 'prog-mode)
"Refactor" "Rewrite"))
(gptel--suffix-rewrite)
(gptel--suffix-rewrite-and-replace)
(gptel--suffix-rewrite-and-ediff)]]
(interactive)
(unless gptel--rewrite-message
(setq gptel--rewrite-message (gptel--rewrite-message)))
(transient-setup 'gptel-rewrite-menu))
;; TODO: Switch to dynamic Transient menus (below) once there's a new Transient release
;; (transient-define-prefix gptel-system-prompt ()
;; "Change the system prompt to send ChatGPT."
@ -216,6 +249,19 @@ will get progressively longer!"
(buf (call-interactively #'gptel)))
(and (bufferp buf)
(with-current-buffer buf (gptel-send)))))
(transient-define-infix gptel--infix-rewrite-prompt ()
"Chat directive (system message) to use for rewriting or refactoring."
:description (lambda () (if (derived-mode-p 'prog-mode)
"Set directives for refactor"
"Set directives for rewrite"))
:format "%k %d"
:class 'transient-lisp-variable
:variable 'gptel--rewrite-message
:key "h"
:prompt "Set directive for rewrite: "
:reader (lambda (prompt _ history)
(read-string
prompt (gptel--rewrite-message) history)))
(transient-define-suffix gptel--suffix-system-message ()
"Set directives sent to ChatGPT."
@ -258,5 +304,67 @@ will get progressively longer!"
(body-function . ,#'select-window)))
(call-interactively #'gptel-menu))))))
(transient-define-suffix gptel--suffix-rewrite ()
"Rewrite or refactor region contents."
:key "r"
:description #'gptel--refactor-or-rewrite
(interactive)
(let* ((prompt (buffer-substring-no-properties
(region-beginning) (region-end)))
(stream gptel-stream)
(gptel--system-message gptel--rewrite-message))
(gptel-request prompt :stream stream)))
(transient-define-suffix gptel--suffix-rewrite-and-replace ()
"Refactor region contents and replace it."
:key "R"
:description (lambda () (concat (gptel--refactor-or-rewrite) " in place"))
(interactive)
(let* ((prompt (buffer-substring-no-properties
(region-beginning) (region-end)))
(stream gptel-stream)
(gptel--system-message gptel--rewrite-message))
(kill-region (region-beginning) (region-end))
(message "Original text saved to kill-ring.")
(gptel-request prompt :stream stream :in-place t)))
(transient-define-suffix gptel--suffix-rewrite-and-ediff (args)
"Refactoring or rewrite region contents and run Ediff."
:key "E"
:description (lambda () (concat (gptel--refactor-or-rewrite) " and Ediff"))
(interactive (list (transient-args transient-current-command)))
(let* ((prompt (buffer-substring-no-properties
(region-beginning) (region-end)))
(gptel--system-message gptel--rewrite-message))
(message "Waiting for response... ")
(gptel-request
prompt
:context (cons (region-beginning) (region-end))
:callback
(lambda (response info)
(if (not response)
(message "ChatGPT response error: %s" (plist-get info :status))
(let* ((gptel-buffer (plist-get info :buffer))
(gptel-bounds (plist-get info :context))
(buffer-mode
(buffer-local-value 'major-mode gptel-buffer)))
(pcase-let ((`(,new-buf ,new-beg ,new-end)
(with-current-buffer (get-buffer-create "*gptel-rewrite-Region.B-*")
(erase-buffer)
(funcall buffer-mode)
(insert response)
(goto-char (point-min))
(list (current-buffer) (point-min) (point-max)))))
(require 'ediff)
(apply
#'ediff-regions-internal
(get-buffer (ediff-make-cloned-buffer gptel-buffer "-Region.A-"))
(car gptel-bounds) (cdr gptel-bounds)
new-buf new-beg new-end
nil
(if (transient-arg-value "-w" args)
(list 'ediff-regions-wordwise 'word-wise nil)
(list 'ediff-regions-linewise nil nil))))))))))
(provide 'gptel-transient)
;;; gptel-transient.el ends here