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

    gptel: Add presets UI in-buffer support
    
    * gptel.el (gptel-mode, gptel--transform-apply-preset)
    (gptel--fontify-preset-keyword, gptel-preset-capf)
    (gptel--prettify-preset): Add in-buffer UI indicators and
    affordances for presets in chat buffers.  This includes
    completion-at-point and fontification for `@preset` cookies.
    
    * gptel-transient.el (gptel--suffix-send): Add a CAPF for presets
    when reading the prompt from the minibuffer.
---
 gptel-transient.el | 13 +++++----
 gptel.el           | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+), 5 deletions(-)

diff --git a/gptel-transient.el b/gptel-transient.el
index 75481ce5e5..fbdfa83fc6 100644
--- a/gptel-transient.el
+++ b/gptel-transient.el
@@ -1386,11 +1386,14 @@ This sets the variable `gptel-include-tool-results', 
which see."
         (prompt
          (cond
           ((member "m" args)
-           (read-string
-            (format "Ask %s: " (gptel-backend-name gptel-backend))
-            (and (use-region-p)
-                 (buffer-substring-no-properties
-                  (region-beginning) (region-end)))))
+           (minibuffer-with-setup-hook
+               (lambda () (add-hook 'completion-at-point-functions
+                               #'gptel-preset-capf nil t))
+             (read-string
+              (format "Ask %s: " (gptel-backend-name gptel-backend))
+              (and (use-region-p)
+                   (buffer-substring-no-properties
+                    (region-beginning) (region-end))))))
           ((member "y" args)
            (unless (car-safe kill-ring)
              (user-error "`kill-ring' is empty!  Nothing to send"))
diff --git a/gptel.el b/gptel.el
index ac3c044f9c..b492099dbd 100644
--- a/gptel.el
+++ b/gptel.el
@@ -1402,6 +1402,7 @@ file."
           (gptel-mode -1)
           (user-error (format "`gptel-mode' is not supported in `%s'." 
major-mode)))
         (add-hook 'before-save-hook #'gptel--save-state nil t)
+        (gptel--prettify-preset)
         (when (derived-mode-p 'org-mode)
           ;; Work around bug in `org-fontify-extend-region'.
           (add-hook 'gptel-post-response-functions #'font-lock-flush nil t))
@@ -1495,6 +1496,7 @@ file."
                          (buttonize (gptel--model-name gptel-model)
                             (lambda (&rest _) (gptel-menu))))))))
     (remove-hook 'before-save-hook #'gptel--save-state t)
+    (gptel--prettify-preset)
     (if gptel-use-header-line
         (setq header-line-format gptel--old-header-line
               gptel--old-header-line nil)
@@ -3588,6 +3590,81 @@ symbol."
      (gptel--apply-preset ,(if (symbolp name) `',name name))
      ,@body))
 
+;;;; Presets in-buffer UI
+(defun gptel--transform-apply-preset (_fsm)
+  "Apply a gptel preset to the buffer depending on the prompt.
+
+If the user prompt begins with @foo, the preset foo is applied."
+  (text-property-search-backward 'gptel nil t)
+  (when (looking-at
+         (concat "[\n[:blank:]]*"
+                 (and-let* ((prefix (gptel-prompt-prefix-string))
+                            ((not (string-empty-p prefix))))
+                   (concat "\\(?:" (regexp-quote prefix) "\\)?"))))
+    (goto-char (match-end 0)))
+  (while-let (((looking-at "[[:blank:]\n]*@\\([^[:blank:]]+\\)\\s-+"))
+              (name (match-string 1))
+              (preset (or (gptel-get-preset (intern-soft name))
+                          (gptel-get-preset name))))
+    (delete-region (match-beginning 0) (match-end 0))
+    (gptel--apply-preset (cons name preset)
+                         (lambda (sym val)
+                           (set (make-local-variable sym) val)))
+    (message "Sending request with preset %s applied!"
+             (propertize name 'face 'mode-line-emphasis))))
+
+(defun gptel--fontify-preset-keyword (end)
+  "Font-lock function for preset indicators in chat buffers.
+
+Return preset fontification info for text up to END."
+  (when (re-search-forward 
"\\(?:^\\|[[:blank:]]+\\)\\(@\\([^[:blank:]\n]+\\)\\)"
+                           end t)
+    (= (match-beginning 1)
+       (save-excursion
+         (text-property-search-backward 'gptel nil t)
+         (save-match-data
+           (if (looking-at
+                (concat "[\n[:blank:]]*"
+                        (and-let* ((prefix (gptel-prompt-prefix-string))
+                                   ((not (string-empty-p prefix))))
+                          (concat "\\(?:" (regexp-quote prefix) "\\)?"
+                                  "[\n[:blank:]]*"))))
+               (match-end 0) (point)))))))
+
+(defun gptel-preset-capf ()
+  "Completion at point for gptel presets in `gptel-mode'.
+
+Add this to `completion-at-point-functions'."
+  (and gptel--known-presets
+       (save-excursion
+         (let ((num (- (skip-syntax-backward "w_"))))
+           (when (= (char-before) ?@)
+             (list (point) (+ (point) num)
+                   gptel--known-presets
+                   :exclusive 'no
+                   :annotation-function
+                   #'(lambda (c) (thread-first
+                              (intern-soft c)
+                              (assq gptel--known-presets) (cdr)
+                              (plist-get :description)))))))))
+
+(defun gptel--prettify-preset ()
+  "Get visual and completion help with presets in gptel buffers.
+
+Intended to be added to `gptel-mode-hook'."
+  (let ((keyword '((gptel--fontify-preset-keyword
+                    1 (when-let* ((comps (all-completions (match-string 2)
+                                          gptel--known-presets))
+                                  ((member (match-string 2) comps)))
+                       '(:box -1 :inherit secondary-selection))
+                    prepend))))
+    (cond
+     (gptel-mode
+      (font-lock-add-keywords nil keyword t)
+      (add-hook 'completion-at-point-functions #'gptel-preset-capf nil t))
+     (t (font-lock-remove-keywords nil keyword)
+        (remove-hook 'completion-at-point-functions #'gptel-preset-capf t)))))
+
 
 ;;; Response tweaking commands
 

Reply via email to