4

While working on a sort of "background processing" function, I noticed that my logs would print backwards if the buffer was in another frame. I cannot seem to figure out why it is happening.

I am not looking for a solution to make the log print correctly, I can just specify that during logging, to move to (point-max) and everything is fine.

My question is, why does the point seem to update while modifying a buffer in the current frame, but not when the buffer is visible in another frame?

It is hard to fully describe so I will include code:

Test Code

Copy the code and run it in *scratch* in these two different frame configurations

  1. With one frame, showing both *scratch* and test-buffer

  2. With two frames, one showing *scratch* and one showing *test-buffer*

(defvar test-buffer (get-buffer-create "test-buffer"))

(progn
  (with-current-buffer test-buffer (delete-region 1 (point-max)))
  (run-with-timer 
   1 nil (lambda () (with-current-buffer test-buffer (insert "first\n"))))
  (run-with-timer 
   2 nil (lambda () (with-current-buffer test-buffer (insert "second\n"))))
  (run-with-timer 
   3 nil (lambda () (with-current-buffer test-buffer (insert "third\n")))))

You will see that at the end of the first test, test-buffer will contain

first
second
third

and after the second test, test-buffer will contain

third
second
first

What is causing this? Is this a documented feature or perhaps even a bug?

Here is a gif of me running the tests:

animated demo

Jordon Biondo
  • 12,332
  • 2
  • 41
  • 62
  • Just to check, does this happen with -Q? – Malabarba Oct 01 '14 at 21:14
  • Technically, you're not supposed to rely on consistent point position when using `with-current-buffer`, so the right thing is indeed to `(goto-char (point-max))`. That said, having a different behaviour depending on which frame the buffer is being displayed sounds like a bug. Maybe `with-current-buffer` tries to “borrow” a window on the current frame, but will use a new (fictitious) window if there isn’t one on the current frame. – Malabarba Oct 01 '14 at 23:22

3 Answers3

5

Each buffer has a point, and in addition each window also has a point. The manual explains the relationship:

  • The window point is established when a window is first created; it is initialized from the buffer's point, or from the window point of another window opened on the buffer if such a window exists.
  • Selecting a window sets the value of point in its buffer from the window's value of point. Conversely, deselecting a window sets the window's value of point from that of the buffer. Thus, when you switch between windows that display a given buffer, the point value for the selected window is in effect in the buffer, while the point values for the other windows are stored in those windows.
  • As long as the selected window displays the current buffer, the window's point and the buffer's point always move together; they remain equal.

Here's a little experiment to illustrate this. Create a buffer and display it in another window (it doesn't matter whether that window is in the same frame or not). Stick to a window that doesn't show the buffer. Insert some text in the buffer, without selecting the other window, and notice that the buffer's point is updated to be after the inserted text. Then select the window displaying the buffer. Notice that now the buffer's point has now changed to the window's point.

(with-selected-window (selected-window) (find-file-other-window "foo"))
#<buffer foo>
(with-current-buffer (set-buffer "foo") (insert "foo") (cons (point) (point-max)))
(4 . 4)
(with-selected-window (selected-window) (other-window 1))
nil
(with-current-buffer (set-buffer "foo") (cons (point) (point-max)))
(1 . 4)

To avoid this effect where the point is reset to another location when the user switches to a window that displays the buffer, you can use insert-before-markers instead of plain insert. This works because the window's point is implemented as a marker internally. (The buffer's point is not a marker.) This is what accept-process-output does internally, for example, which is why output from a subprocess doesn't get mixed up when the destination buffer is displayed in a non-selected window.

Another approach is to set window-point-insertion-type to t. By default, a window's point stays behind insertions. By setting it to a non-nil value, the window's point moves with insertions at that location. This is the approach used by Comint, for example: it declares window-point-insertion-type as buffer-local and sets it to t.

In your experiment, you never explicitly switch to the window that's displaying the test buffer. But that buffer is in the selected window of another GUI frame. Since the insertion code is running in a timer, with user interactions between each insertion, the redisplay code runs between each insertion. One of the effects of the redisplay code is to recalculate the frame's title. Deep in the bowels of x_consider_frame_title, the code temporarily selects the frame's selected window, causing the cursor update. This analysis is confirmed by running the experiment with the buffer displayed in a terminal frame instead of a GUI frame: the insertions are in the expected order. You'd see the same effect from any kind of asynchronous operation: timer, process sentinel, etc.

2

You can also tell Emacs to move the window-point forward when text is inserted there. This should work at least in simple cases like this.

(setq-local window-point-insertion-type t)
tarsius
  • 25,298
  • 4
  • 69
  • 109
1

what is causing this?

The problem here is that you seem to consider that the point is associated to a buffer, but this is not exactly the case.

Consider for example the case where the same buffer is displayed in two different windows. You'll notice that the buffer contents is the same (i.e. modifying the buffer contents in one window modifies it in the other), but point positions are not identical (i.e. moving the point in one buffer doesn't move it in the other, which is a desirable and documented feature).

In other words, point position is not attached to the buffer itself, but rather to the (buffer, window) pair.

A consequence of this is that you should not rely on the point position being where you last left it when you change buffer (using with-current-buffer for example). A reliable behaviour could be achieved by always calling a point movement function before doing anything else. For example:

(with-current-buffer test-buffer
  (goto-char (point-max))
  (insert "first\n"))
François Févotte
  • 5,917
  • 1
  • 24
  • 37
  • This explanation is instructive, but it still doesn't address why there's a different behaviour when the window is on the same frame vs different frame. – Malabarba Oct 01 '14 at 23:17
  • @Gilles I don't see it either, but that's what the question said. Or am I horribly misunderstanding it? – Malabarba Oct 01 '14 at 23:44
  • @Gilles by watching the gif, it seems to me the window was *not* selected on both tests. In both cases the test was run from the window holding the scratch buffer. – Malabarba Oct 01 '14 at 23:56
  • 1
    @Malabarba Sorry, my remarks about frames not mattering were wrong, I mixed up the results of my experiments. I've finally traced down the effect of showing the buffer in a different frame: that, due to some fairly specific circumstances of Jordon's experiment, triggers an internal use of `select-window` (well, `Fselect_window`) which updates the buffer's point to the window's point. – Gilles 'SO- stop being evil' Oct 02 '14 at 00:57