karthink / gptel

A simple LLM client for Emacs
GNU General Public License v3.0
1.04k stars 111 forks source link

move point to last response #243

Closed fhansen42 closed 3 months ago

fhansen42 commented 3 months ago

Is there a way to move the point to the last response? I see that gptel--mark-response exists. I'd like to be able to move to the last response then call gptel--mark-response. I tried gptel-end-of-response, but it seems to move the point just past the last response.

karthink commented 3 months ago

By "last", do you mean the response closest to the end of the buffer, or the latest (in time) response, or the response that the cursor is closest to?

fhansen42 commented 3 months ago

latest (in time) response

karthink commented 3 months ago

Okay. Still trying to understand what you're looking for. Which of the following is it?

  1. Mark a response automatically immediately after it is inserted.
  2. A command you can manually invoke at any time, that jump to the latest (in time) gptel response in the buffer and marks it.
  3. Something else?
fhansen42 commented 3 months ago

2. A command you can manually invoke at any time, that jump to the latest (in time) gptel response in the buffer and marks it.

I'm just looking to get the point to the last (most recent) response. From there I can use gptel--mark-response to mark it. Doing this manually is usually as simple as doing next-line or prev-line a couple of times (depending on whether or not I have set (gptel-post-response-functions . gptel-end-of-response)). I'd like to be able to do it programmatically, however, so that I could write other functions that do stuff with the response.

karthink commented 3 months ago

Got it, thanks for the explanation.

When there are many responses and the chronological order is not the buffer order (i.e. top to bottom, like on the website), there is no way for gptel to know which one is the "latest" response. So you'll have to keep track of it on your own.

Here's a recipe:

  1. Make a buffer-local variable.
  2. When there is a response, record the start and end positions in this variable. Overwrite any existing values.
  3. When you need to act on the response programmatically, use this variable.

How to do 1:

(defvar-local fhansen/gptel-response-pos nil
  "Used to track the position of the latest response from gptel.")

How to do 2:

First define the tracking function

(defun fhansen/gptel-track-response (beg end)
  "Track response between BEG and END."
  (if (not fhansen/gptel-response-pos)
      ;; Not defined yet
      (setq fhansen/gptel-response-pos
            (cons (set-marker (make-marker) beg)
                  (set-marker (make-marker) end)))
    ;; Defined already, just move
    (move-marker (car fhansen/gptel-response-pos) beg)
    (move-marker (cdr fhansen/gptel-response-pos) end)))

We use markers instead of positions since we want to track the positions dynamically, i.e. through changes to the buffer.

Run this function after each response:

(add-hook 'gptel-post-response-functions #'fhansen/gptel-track-response)

How to do 3: (example with marking -- change as appropriate)

(defun fhansen/gptel-mark-latest ()
  (interactive)
  (goto-char (car fhansen/gptel-response-pos))
  (push-mark)
  (goto-char (cdr fhansen/gptel-response-pos))
  (activate-mark))

Alternatively, you can run (goto-char (car fhansen/gptel-response-pos)) and then call gptel--mark-response instead. But note that gptel--mark-response is an internal function and might change or disappear. The hook functions will always work.

Untested, please test.

fhansen42 commented 3 months ago

@karthink that worked perfectly! Thanks so much.