branch: elpa/gptel
commit 4ae9c1b24c95c4de316b92d696e8677624bb2486
Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>

    gptel-rewrite: Improve rewrite UI and feature
    
    * gptel-rewrite.el (gptel--rewrite-message,
    gptel-rewrite-directive, gptel-rewrite-directives-hook,
    gptel--rewrite-expand-prompt, gptel-rewrite-menu,
    gptel--infix-rewrite-prompt, gptel--suffix-rewrite): Use the new
    dynamic gptel directives feature to improve how rewriting works.
    
    - `gptel--rewrite-directive' generates a mode-specific rewrite
    message that is used as the system message.  This can be
    overridden via functions in `gptel-rewrite-directives-hook'.
    - The actual instructions and code to be rewritten are fed to
    `gptel-request' as regular user prompts via a template.
    - The UI around adding rewrite instructions has been improved, it
    now shows a preview of the active system message in the
    minibuffer.
---
 gptel-rewrite.el | 150 ++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 121 insertions(+), 29 deletions(-)

diff --git a/gptel-rewrite.el b/gptel-rewrite.el
index 31e8dc4e0f..5cb86c03f0 100644
--- a/gptel-rewrite.el
+++ b/gptel-rewrite.el
@@ -33,7 +33,7 @@
 
 ;; * User options
 
-(defcustom gptel-rewrite-directives-hook (list #'gptel--rewrite-message)
+(defcustom gptel-rewrite-directives-hook nil
   "Hook run to generate gptel's default rewrite directives.
 
 Each function in this hook is called with no arguments until one
@@ -42,7 +42,7 @@ rewrite/refactor instruction.
 
 Use this hook to tailor context-specific refactoring directives.
 For example, you can specialize the default refactor directive
-for a particular major-mode."
+for a particular major-mode or project."
   :group 'gptel
   :type 'hook)
 
@@ -90,7 +90,54 @@ automatically do one of these things instead."
 (defvar-local gptel--rewrite-overlays nil
   "List of active rewrite overlays in the buffer.")
 
-(defvar-local gptel--rewrite-message nil)
+(defvar-local gptel--rewrite-message nil
+  "Request-specific instructions for a gptel-rewrite action.")
+
+;; ;; NOTE: Should we expose this option?  `gptel-rewrite-directives-hook'
+;; ;; already functions as a robust way to customize the system message for
+;; ;; rewriting.
+;;
+;; (defcustom gptel-rewrite-directive
+;;   #'gptel--rewrite-directive
+;;   "A gptel template (system message, a string), or function that
+;; returns a system message intended for a rewrite action."
+;;   :group 'gptel
+;;   :type '(choice
+;;           (string :tag "System message")
+;;           (function :tag "Function that returns system message")))
+
+(defun gptel--rewrite-directive ()
+  "General gptel directive when rewriting or refactoring.
+
+This supplies the general instructions to the LLM that are not
+specific to any particular required change.
+
+The returned string is interpreted as the system message for the
+rewrite request.  To substitute your own, add to
+`gptel-rewrite-directives-hook', which see."
+  (or (save-mark-and-excursion
+        (run-hook-with-args-until-success
+         'gptel-rewrite-directives-hook))
+      (let* ((lang (downcase (gptel--strip-mode-suffix major-mode)))
+             (article (if (and lang (not (string-empty-p lang))
+                               (memq (aref lang 0) '(?a ?e ?i ?o ?u)))
+                          "an" "a")))
+        (if (derived-mode-p 'prog-mode)
+            (format (concat "You are %s %s programmer.  "
+                            "Follow my instructions and refactor %s code I 
provide.\n"
+                            "- Generate ONLY %s code as output, without "
+                            "any explanation or markdown code fences.\n"
+                            "- Generate code in full, do not abbreviate or 
omit code.\n"
+                            "- Do not ask for further clarification, and make "
+                            "any assumptions you need to follow instructions.")
+                    article lang lang lang)
+          (concat
+           (if (string-empty-p lang)
+               "You are an editor."
+             (format "You are %s %s editor." article lang))
+           "  Follow my instructions and improve or rewrite the text I 
provide."
+           "  Generate ONLY the replacement text,"
+           " without any explanation or markdown code fences.")))))
 
 ;; * Helper functions
 
@@ -107,12 +154,45 @@ Or is it the other way around?"
   (if (derived-mode-p 'prog-mode)
       "Refactor" "Rewrite"))
 
-(defun gptel--rewrite-message ()
-  "Set a generic refactor/rewrite message for the buffer."
-  (if (derived-mode-p 'prog-mode)
-      (format "You are a %s programmer. Generate only code, no explanation, no 
code fences. Refactor the following code."
-              (gptel--strip-mode-suffix major-mode))
-    (format "You are a prose editor. Rewrite the following text to be more 
professional.")))
+(defun gptel--rewrite-expand-prompt ()
+  "Show the active rewrite directive in the minibuffer.
+
+The directive, in this case just the system message, is shown in
+an overlay.  Repeated calls to this command will toggle its
+visibility state."
+  (interactive)
+  (unless (minibufferp)
+    (user-error "This command is intended to be used in the minibuffer."))
+  (let ((full (with-minibuffer-selected-window
+                (gptel--rewrite-directive)))
+        (update
+         (lambda (ov s)
+           (overlay-put
+            ov 'after-string
+            (and s (concat (propertize (concat "\n" s "\n") 'face 'shadow)
+                           (make-separator-line)))))))
+    (when full
+      (unless visual-line-mode (visual-line-mode 1))
+      (goto-char (minibuffer-prompt-end))
+      (pcase-let ((`(,prop . ,ov)
+                   (get-char-property-and-overlay
+                    (point-min) 'gptel)))
+        (unless ov
+          (setq ov (make-overlay
+                    (point-min) (minibuffer-prompt-end) nil t)))
+        (pcase prop
+          ('partial
+           (if (> (length full) (window-width))
+               (progn (overlay-put ov 'gptel 'full)
+                      (funcall update ov full))
+             (overlay-put ov 'gptel 'hide)
+             (funcall update ov nil)))
+          ('full (overlay-put ov 'gptel 'hide)
+                 (funcall update ov nil))
+          (_ (overlay-put ov 'gptel 'partial)
+             (funcall update ov (truncate-string-to-width
+                                 full (window-width) nil nil
+                                 'ellipsis))))))))
 
 (defun gptel--rewrite-key-help (callback)
   "Eldoc documentation function for gptel rewrite actions.
@@ -286,10 +366,9 @@ BUF is the buffer to modify, defaults to the overlay 
buffer."
   "Rewrite or refactor text region using an LLM."
   [:description
    (lambda ()
-     (format "Directive:  %s"
-             (truncate-string-to-width
-              gptel--rewrite-message
-              (max (- (window-width) 14) 20) nil nil t)))
+     (format "%s" (truncate-string-to-width
+                   gptel--rewrite-message
+                   (max (- (window-width) 14) 20) nil nil t)))
    (gptel--infix-rewrite-prompt)]
   ;; FIXME: We are requiring `gptel-transient' because of this suffix, perhaps
   ;; we can get find some way around that?
@@ -305,7 +384,9 @@ BUF is the buffer to modify, defaults to the overlay 
buffer."
    [:description gptel--refactor-or-rewrite
     :if use-region-p
     (gptel--suffix-rewrite)]
-   ["Dry Run" :if (lambda () (or gptel-log-level gptel-expert-commands))
+   ["Dry Run"
+    :if (lambda () (and (use-region-p)
+                   (or gptel-log-level gptel-expert-commands)))
     ("I" "Inspect query (Lisp)"
      (lambda ()
        "Inspect the query that will be sent as a lisp object."
@@ -335,9 +416,7 @@ BUF is the buffer to modify, defaults to the overlay 
buffer."
   (interactive)
   (unless gptel--rewrite-message
     (setq gptel--rewrite-message
-          (save-mark-and-excursion
-            (run-hook-with-args-until-success
-             'gptel-rewrite-directives-hook))))
+          (concat (gptel--refactor-or-rewrite) ": ")))
   (transient-setup 'gptel-rewrite-menu))
 
 ;; * Transient infixes for rewriting/refactoring
@@ -351,14 +430,22 @@ BUF is the buffer to modify, defaults to the overlay 
buffer."
   :class 'transient-lisp-variable
   :variable 'gptel--rewrite-message
   :key "d"
-  :prompt "Set directive for rewrite: "
+  :prompt (concat "Instructions ("
+                  (propertize "TAB" 'face 'help-key-binding) " to expand, "
+                  (propertize "M-n" 'face 'help-key-binding) "/"
+                  (propertize "M-p" 'face 'help-key-binding) " for 
next/previous): ")
   :reader (lambda (prompt _ history)
-            (read-string
-             prompt
-             (save-mark-and-excursion
-               (run-hook-with-args-until-success
-                'gptel-rewrite-directives-hook))
-             history)))
+            (let ((minibuffer-local-map
+                   (make-composed-keymap
+                    (define-keymap "TAB" #'gptel--rewrite-expand-prompt
+                      "<tab>" #'gptel--rewrite-expand-prompt)
+                    minibuffer-local-map)))
+              (minibuffer-with-setup-hook #'gptel--rewrite-expand-prompt
+                (read-string
+                 prompt
+                 (or gptel--rewrite-message
+                     (concat (gptel--refactor-or-rewrite) ": "))
+                 history)))))
 
 (transient-define-argument gptel--rewrite-infix-diff:-U ()
   :description "Context lines"
@@ -373,14 +460,19 @@ BUF is the buffer to modify, defaults to the overlay 
buffer."
   :key "r"
   :description #'gptel--refactor-or-rewrite
   (interactive (list gptel--rewrite-message))
-  (let* ((prompt (buffer-substring-no-properties
-                  (region-beginning) (region-end)))
-         (gptel--system-message (or rewrite-message gptel--rewrite-message))
-         ;; always send context with system message
-         (gptel-use-context (and gptel-use-context 'system)))
+  (let* ((nosystem (gptel--model-capable-p 'nosystem))
+         ;; Try to send context with system message
+         (gptel-use-context
+          (and gptel-use-context (if nosystem 'user 'system)))
+         (prompt (list (buffer-substring-no-properties (region-beginning) 
(region-end))
+                       "What is the required change?"
+                       (or rewrite-message gptel--rewrite-message))))
     (deactivate-mark)
+    (when nosystem (setcar prompt (concat (gptel--rewrite-directive)
+                                          "\n\n" (car prompt))))
     (gptel-request prompt
       :dry-run dry-run
+      :system #'gptel--rewrite-directive
       :context
       (let ((ov (make-overlay (region-beginning) (region-end))))
         (overlay-put ov 'category 'gptel)

Reply via email to