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