10

NB: The post How to move point to the end of another buffer, using `with-current-buffer` and `goto-char`? presumably asks the same question as this one, but the answers given to it do not readily apply to my case here. E.g. those answers involve inserting text in the other buffer, etc. The question I'm asking here is considerably simpler, and I expect the answer to it should be correspondingly simple as well.


Suppose that

  • the Emacs frame is split into two windows, where one of the windows holds a buffer called *whatever* and the other one holds the standard *scratch* buffer;
  • the *whatever* buffer contains several lines of text;
  • the cursor in the *whatever* buffer is at the beginning of that buffer (i.e. at its (point-min));
  • the *scratch* buffer is the current one;

If I now evaluate any of the following in the *scratch* buffer, the position of the cursor in the *whatever* buffer remains unchanged:

(with-current-buffer "*whatever*"
  (goto-char (point-max)))
(with-current-buffer "*whatever*"
  (end-of-buffer))
(save-excursion
  (set-buffer "*whatever*")
  (goto-char (point-max)))
(save-excursion
  (set-buffer "*whatever*")
  (end-of-buffer))

I figure that, whatever (goto-char (point-max)) or (end-of-buffer) does to *whatever*'s cursor's position gets subsequently discarded.

  1. How must I modify any of the snippets above so that the *whatever* buffer's cursor gets positioned (persistently!) at the end of that buffer.

  2. What should I have done to answer this question myself using the Emacs docs? I tried everything I could think of.


After reading Jules Tamagnan's illuminating answer I realized that I need make a further stipulation explicit:

I'm looking for a solution that will work irrespective of whether the buffer in question is visible in a window or not.

After all, if I view buffer B in a window W, move the cursor to a particular location L, then view some other buffer C in the same window W, and finally point W back to buffer B, I expect to see the cursor at the same location L where I left it. IOW, there must be a way for a buffer to remember where its point is, irrespective of whether it is associated with a window or not.

What about the case where the target buffer is currently visible in two or more windows (W1, W2, W3, ...)? There are many reasonable possibilities for this case. The proposed solution could, for example, change the position of the cursor

  • in the most recently active window among W1, W2, W3, ...;
  • in all the windows W1, W2, W3, ...; or
  • etc., etc.

(I don't have a strong opinion on this corner case, since I expect it to be extremely rare in practice.)

3
  • 1
    The key sections in the manual regarding this are Point and Window Point.
    – cjm
    Commented Apr 6, 2016 at 21:59
  • 2
    Does this answer your question? How to move point to the end of another buffer, using `with-current-buffer` and `goto-char`?
    – Drew
    Commented Nov 20, 2022 at 19:29
  • Just because another question is the same, or essentially the same, but the answers posted to it don't seem to answer the case you have, that doesn't mean you should post a duplicate question to get more answers to the question. And the answers to the original question are relevant to what you ask, since they point you to window-point.
    – Drew
    Commented Nov 20, 2022 at 19:30

3 Answers 3

11

This was a pretty cool question, I learned a lot that I didn't know while trying to figure this out. What I learned was that each window has its own value for point. This is important because it means that the point is not associated with the buffer but instead the actual window. As we've seen this makes a big difference.

What we need to do then is set the the value of the point in the window, the function for that is set-window-point (I found this though google, I find that if I don't know where to start looking in the build in emacs-docs looking on google may shed some light). We then need to tell set-window-point which window to select. To do that I used get-buffer-window (once again I used google to find this).

This code will only work if the buffer is open in a window. Another version should be able to be made where you open the buffer in a window, set the point and then go back to the original window configuration. The code below though is much simpler.

(set-window-point (get-buffer-window "*whatever*") (point-max))

I hope this helps, if you have any other questions or if it doesn't work exactly how you want, just let me know.

2
  • This makes sense; if you open a buffer in 2 windows (eg. via split window), you have 2 independent points.
    – Juancho
    Commented Apr 6, 2016 at 16:58
  • @Juancho Yeah, deep down I knew thats how it worked but it never really sank in until just now
    – Jules
    Commented Apr 6, 2016 at 17:10
7

(Jules Tamagnan's answer identifies the problem, I'll expand on it.)

Your solutions with save-excursion or save-excursion do in fact work! You can see it by checking the value of (point) in that buffer afterwards, e.g.

(progn
  (with-current-buffer "*whatever*"
    (goto-char 42))
  (with-current-buffer "*whatever*"
    (point)))

always returns 42 (provided that it's a valid position in the buffer. That's even mentioned as a gotcha in the documentation of save-excursion: it restores only the position in the original buffer, not the position in any other buffer.

If you find that the buffer position isn't the one you set, that's because something else is changing it, and that thing is most likely a window (or a hook somewhere, but most hooks have no business messing with the point — still, it could be a e.g. process output hook). In the documentation about point, there's a note:

Each buffer has its own value of point, which is independent of the value of point in other buffers. Each window also has a value of point, which is independent of the value of point in other windows on the same buffer. This is why point can have different values in various windows that display the same buffer. When a buffer appears in only one window, the buffer’s point and the window’s point normally have the same value, so the distinction is rarely important. See Window Point, for more details.

If the buffer is shown in a window, then the buffer's point is updated to the window's point every time the point changes or the window is redisplayed. As a consequence, setting the point basically only lasts as long as your Lisp code lasts: as soon as the toplevel command loop takes control, the window is redisplayed (even if it isn't visible, as far as I can tell) and the buffer position is overridden. In effect, the user's chosen position trumps the program-chosen position.

If you want to also update the point of any window that displays the buffer, you can call get-buffer-window-list to enumerate the windows:

(with-current-buffer "*whatever*"
  (goto-char …)
  (cl-dolist (window (get-buffer-window-list nil nil t))
    (set-window-point window (point))))

But consider that if the user was visiting the buffer, they might not appreciate having the point changed under their nose.

As far as I know Emacs doesn't track when windows were selected (that's “active” in Emacs terminology), you can only know whether they are currently selected. If you start caring about selected windows, beware that each frame has a selected window, and Emacs doesn't always know when frames are selected ((selected-frame) doesn't completely match the end user experience, especially with frames inside terminal emulators since Emacs has no way to know when the user has the terminal emulator window active).

0

The emacs src is full of solutions to small problems like this. Check out the code for compilation-auto-jump:

(defun compilation-auto-jump (buffer pos)
  (when (buffer-live-p buffer)
    (with-current-buffer buffer
      (goto-char pos)
      (let ((win (get-buffer-window buffer 0)))
        (if win (set-window-point win pos)))
      (if compilation-auto-jump-to-first-error
      (compile-goto-error)))))

Note that in addition to setting the position, the window position of the first window showing the buffer (if any) on all frames is set as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.