Remove superfluous hooks and start cleanup

This commit is contained in:
daedsidog 2024-05-12 23:16:55 +03:00
parent 329a2776b3
commit 192a4ba9de

View file

@ -31,23 +31,17 @@
(require 'cl-lib)
(defvar gptel--context-buffer nil)
(defvar gptel--contexts-alist nil
"An association list from buffers to a list of regions.")
(defvar gptel--current-highlight-region nil)
(defvar gptel--context-buffer-point nil)
;; We need a minor mode for a custom keymap, so that we may be able to remove
;; contexts directly from the contexts buffer.
(define-minor-mode context-mode
(define-minor-mode gptel-context-mode
"A minor mode for working with context."
:lighter " Context"
:lighter " GPTel Context"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "c")
'gptel-remove-context-at-point-from-context-buffer)
'gptel-remove-context-at-point)
map))
(defcustom gptel-contexter-selection-face 'secondary-selection
(defcustom gptel-context-highlight-face 'header-line
"Face to use to highlight selected context in the buffers."
:group 'gptel
:type 'symbol)
@ -56,24 +50,41 @@
;;; ------------------------------ FUNCTIONS ------------------------------- ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun gptel--highlight-region (start end)
;;;###autoload
(defun gptel-add-context (&optional arg)
"Add context to GPTel.
When called regularly, adds current buffer as context.
When ARG is positive, prompts for buffer name to add as context.
When ARG is negative, removes current buffer from context.
When called in `dired-mode', adds selected files as context.
When called with region selected, adds selected region as context."
(interactive)
(cond
;; A region is selected.
((use-region-p)
(gptel--add-region-as-context (current-buffer)
(region-beginning)
(region-end))
(deactivate-mark))
;; No region is currently selected, so delete a context under point if there
;; is one.
((gptel-context-at-point)
(gptel-delete-context (gptel-context-at-point))
(message "Context under point has been deleted."))))
(defun gptel--make-context-overlay (start end)
"Highlight the region from START to END."
(let ((overlay (make-overlay start end)))
(overlay-put overlay 'face gptel-contexter-selection-face)
(overlay-put overlay 'contexter t)))
(overlay-put overlay 'face gptel-context-highlight-face)
(overlay-put overlay 'gptel-context t)
overlay))
(defun gptel--unhighlight-region (start end)
"Remove highlighting from the region between START and END."
(dolist (overlay (overlays-in start end))
(when (and (overlay-get overlay 'contexter)
(overlay-get overlay 'face))
(delete-overlay overlay))))
(cl-defun gptel--add-region-to-contexts ()
"Add the selected region to te contexts.
Order of the contexts in a buffer is determined by their order in the buffer."
(unless (use-region-p)
(error "No region selected"))
(cl-defun gptel--add-region-as-context (buffer start end)
"Add the region delimited by START and END in BUFFER as context."
;; Remove existing contexts in the same region, if any.
(mapc #'gptel-delete-context
(gptel-contexts-in-region buffer start end))
(let ((start (make-marker))
(end (make-marker)))
(set-marker start (region-beginning) (current-buffer))
@ -98,163 +109,51 @@ Order of the contexts in a buffer is determined by their order in the buffer."
(message "No content in selected region.")
(cl-return-from gptel--add-region-to-contexts nil))
;; First, highlight the region.
(gptel--highlight-region start end)
;; Then, add the region to `gptel--contexts-alist', associating it with
;; the current buffer.
(let ((existing-entry (assoc (current-buffer) gptel--contexts-alist)))
(if (not existing-entry)
(add-to-list 'gptel--contexts-alist
(cons (current-buffer) (list (list start end))))
(let ((regions (cdr existing-entry))
(new-region (list (list start end))))
(setcdr existing-entry
(cl-sort (append regions new-region)
(lambda (a b)
(or (<= (car a) (car b)))))))))
(message "Region added to context buffer.")
t))
(prog1 (gptel--make-context-overlay start end)
(message "Region added to context buffer."))))
;;;###autoload
(defun gptel-context-in-region (buffer start end)
"Return the context in the given region, if any, in BUFFER.
START and END signify the region delimiters.
A context in this function is a cons cell with the buffer as the CAR and the
region tuple as the CDR. The tuple is a list, not a cons cell."
(interactive)
(let (context regions)
(setq context (assoc buffer gptel--contexts-alist))
(when context
(cl-loop for (ctx-start ctx-end) in (cdr context)
;; If the current context range overlaps with the argument range.
when (or (<= ctx-start start end ctx-end)
(<= ctx-start start ctx-end end)
(<= start ctx-start end ctx-end)
(<= start ctx-start ctx-end end))
do (push (list ctx-start ctx-end) regions)))
(if (not (zerop (length regions)))
(setq context (cons buffer regions))
(setq context nil))
context))
(defun gptel-contexts-in-region (buffer start end)
"Return the list of context overlays in the given region, if any, in BUFFER.
START and END signify the region delimiters."
(with-current-buffer buffer
(cl-remove-if-not #'(lambda (overlay)
(overlay-get overlay 'gptel-context))
(overlays-in start end))))
;;;###autoload
(defun gptel-context-at-point ()
"Return the context at point, if any.
A context in this function is a cons cell with the buffer as the CAR and the
region tuple as the CDR. The tuple is a list, not a cons cell."
"Return the context overlay at point, if any."
(car (overlays-in (point) (point))))
;;;###autoload
(defun gptel-delete-context (context)
"Remove the CONTEXT overlay from the contexts list."
(delete-overlay context))
;;;###autoload
(defun gptel-contexts ()
"Get the list of all context overlays in all active buffers."
(cl-remove-if-not #'(lambda (ov)
(overlay-get ov 'gptel-context))
(let ((all-overlays '()))
(dolist (buf (buffer-list))
(with-current-buffer buf
(setq all-overlays
(append all-overlays
(overlays-in (point-min)
(point-max))))))
all-overlays)))
;;;###autoload
(defun gptel-delete-all-contexts ()
"Clear all contexts."
(interactive)
(let ((point (point))
(buffer (current-buffer))
context
regions '())
(setq context (assoc buffer gptel--contexts-alist))
(when context
(cl-loop for (start end) in (cdr context)
when (and (>= point start) (<= point end))
do (push (list start end) regions)))
(if (not (zerop (length regions)))
(setq context (cons buffer regions))
nil)))
(mapc #'gptel-delete-context
(gptel-contexts)))
;;;###autoload
(defun gptel-remove-context (context &optional refresh-context-buffer)
"Remove the CONTEXT snippet and unhighlight its region.
If REFRESH-CONTEXT-BUFFER is set to t, then also refresh the context buffer."
(let* ((buffer (car context))
(regions (cdr context)))
;; Unhighlight each region.
(with-current-buffer buffer
(cl-loop for (start end) in regions
do (gptel--unhighlight-region start end)))
;; Remove regions from the list and clean up if empty.
(setq gptel--contexts-alist
(delq nil
(mapcar (lambda (item)
(if (eq (car item) buffer)
(let ((new-value
(seq-difference (cdr item) regions)))
(unless (seq-empty-p new-value)
(cons (car item) new-value)))
item))
gptel--contexts-alist))))
(when refresh-context-buffer
(gptel--refresh-context-buffer)))
;;;###autoload
(cl-defun gptel-pop-or-push-context ()
"Pop or push text into the context buffer depending on selection state.
If a region is selected, push the region as context.
If no region is selected, try to pop the context at the point.
If a region is selected but it contains contexts, pop all contexts within it and
add the region as a single context..
Popping context has no other meaning other than erasing it from the context
buffer."
(interactive)
(let ((context
(if (use-region-p)
(gptel-context-in-region (current-buffer)
(region-beginning)
(region-end))
(gptel-context-at-point))))
(when context
(gptel-remove-context context))
(when (use-region-p)
(unless (gptel--add-region-to-contexts)
(cl-return-from gptel-pop-or-push-context))
(deactivate-mark)))
(gptel--refresh-context-buffer))
;;;###autoload
(defun gptel-remove-all-contexts ()
"Clear all saved context regions."
(interactive)
(mapc #'gptel-remove-context gptel--contexts-alist)
(gptel--refresh-context-buffer))
(defun gptel--cleanup-killed-buffer ()
"Remove contexts if their buffer was killed."
(let ((context (assoc (current-buffer) gptel--contexts-alist)))
(when context
(setq gptel--contexts-alist (assoc-delete-all (current-buffer)
gptel--contexts-alist))
(gptel--refresh-context-buffer))))
;; We don't care about the hook parameters.
(defun gptel--cleanup-degenerate-contexts (_ _ _)
"Clean up contexts by degenerate regions."
(let ((context (assoc (current-buffer) gptel--contexts-alist)))
(when context
(setcdr context
(cl-remove-if (lambda (region)
(= (cl-first region) (cl-second region)))
(cdr context)))
(when (zerop (length (cdr context))) ; All regions were removed!
(setq gptel--contexts-alist
(assoc-delete-all (current-buffer) gptel--contexts-alist)))
(gptel--refresh-context-buffer))))
(defun gptel--sync-context-buffer (start end _)
"See if the markers in the buffer have been changed between START and END.
If they have, update the context buffer."
(when (gptel-context-in-region (current-buffer) start end)
(gptel--refresh-context-buffer)))
(defun gptel--ensure-context-buffer-exists ()
"Make sure the context buffer exists. Create it if it does not."
(with-current-buffer (get-buffer-create "*Context*") ; Create, if nonexistant.
(setq gptel--context-buffer (current-buffer))
;; Ensure the minor mode context-mode is enabled in this buffer.
(unless (bound-and-true-p context-mode)
(context-mode 1))
(read-only-mode 1))) ; Set read-only mode.
(defun gptel--sort-pairs-by-ascending-order (regions)
"Sort the pairs in REGIONS by ascending order."
(sort regions (lambda (a b) (< (car a) (car b)))))
;;;###autoload
(defun gptel--major-mode-md-prog-lang (mode)
(defun gptel-major-mode-md-prog-lang (mode)
"Get the Markdown programming language string for the given MODE."
(cond
((eq mode 'emacs-lisp-mode) "emacs-lisp")
@ -377,7 +276,7 @@ REGIONS is a list of pairs of (start, end) lists."
(goto-char (point-max))
(skip-chars-backward " \t\n\r")
(point))
prog-lang-tag (gptel--major-mode-md-prog-lang
prog-lang-tag (gptel-major-mode-md-prog-lang
major-mode)))
(insert "\n\n```" prog-lang-tag "\n")
(cl-loop for (start end) in regions do
@ -521,7 +420,7 @@ In essence, just highlights the context where the point is in the buffer."
(gptel--unhighlight-region current-start current-end)
(setq gptel--current-highlight-region nil))
(when context
(gptel--highlight-region start end)
(gptel--make-context-region start end)
(setq gptel--current-highlight-region (list start end)))))
(cl-defun gptel--monitor-context-buffer-point-change ()
@ -536,27 +435,10 @@ Used mainly for selecting contexts when the point has moved."
;;;###autoload
(defun gptel-context-string ()
"Return the contents of the context buffer."
"Return the string of all the contexts."
(gptel--ensure-context-buffer-exists)
(with-current-buffer gptel--context-buffer
(buffer-substring-no-properties (point-min) (point-max))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; -------------------------------- HOOKS --------------------------------- ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(add-hook 'kill-buffer-hook #'gptel--cleanup-killed-buffer)
(add-hook 'after-change-functions
#'gptel--cleanup-degenerate-contexts
#'gptel--sync-context-buffer)
(add-hook 'post-command-hook #'gptel--monitor-context-buffer-point-change)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; ---------------------------- INITIALIZATION ---------------------------- ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Just make sure an empty buffer exists.
(gptel--refresh-context-buffer)
(provide 'contexter)
(provide 'gptel-contexter)
;;; gptel-contexter.el ends here.