emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/gptel 5d093f2135 10/20: gptel-context: Support for files a


From: ELPA Syncer
Subject: [nongnu] elpa/gptel 5d093f2135 10/20: gptel-context: Support for files as context
Date: Sun, 23 Jun 2024 00:59:53 -0400 (EDT)

branch: elpa/gptel
commit 5d093f2135dcf69c8493183902e5b932b0aa2c2b
Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Commit: karthink <karthikchikmagalur@gmail.com>

    gptel-context: Support for files as context
    
    * gptel-context.el (gptel-context-confirm,
    gptel-context--buffer-setup, gptel-context--string-default,
    gptel-context--insert-file-string, gptel-context--collect,
    gptel-context-add, gptel-context-add-file): Support for adding
    files to gptel's context.
    
    * gptel-transient.el (gptel-menu, gptel--suffix-context-buffer,
    gptel-context--add-region, gptel-context--add-buffer,
    gptel-context--add-file): menu changes for adding files to
    context.
    
    * gptel.el (gptel--create-prompt, gptel-request,
    gptel-context--overlay-alist, gptel-context--alist):
    Rename `gptel-context--overlay-alist` to `gptel-context--alist`
    since it is no longer a list of overlays.
    
    * gptel-transient.el (gptel--suffix-context-buffer): Adjust for
    renaming.
    
    * gptel-context.el (gptel-context-confirm,
    gptel-context--buffer-setup, gptel-context--string-default,
    gptel-context--collect, gptel-context--make-overlay,
    gptel-context-add-file, gptel-context-string-function): Adjust for
    renaming, and improve docstrings involving the use of
    `gptel-context--alist`.
    
    * gptel-context.el (gptel-context-confirm, gptel-context-add-file,
    gptel-context-add, gptel-add-file): Alias `gptel-context-add-file`
    to `gptel-add-file`.  Add support for dired buffers to `gptel-add`.
    
    * gptel-transient.el (gptel--suffix-context-add-file): Adjust for
    renaming.
    
    * gptel-context.el (gptel-context-add-file): Handle missing files
    gracefully.
    
    Fix invalid format string in gptel-context-add-file
---
 gptel-context.el   | 145 +++++++++++++++++++++++++++++++++++------------------
 gptel-transient.el |  45 ++++++++++++-----
 gptel.el           |  15 ++++--
 3 files changed, 139 insertions(+), 66 deletions(-)

diff --git a/gptel-context.el b/gptel-context.el
index 3625022dc9..8f5740df1d 100644
--- a/gptel-context.el
+++ b/gptel-context.el
@@ -32,6 +32,7 @@
 (require 'cl-lib)
 
 (declare-function gptel-menu "gptel-transient")
+(declare-function dired-get-marked-files "dired")
 
 (defface gptel-context-highlight-face
   '((t :inherit header-line))
@@ -54,14 +55,16 @@ This is used in gptel context buffers."
 (defcustom gptel-context-string-function #'gptel-context--string-default
   "Function to format the context string sent with the gptel request.
 
-This function receives one argument, an alist of context overlays
+This function receives one argument, an alist of contexts
 organized by buffer.  It should return a string containing the
 formatted context and any additional comments you wish to
 include.
 
-The alist of context overlays is structured as follows:
+Each gptel \"context\" is either a file path or an overlay in a
+buffer. The alist of contexts is structured as follows:
 
  ((buffer1 . (overlay1 overlay2)
+  (\"path/to/file\")
   (buffer2 . (overlay3 overlay4 overlay5))))
 
 Each overlay covers a buffer region containing the
@@ -76,21 +79,17 @@ context chunk.  This is accessible as, for example:
 (defun gptel-context-add (&optional arg)
   "Add context to gptel in a DWIM fashion.
 
-When called without prefix argument ARG:
-
 - If a region is selected, add the selected region to the
-context.
-
-- If there is a gptel context under point, remove it.
+  context.  If there is already a gptel context at point, remove it
+  instead.
 
-- Otherwise add the current buffer to the context.
+- If in Dired, add marked files or file at point to the context.
+  With negative prefix ARG, remove them from the context instead.
 
-Otherwise:
+- Otherwise add the current buffer to the context.  With positive
+  prefix ARG, prompt for a buffer name and add it to the context.
 
-- When ARG is positive, prompt for a buffer name and add it to
-  the context.
-
-- When ARG is negative, removes all gptel contexts from the
+- With negative prefix ARG, remove all gptel contexts from the
   current buffer."
   (interactive "P")
   (cond
@@ -101,6 +100,12 @@ Otherwise:
                                   (region-end))
     (deactivate-mark)
     (message "Current region added as context."))
+   ;; If in dired
+   ((derived-mode-p 'dired-mode)
+    (mapc (if (and arg (< (prefix-numeric-value arg) 0))
+              #'gptel-context-remove
+              #'gptel-context-add-file)
+          (dired-get-marked-files)))
    ;; No region is selected, and ARG is positive.
    ((and arg (> (prefix-numeric-value arg) 0))
     (let ((buffer-name (read-buffer "Choose buffer to add as context: " nil 
t)))
@@ -129,27 +134,46 @@ Otherwise:
       (gptel-context--add-region (current-buffer) (point-min) (point-max) t)
       (message "Current buffer added as context.")))))
 
-;;;###autoload (autoload 'gptel-add "gptel-context" "Add or remove context to 
gptel's requests." t)
+;;;###autoload (autoload 'gptel-add "gptel-context" "Add/remove regions or 
buffers from gptel's context." t)
 (defalias 'gptel-add #'gptel-context-add)
   
+(defun gptel-context-add-file (path)
+  "Add the file at PATH to the gptel context.
+
+PATH should be readable as text."
+  (interactive "fChoose file to add to context: ")
+  (condition-case nil
+      ;; Check if file is binary
+      (if (with-temp-buffer ;; (create-file-buffer file)
+            (insert-file-contents path nil 1 512 'replace)
+            (eq buffer-file-coding-system 'no-conversion))
+          (message "Ignoring unsupported binary file \"%s\"." path)
+        (cl-pushnew (list path) gptel-context--alist :test #'equal)
+        (message "File \"%s\" added to context." path)
+        path)
+    (file-missing (message "File \"%s\" does not exist." path))))
+
+;;;###autoload (autoload 'gptel-add-file "gptel-context" "Add files to gptel's 
context." t)
+(defalias 'gptel-add-file #'gptel-context-add-file)
+
 (defun gptel-context-remove (&optional context)
   "Remove the CONTEXT overlay from the contexts list.
 If CONTEXT is nil, removes the context at point.
 If selection is active, removes all contexts within selection."
-  (interactive)
   (cond
    ((overlayp context)
     (delete-overlay context))
+   ((stringp context)                   ;file
+    (setf (alist-get context gptel-context--alist nil 'remove #'equal) 
+          nil))
    ((region-active-p)
-    (let ((contexts (gptel-context--in-region (current-buffer)
-                                              (region-beginning)
-                                              (region-end))))
-      (when contexts
-        (cl-loop for ctx in contexts do (delete-overlay ctx)))))
+    (when-let ((contexts (gptel-context--in-region (current-buffer)
+                                                   (region-beginning)
+                                                   (region-end))))
+      (cl-loop for ctx in contexts do (delete-overlay ctx))))
    (t
-    (let ((ctx (gptel-context--at-point)))
-      (when ctx
-        (delete-overlay ctx))))))
+    (when-let ((ctx (gptel-context--at-point)))
+      (delete-overlay ctx)))))
 
 (defun gptel-context--make-overlay (start end &optional advance)
   "Highlight the region from START to END."
@@ -158,7 +182,7 @@ If selection is active, removes all contexts within 
selection."
     (overlay-put overlay 'face 'gptel-context-highlight-face)
     (overlay-put overlay 'gptel-context t)
     (push overlay (alist-get (current-buffer)
-                             gptel-context--overlay-alist))
+                             gptel-context--alist))
     overlay))
 
 ;;;###autoload
@@ -203,15 +227,15 @@ START and END signify the region delimiters."
 (defun gptel-context--collect ()
   "Get the list of all active context overlays."
   ;; Get only the non-degenerate overlays, collect them, and update the 
overlays variable.
-  (let ((overlay-alist
-         (cl-loop for (buf . ovs) in gptel-context--overlay-alist
-                  when (buffer-live-p buf)
-                  for updated-ovs = (cl-loop for ov in ovs
-                                             when (overlay-start ov)
-                                             collect ov)
-                  when updated-ovs
-                  collect (cons buf updated-ovs))))
-    (setq gptel-context--overlay-alist overlay-alist)))
+  (setq gptel-context--alist
+        (cl-loop for (buf . ovs) in gptel-context--alist
+                 if (buffer-live-p buf)
+                   if (cl-loop for ov in ovs when (overlay-start ov) collect 
ov)
+                   collect (cons buf it) into elements
+                   end
+                 else if (file-exists-p buf)
+                   collect (list buf) into elements
+                 finally return elements)))
 
 (defun gptel-context--insert-buffer-string (buffer contexts)
   "Insert at point a context string from all CONTEXTS in BUFFER."
@@ -252,16 +276,26 @@ START and END signify the region delimiters."
         (insert "\n..."))
       (insert "\n```")))
 
+(defun gptel-context--insert-file-string (path)
+  "Insert at point the contents of the file at PATH as context."
+  (insert (format "In file `%s`:" (file-name-nondirectory path))
+          "\n\n```\n")
+  (insert-file-contents path)
+  (goto-char (point-max))
+  (insert "\n```\n"))
+
 (defun gptel-context--string-default (context-alist)
   "Format the aggregated gptel context as annotated markdown fragments.
 
 Returns a string.  CONTEXT-ALIST is a structure containing
-context overlays, see `gptel-context--overlay-alist'."
+context overlays, see `gptel-context--alist'."
   (with-temp-buffer
     (insert "Request context:\n\n")
     (cl-loop for (buf . ovs) in context-alist
+             if (bufferp buf)
              do (gptel-context--insert-buffer-string buf ovs)
-             (insert "\n\n")
+             else do (gptel-context--insert-file-string buf)
+             do (insert "\n\n")
              finally return (buffer-string))))
 
 ;;;###autoload
@@ -301,27 +335,42 @@ context overlays, see `gptel-context--overlay-alist'."
              (propertize "C-c C-k" 'face 'help-key-binding) ": cancel, "
              (propertize "q" 'face 'help-key-binding) ": quit"))
       (save-excursion
-        (let ((contexts gptel-context--overlay-alist))
+        (let ((contexts gptel-context--alist))
           (if (length> contexts 0)
               (let (beg ov l1 l2)
                 (pcase-dolist (`(,buf . ,ovs) contexts)
-                  (dolist (source-ov ovs)
-                    (with-current-buffer buf
-                      (setq l1 (line-number-at-pos (overlay-start source-ov))
-                            l2 (line-number-at-pos (overlay-end source-ov))))
+                  (if (bufferp buf)
+                      ;; It's a buffer with some overlay(s)
+                      (dolist (source-ov ovs)
+                        (with-current-buffer buf
+                          (setq l1 (line-number-at-pos (overlay-start 
source-ov))
+                                l2 (line-number-at-pos (overlay-end 
source-ov))))
+                        (insert (make-separator-line)
+                                (propertize (format "In buffer %s (lines 
%d-%d):\n\n"
+                                                    (buffer-name buf) l1 l2)
+                                            'face 'bold))
+                        (setq beg (point))
+                        (insert-buffer-substring
+                         buf (overlay-start source-ov) (overlay-end source-ov))
+                        (setq ov (make-overlay beg (point)))
+                        (overlay-put ov 'gptel-context source-ov)
+                        (overlay-put ov 'gptel-overlay t)
+                        (overlay-put ov 'evaporate t)
+                        (insert "\n"))
+                    ;; BUF is a file path, not a buffer
                     (insert (make-separator-line)
-                            (propertize (format "In buffer %s (lines 
%d-%d):\n\n"
-                                                (buffer-name buf) l1 l2)
+                            (propertize (format "In file %s:\n\n" 
(file-name-nondirectory buf))
                                         'face 'bold))
                     (setq beg (point))
-                    (insert-buffer-substring
-                     buf (overlay-start source-ov) (overlay-end source-ov))
+                    (insert-file-contents buf)
+                    (goto-char (point-max))
                     (setq ov (make-overlay beg (point)))
-                    (overlay-put ov 'gptel-context source-ov)
+                    (overlay-put ov 'gptel-context buf)
                     (overlay-put ov 'gptel-overlay t)
-                    (insert "\n")))
+                    (overlay-put ov 'evaporate t)
+                    (insert "\n\n")))
                 (goto-char (point-min)))
-            (insert "There are no active contexts in any buffer.")))))
+            (insert "There are no active gptel contexts.")))))
     (display-buffer (current-buffer)
                     `((display-buffer-reuse-window
                        display-buffer-reuse-mode-window
@@ -418,7 +467,7 @@ If non-nil, indicates backward movement.")
   "Confirm pending operations and return to gptel's menu."
   (interactive)
   ;; Delete all the context overlays that have been marked for deletion.
-  (mapc #'delete-overlay
+  (mapc #'gptel-context-remove
         (delq nil (mapcar (lambda (ov)
                             (and
                              (overlay-get ov 'gptel-context-deletion-mark)
diff --git a/gptel-transient.el b/gptel-transient.el
index 195cd737f2..5d24b62cad 100644
--- a/gptel-transient.el
+++ b/gptel-transient.el
@@ -290,11 +290,13 @@ Also format its value in the Transient menu."
     "Instructions"
     ("s" "Set system message" gptel-system-prompt :transient t)
     (gptel--infix-add-directive)]
-   [""
+   [:pad-keys t
+    ""
     "Context"
     (gptel--infix-use-context)
     (gptel--suffix-context-add-region)
     (gptel--suffix-context-add-buffer)
+    (gptel--suffix-context-add-file)
     (gptel--suffix-context-buffer)]]
   [["Request Parameters"
     :pad-keys t
@@ -593,7 +595,7 @@ querying the LLM."
   :set-value #'gptel--set-with-scope
   :display-if-true "Yes"
   :display-if-false "No"
-  :key "-r")
+  :key "-d")
 
 ;; ** Infix for the refactor/rewrite system message
 
@@ -926,16 +928,21 @@ When LOCAL is non-nil, set the system message only in the 
current buffer."
   "Display all contexts from all buffers & files."
   :transient 'transient--do-exit
   :key "C"
-  :if (lambda () gptel-context--overlay-alist)
+  :if (lambda () gptel-context--alist)
   :description
   (lambda ()
-    (let* ((contexts (and gptel-context--overlay-alist 
(gptel-context--collect)))
-           (buffer-count (length contexts))
-           (ov-count (if (> buffer-count 0)
-                         (cl-loop for (_ . ovs) in contexts
-                                  sum (length ovs))
-                       0)))
-      (concat "Inspect context "
+    (pcase-let*
+        ((contexts (and gptel-context--alist (gptel-context--collect)))
+         (buffer-count (length contexts))
+         (`(,file-count ,ov-count)
+          (if (> buffer-count 0)
+              (cl-loop for (buf-file . ovs) in contexts
+                       if (bufferp buf-file)
+                       sum (length ovs) into ov-count
+                       else count (stringp buf-file) into file-count
+                       finally return (list file-count ov-count))
+            (list 0 0))))
+      (concat "Inspect "
               (format
                (propertize "(%s)" 'face 'transient-delimiter)
                (propertize
@@ -961,7 +968,7 @@ When LOCAL is non-nil, set the system message only in the 
current buffer."
 (transient-define-suffix gptel--suffix-context-add-region ()
   "Add current region to gptel's context."
   :transient 'transient--do-stay
-  :key "cr"
+  :key "-r"
   :if (lambda () (or (use-region-p)
                 (and (fboundp 'gptel-context--at-point)
                      (gptel-context--at-point))))
@@ -978,10 +985,22 @@ When LOCAL is non-nil, set the system message only in the 
current buffer."
 (transient-define-suffix gptel--suffix-context-add-buffer ()
   "Add a buffer to gptel's context."
   :transient 'transient--do-stay
-  :key "cb"
+  :key "-b"
   :description "Add a buffer to context"
   (interactive)
-  (gptel-add '(4)))
+  (gptel-add '(4))
+  (transient-setup))
+
+(declare-function gptel-add-file "gptel-context")
+
+(transient-define-suffix gptel--suffix-context-add-file ()
+  "Add a file to gptel's context."
+  :transient 'transient--do-stay
+  :key "-f"
+  :description "Add a file to context"
+  (interactive)
+  (call-interactively #'gptel-add-file)
+  (transient-setup))
 
 ;; ** Suffixes for rewriting/refactoring
 
diff --git a/gptel.el b/gptel.el
index 3820619704..dec2a44ed3 100644
--- a/gptel.el
+++ b/gptel.el
@@ -539,8 +539,13 @@ Currently supported options are:
 
 (defvar-local gptel--old-header-line nil)
 
-(defvar gptel-context--overlay-alist nil
-  "Alist of buffers and their corresponding context chunks.")
+(defvar gptel-context--alist nil
+  "List of gptel's context sources.
+
+Each entry is of the form
+ (buffer . (overlay1 overlay2 ...))
+or
+ (\"path/to/file\").")
 
 
 ;; Utility functions
@@ -931,7 +936,7 @@ Model parameters can be let-bound around calls to this 
function."
   (declare (indent 1))
   (let* ((gptel--system-message
           ;Add context chunks to system message if required
-          (if (and gptel-context--overlay-alist
+          (if (and gptel-context--alist
                    (eq gptel-use-context 'system))
               (gptel-context--wrap system)
             system))
@@ -1082,7 +1087,7 @@ recent exchanges.
 If the region is active limit the prompt to the region contents
 instead.
 
-If `gptel-context--overlay-alist' is non-nil and the additional
+If `gptel-context--alist' is non-nil and the additional
 context needs to be included with the user prompt, add it.
 
 If PROMPT-END (a marker) is provided, end the prompt contents
@@ -1105,7 +1110,7 @@ there."
                   (gptel--parse-buffer gptel-backend max-entries)))))
         ;; Inject context chunks into the last user prompt if required
         ;; NOTE: prompts is modified in place
-        (when (and gptel-context--overlay-alist
+        (when (and gptel-context--alist
                    (eq gptel-use-context 'user)
                    (> (length prompts) 0))
           (gptel--wrap-user-prompt gptel-backend prompts))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]