Konubinix' opinionated web of thoughts

How to Change the Point in a Buffer in Another Window in Emacs?

fleeting

how to change the point in a buffer in another window in emacs?

Say I have a window somewhere.

(setq-local mybuffer (get-buffer-create "somebuffer"))

If I want to put some content in it, I can just use with-current-buffer

(with-current-buffer mybuffer
  (insert "someline
someotherline
and some more")
  )

Now that I have this buffer, I would like to move its point.

Currently, the point is

(with-current-buffer mybuffer
  (point)
  )
37

I can simply run

(with-current-buffer mybuffer
  (goto-char 10)
  )
10

And then check that the point is indeed 10

(with-current-buffer mybuffer
  (point)
  )
10

So far so good, now I would like to do the same while seeing the buffer.

(let ((here (current-buffer)))
  (pop-to-buffer mybuffer)
  (pop-to-buffer here)
  )

And now, I can try to do the same.

Moving to some char

(with-current-buffer mybuffer
  (goto-char 1)
  )
1

And realizing that…

(with-current-buffer mybuffer
  (point)
  )
10

Wait, what? It has not moved at all.

first idea

It looks like it saves the window configuration. According to the documentation.

current-window-configuration is a built-in function in ‘src/window.c’.

(current-window-configuration &optional FRAME)

Probably introduced at or before Emacs version 18.

Return an object representing the current window configuration of FRAME. If FRAME is nil or omitted, use the selected frame. This describes the number of windows, their sizes and current buffers, and for each displayed buffer, where display starts, and the position of point. An exception is made for point in the current buffer: its value is -not- saved. This also records the currently selected frame, and FRAME’s focus redirection (see ‘redirect-frame-focus’). The variable ‘window-persistent-parameters’ specifies which window parameters are saved by this function.

[back]

It sounds like using stuff that save the window configuration will only persist the point of the current buffer.

Trying to debug this, I realize that there is also with-current-buffer-window that according to the documentation.

with-current-buffer-window is a Lisp macro in ‘window.el’.

(with-current-buffer-window BUFFER-OR-NAME ACTION QUIT-FUNCTION &rest BODY)

Evaluate BODY with a buffer BUFFER-OR-NAME current and show that buffer. This construct is like ‘with-temp-buffer-window’ but unlike that, makes the buffer specified by BUFFER-OR-NAME current for running BODY.

[back]

After trying it a bit, it seems like it is overly complicated and does not do what I want.

I could try set-buffer, but without much success.

(progn
  (set-buffer mybuffer)
  (goto-char 1)
  )
1
(with-current-buffer mybuffer
  (point)
  )
37

It still did not move.

I can try to temporarily hide the buffer within a macro that would call with-current-buffer.

(defmacro konix/with-current-buffer (buffer body)
  `(let ((window (get-buffer-window ,buffer))
	 (here (current-buffer))
	 (some-temp-buffer (get-buffer-create "sometempbuffer"))
	 (res)
	 )
     (when window
       (pop-to-buffer ,buffer)
       (switch-to-buffer some-temp-buffer)
       (pop-to-buffer here)
       )
     (setq res (with-current-buffer ,buffer
		 ,body
		 ))
     (when window
       (pop-to-buffer some-temp-buffer)
       (switch-to-buffer ,buffer)
       (pop-to-buffer here)
       )
     res
     )
  )
(konix/with-current-buffer mybuffer
			   (goto-char 1)
			   )
1

It has several corner cases that tell me that this is not a good idea, nor I want to dig into it.

I can more simply deal with the fact that the buffer may or may not be visible.

(defmacro konix/with-current-buffer (buffer body)
  `(let ((window (get-buffer-window ,buffer))
	 (here (current-buffer))
	 (res)
	 )
     (if window
	 (pop-to-buffer ,buffer)
       (switch-to-buffer ,buffer)
       )
     (setq res (save-window-excursion ,body))
     (if window
	 (pop-to-buffer here)
       (switch-to-buffer here)
       )
     res
     )
  )
(konix/with-current-buffer mybuffer
			   (goto-char 1)
			   )
1
(with-current-buffer mybuffer
  (point)
  )
1

This appears to do the trick, while not that elegant in my mind. It has also corner cases, like when the minibuffer is active, it will raise an error linked to the fact you cannot switch from the minibuffer to another window.

a story of points

Actually, there are two kinds of point

  1. point: the point of the buffer
  2. window point: the point associated to the buffer in each window

This allows to have the same buffer displayed in several windows while being showed with several different points.

The value of point is moved to the location of the last displayed window.

Also, windows keep an history of points for each buffer.

That means that:

  1. while I don’t see the buffer in any window, point remains stable.
  2. when I display the buffer for the first time, the window point is initialized to the value of point, so far so good,
  3. when I bury the buffer and change its point, it remains stable while I don’t show the buffer again,
  4. if I show the buffer in another window and then bury it, the point remains stable because the window point was initialized with the value of point,
  5. if I delete the windows and recreate the same configuration and then show the buffer, everything is ok
  6. BUT, if I show the buffer in a window that previously showed it, its historical value of window point will overwrite the value of point
  7. ALSO, if a window is showing the buffer, any code moving the point will see it keeping its value until returning. When the code return, the top-level loop redisplays the window and then the buffer gets overwritten,

trying updating the window point everywhere

Then, why not moving across all the windows and setting the point accordingly.

I can list all the windows with window-list, using a value other than nil or t for MINIBUFFER so that it leaves the minibuffer alone.

There set-window-point, but it only sets the point of the window according to the currently displayed buffer.

There is also switch-to-buffer-preserve-window-point that allow using point instead of the historical value of window-point, but it behaves globally and thus does not fit this use case.

There is set-window-buffer-start-and-point that appears to help update this historical value.

(defun konix/persist-point-all-windows-not-showing-buffer (&optional frame)
  (let (
	(buffer (current-buffer))
	(point (point))
	)
    (with-selected-frame (or frame (selected-frame))
      (mapc
       (lambda (w)
	 (let (
	       (prev-buffers (window-prev-buffers w))
	       )
	   (save-window-excursion
	     (set-window-buffer-start-and-point w buffer nil point)
	     )
	   (set-window-prev-buffers w prev-buffers)
	   )
	 )
       (window-list nil -1)
       )
      )
    )
  )

This appears to work for windows that don’t currently show the buffer. Also I need to save-window-excursion because it actually pop to the buffer.

Also, because save-window-excursion only applies to the selected frame, I need to make sure the appropriate frame is selected before doing it.

I need to first save the buffer and the point, because (current-buffer) and (point) depend on the selected frame.

Finally, I need to save and restore the prev buffers for each window or the persisting buffer will always be moved as next buffer by set-window-prev-buffers. This is not saved by save-window-excursion and needs to be done manually.

To do the same with the windows that show the buffer, I can do this

(defun konix/persist-point-all-windows-showing-buffer (&optional frame)
  (mapc
   (lambda (w)
     (when (equal (window-buffer w) (current-buffer))
       (set-window-point w (point))
       )
     )
   (window-list frame -1)
   )
  )
(with-current-buffer mybuffer
  (goto-char 10)
  (konix/persist-point-all-windows-showing-buffer)
  (konix/persist-point-all-windows-not-showing-buffer)
  )

Now I need to do this on all frames. Because save-window-excursion does work only in the selected frame, I also need to select the frame before running those.

(defun konix/persist-point-all-windows ()
  (mapc
   (lambda (f)
     (konix/persist-point-all-windows-showing-buffer f)
     (konix/persist-point-all-windows-not-showing-buffer f)
     )
   (frame-list)
   )
  )
(with-current-buffer mybuffer
  (goto-char 10)
  (konix/persist-point-all-windows)
  )

Now, whatever the window is showing the buffer or not, whatever it happens in another frame or not, the point is correctly persisted everywhere.

Now I can wrap this up in a simple macro that behaves like with-current-buffer

(defmacro konix/with-current-buffer-persisted-point (buffer body)
  `(let ((res))
     (with-current-buffer ,buffer
       (setq res ,body)
       (konix/persist-point-all-windows)
       )
     res
     )
  )

I now can simply run

(konix/with-current-buffer-persisted-point mybuffer
					   (goto-char 10)
					   )

And realize that the point was actually persisted in all windows, even if the buffer is shown or not, in another frame or not.

(with-current-buffer mybuffer
  (point)
  )
10