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

    gptel-org: Strip property drawers from the prompt
    
    * gptel-org.el: (gptel-org--create-prompt,
    gptel-org-ignore-elements, gptel-org--element-end,
    gptel-org--strip-elements): Now that prompts in Org mode are
    always generated from a temp buffer, we are free to modify the
    buffer as we wish.  Add user option `gptel-org-ignore-elements' to
    remove specified Org elements from the prompt, and strip property
    drawers by default.  (#408, #503)
    
    This behavior can be reverted by setting this
    user option to nil, or by selecting a region of text.  (No
    filtering is done when selecting a region.)
    
    Eventually prompt filtering will be user-customizable via a hook.
    
    * README.org (Additional Configuration): Mention
    `gptel-org-ignore-elements'.
---
 README.org   |  1 +
 gptel-org.el | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/README.org b/README.org
index 0928457a62..663d550b7c 100644
--- a/README.org
+++ b/README.org
@@ -1340,6 +1340,7 @@ Other Emacs clients for LLMs prescribe the format of the 
interaction (a comint s
 | *Org mode UI options*         |                                              
         |
 
|-------------------------------+-------------------------------------------------------|
 | =gptel-org-branching-context= | Make each outline path a separate 
conversation branch |
+| =gptel-org-ignore-elements=   | Ignore parts of the buffer when sending a 
query       |
 
|-------------------------------+-------------------------------------------------------|
 
 
|---------------------------------+-------------------------------------------------------------|
diff --git a/gptel-org.el b/gptel-org.el
index 0ff0b70877..fc20baf6e5 100644
--- a/gptel-org.el
+++ b/gptel-org.el
@@ -89,10 +89,15 @@ of Org."
           (nreverse acc)))))
   (if (fboundp 'org-element-begin)
       (progn (declare-function org-element-begin "org-element")
-             (defalias 'gptel-org--element-begin 'org-element-begin))
+             (declare-function org-element-end "org-element")
+             (defalias 'gptel-org--element-begin 'org-element-begin)
+             (defalias 'gptel-org--element-end 'org-element-end))
     (defun gptel-org--element-begin (node)
       "Get `:begin' property of NODE."
-      (org-element-property :begin node))))
+      (org-element-property :begin node))
+    (defun gptel-org--element-end (node)
+      "Get `:end' property of NODE."
+      (org-element-property :end node))))
 
 
 ;;; User options
@@ -140,6 +145,20 @@ This makes it feasible to have multiple conversation 
branches."
   :type 'boolean
   :group 'gptel)
 
+(defcustom gptel-org-ignore-elements '(property-drawer)
+  "List of Org elements that should be stripped from the prompt
+before sending it.
+
+By default gptel will remove Org property drawers from the
+prompt.  For the full list of available elements, please see
+`org-element-all-elements'.
+
+Please note: Removing property-drawer elements is fast, but
+adding elements to this list can significantly slow down
+`gptel-send'."
+  :group 'gptel
+  :type '(repeat symbol))
+
 
 ;;; Setting context and creating queries
 (defun gptel-org--get-topic-start ()
@@ -229,6 +248,7 @@ value of `gptel-org-branching-context', which see."
                        (goto-char (point-min)))
               (goto-char (point-max))
               (gptel-org--unescape-tool-results)
+              (gptel-org--strip-elements)
               (gptel-org--strip-block-headers)
               (let ((major-mode 'org-mode))
                 (gptel--parse-buffer gptel-backend max-entries)))))
@@ -244,13 +264,39 @@ value of `gptel-org-branching-context', which see."
                  (buffer-local-value sym org-buf)))
           (insert-buffer-substring org-buf beg end)
           (gptel-org--unescape-tool-results)
+          (gptel-org--strip-elements)
           (gptel-org--strip-block-headers)
           (let ((major-mode 'org-mode))
             (gptel--parse-buffer gptel-backend max-entries)))))))
 
-(defun gptel-org--strip-tool-headers ()
-  "Remove all tool_call block headers and footers.
-Every line that matches will be removed entirely."
+(defun gptel-org--strip-elements ()
+  "Remove all elements in `gptel-org-ignore-elements' from the
+prompt."
+  (let ((major-mode 'org-mode) element-markers)
+    (if (equal '(property-drawer) gptel-org-ignore-elements)
+        (save-excursion
+          (goto-char (point-min))
+          (while (re-search-forward org-property-drawer-re nil t)
+            ;; ;; Slower but accurate
+            ;; (let ((drawer (org-element-at-point)))
+            ;;   (when (org-element-type-p drawer 'property-drawer)
+            ;;     (delete-region (org-element-begin drawer) (org-element-end 
drawer))))
+
+            ;; Fast but inexact, can have false positives
+            (delete-region (match-beginning 0) (match-end 0))))
+      ;; NOTE: Parsing the buffer is extremely slow.  Avoid this path unless
+      ;; required.
+      ;; NOTE: `org-element-map' takes a third KEEP-DEFERRED argument in newer
+      ;; Org versions
+      (org-element-map (org-element-parse-buffer 'element nil)
+          gptel-org-ignore-elements
+        (lambda (node)
+          (push (list (gptel-org--element-begin node)
+                      (gptel-org--element-end node))
+                element-markers)))
+      (dolist (bounds element-markers)
+        (apply #'delete-region bounds)))))
+
 (defun gptel-org--strip-block-headers ()
   "Remove all gptel-specific block headers and footers.
 Every line that matches will be removed entirely.

Reply via email to