unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#12170: save-excursion fails boundary case with recenter
@ 2012-08-10  1:23 Bill Brodie
  2012-08-10  9:34 ` martin rudalics
  0 siblings, 1 reply; 25+ messages in thread
From: Bill Brodie @ 2012-08-10  1:23 UTC (permalink / raw)
  To: 12170

[-- Attachment #1: Type: text/plain, Size: 427 bytes --]

Running GNU Emacs 24.1.1 (i386-mingw-nt6.1.7601) of 2012-06-10 on MARVIN
under Windows 7.

 

Define:

 

                (defun f (n) (save-excursion (forward-line (- n)) (recenter
0)))

 

Let H be the window height in lines, and suppose that point is positioned at
the beginning of line L > H.

 

Despite 'save-excursion', (f K) moves point if K = H.  It leaves point
unchanged for any other value of K, 0 <= K < L.

 

 


[-- Attachment #2: Type: text/html, Size: 2450 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10  1:23 bug#12170: save-excursion fails boundary case with recenter Bill Brodie
@ 2012-08-10  9:34 ` martin rudalics
  2012-08-10  9:50   ` Bastien
  2012-08-10 13:40   ` Bill Brodie
  0 siblings, 2 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-10  9:34 UTC (permalink / raw)
  To: Bill Brodie; +Cc: 12170

 >                 (defun f (n) (save-excursion (forward-line (- n)) (recenter
 > 0)))
 >
 >
 >
 > Let H be the window height in lines, and suppose that point is positioned at
 > the beginning of line L > H.
 >
 >
 >
 > Despite 'save-excursion', (f K) moves point if K = H.  It leaves point
 > unchanged for any other value of K, 0 <= K < L.

I'm not sure whether I clearly understand your scenario.  Suppose with
emacs -Q I evaluate

(progn
   (defun f (n)
     (save-excursion (forward-line (- n)) (recenter 0)))
   (let ((buffer (switch-to-buffer "foo"))
	(height (window-height (get-buffer-window "foo"))))
     (insert-char 10 (* height 2))
     (let ((pt (point)))
       (f height)
       (message "old %s new %s" pt (point)))))

Then the values printed by the message are equal.  Please elaborate.

Thanks, martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10  9:34 ` martin rudalics
@ 2012-08-10  9:50   ` Bastien
  2012-08-10 13:03     ` martin rudalics
  2012-08-10 13:40   ` Bill Brodie
  1 sibling, 1 reply; 25+ messages in thread
From: Bastien @ 2012-08-10  9:50 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, Bill Brodie

martin rudalics <rudalics@gmx.at> writes:

> Then the values printed by the message are equal.  

Indeed.

> Please elaborate.

I think the OP lacks something like `save-visual-excursion'.

-- 
 Bastien





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10  9:50   ` Bastien
@ 2012-08-10 13:03     ` martin rudalics
  0 siblings, 0 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-10 13:03 UTC (permalink / raw)
  To: Bastien; +Cc: 12170, Bill Brodie

 > I think the OP lacks something like `save-visual-excursion'.

Then `save-window-excursion' should handle it.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10  9:34 ` martin rudalics
  2012-08-10  9:50   ` Bastien
@ 2012-08-10 13:40   ` Bill Brodie
  2012-08-10 14:47     ` martin rudalics
                       ` (2 more replies)
  1 sibling, 3 replies; 25+ messages in thread
From: Bill Brodie @ 2012-08-10 13:40 UTC (permalink / raw)
  To: 'martin rudalics'; +Cc: 12170

Martin,

Thanks for the test code and clarification request.

(1) By "window height", I meant the number of lines displayed for the actual
buffer, not counting the mode line or minibuffer.  It turns out this is one
less than the value returned by `window-height'.

(2) The value of `point' changes (the cursor "hops", in other words) when
the window is redisplayed, or when control returns to the user.

Here is code that will produce the bug (with emacs -Q):

(progn
   (defun f (n)
     (save-excursion (forward-line (- n)) (recenter 0)))
   (let ((buffer (switch-to-buffer "foo"))
         (height (1- (window-height (get-buffer-window "foo")))))
     (insert-char 10 (* height 2))
     (let ((pt (point)))
       (f height)
       (redisplay)
       (message "height %s old %s new %s" height pt (point)))))

If you leave out the `redisplay' call, on the other hand, old = new in the
message -- but after control returns to the user, point has still been moved
in the buffer.

Bill

-----Original Message-----
From: martin rudalics [mailto:rudalics@gmx.at] 
Sent: Friday, August 10, 2012 5:34 AM
To: Bill Brodie
Cc: 12170@debbugs.gnu.org
Subject: Re: bug#12170: save-excursion fails boundary case with recenter

 >                 (defun f (n) (save-excursion (forward-line (- n))
(recenter
 > 0)))
 >
 >
 >
 > Let H be the window height in lines, and suppose that point is positioned
at  > the beginning of line L > H.
 >
 >
 >
 > Despite 'save-excursion', (f K) moves point if K = H.  It leaves point  >
unchanged for any other value of K, 0 <= K < L.

I'm not sure whether I clearly understand your scenario.  Suppose with emacs
-Q I evaluate

(progn
   (defun f (n)
     (save-excursion (forward-line (- n)) (recenter 0)))
   (let ((buffer (switch-to-buffer "foo"))
	(height (window-height (get-buffer-window "foo"))))
     (insert-char 10 (* height 2))
     (let ((pt (point)))
       (f height)
       (message "old %s new %s" pt (point)))))

Then the values printed by the message are equal.  Please elaborate.

Thanks, martin






^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 13:40   ` Bill Brodie
@ 2012-08-10 14:47     ` martin rudalics
  2012-08-10 16:18       ` Stefan Monnier
  2012-08-10 19:01       ` Eli Zaretskii
  2012-08-10 15:04     ` Stefan Monnier
  2012-08-10 18:58     ` Eli Zaretskii
  2 siblings, 2 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-10 14:47 UTC (permalink / raw)
  To: Bill Brodie; +Cc: 12170

 > (1) By "window height", I meant the number of lines displayed for the actual
 > buffer, not counting the mode line or minibuffer.  It turns out this is one
 > less than the value returned by `window-height'.
 >
 > (2) The value of `point' changes (the cursor "hops", in other words) when
 > the window is redisplayed, or when control returns to the user.
 >
 > Here is code that will produce the bug (with emacs -Q):
 >
 > (progn
 >    (defun f (n)
 >      (save-excursion (forward-line (- n)) (recenter 0)))
 >    (let ((buffer (switch-to-buffer "foo"))
 >          (height (1- (window-height (get-buffer-window "foo")))))
 >      (insert-char 10 (* height 2))
 >      (let ((pt (point)))
 >        (f height)
 >        (redisplay)
 >        (message "height %s old %s new %s" height pt (point)))))
 >
 > If you leave out the `redisplay' call, on the other hand, old = new in the
 > message -- but after control returns to the user, point has still been moved
 > in the buffer.

I see it now, thanks.  Surprisingly it doesn't show up with code like

(progn
   (defmacro save-this-window-excursion (&rest body)
     "..."
     (let ((start (make-symbol "start"))
	  (point (make-symbol "point")))
       `(let ((,start (copy-marker (window-start)))
	     (,point (copy-marker (window-point))))
	 (save-selected-window
	   (progn ,@body))
	 (set-window-start (selected-window) ,start t)
	 (set-window-point (selected-window) ,point))))

   (defun f (n)
      (save-this-window-excursion (forward-line (- n)) (recenter 0)))

    (let ((buffer (switch-to-buffer "foo"))
          (height (1- (window-height (get-buffer-window "foo")))))
      (insert-char 10 (* height 2))
      (let ((pt (point)))
        (f height)
        (redisplay)
        (message "height %s old %s new %s" height pt (point)))))

so I'd suspect the culprit somewhere in redisplay_window's code

   if (w->optional_new_start

w->optional_new_start is 1 from `recenter'

       && CHARPOS (startp) >= BEGV
       && CHARPOS (startp) <= ZV)
     {
       w->optional_new_start = 0;
       start_display (&it, w, startp);
       move_it_to (&it, PT, 0, it.last_visible_y, -1,
		  MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
       if (IT_CHARPOS (it) == PT)
	w->force_start = 1;
       /* IT may overshoot PT if text at PT is invisible.  */
       else if (IT_CHARPOS (it) > PT && CHARPOS (startp) <= PT)
	w->force_start = 1;

w->force_start 1 will cause redisplay to honor the start position set up
by `recenter' despite of save_excursion_restore's Fgoto_char.

     }

But I don't have the slightest idea how calling

	 (set-window-start (selected-window) ,start t)

would remedy this.  Eli will soon teach us a lesson here.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 13:40   ` Bill Brodie
  2012-08-10 14:47     ` martin rudalics
@ 2012-08-10 15:04     ` Stefan Monnier
  2012-08-10 15:46       ` Bill Brodie
  2012-08-10 16:46       ` martin rudalics
  2012-08-10 18:58     ` Eli Zaretskii
  2 siblings, 2 replies; 25+ messages in thread
From: Stefan Monnier @ 2012-08-10 15:04 UTC (permalink / raw)
  To: Bill Brodie; +Cc: 12170

> (progn
>    (defun f (n)
>      (save-excursion (forward-line (- n)) (recenter 0)))
>    (let ((buffer (switch-to-buffer "foo"))
>          (height (1- (window-height (get-buffer-window "foo")))))
>      (insert-char 10 (* height 2))
>      (let ((pt (point)))
>        (f height)
>        (redisplay)
>        (message "height %s old %s new %s" height pt (point)))))

As mentioned by Martin, this is a misunderstanding about what
save-excursion does and what `point' is.

Every buffer can have many different `point's (it basically has one per
window, accessible via `window-point' and changeable via
`set-window-point', plus one for itself, called `point').
`save-excursion' preserves only `point'.
`recenter' changes `window-point'.

But `point' and `window-point' are linked (point is set to window-point
and vice-versa in various occasions), so they're often confused.

It seems your real problem is not that `point' changes but that the
cursor ends up in a different position than the one you wanted (the
cursor position, is represented by `window-point' rather than by
`point'), right?

If so, you want to preserve window-point.  And there's nothing quite
like save-excursion to preserve window-point.  You can try
save-window-excursion, tho it will do a lot more than you asked for.
Or otherwise manually read window-point at the beginning and
set-window-point at the end.


        Stefan





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 15:04     ` Stefan Monnier
@ 2012-08-10 15:46       ` Bill Brodie
  2012-08-10 16:46         ` martin rudalics
  2012-08-10 16:46       ` martin rudalics
  1 sibling, 1 reply; 25+ messages in thread
From: Bill Brodie @ 2012-08-10 15:46 UTC (permalink / raw)
  To: 'Stefan Monnier'; +Cc: 12170

Thanks, Stefan.

The purpose of the original code was to scroll the window so that a previous
section marker is at the top of the window only if it will fit without
moving point:

(defun f ()
   (interactive)
   (save-excursion
     (re-search-backward "^__")
     (beginning-of-line)
     (recenter 0)))

I was surprised to discover that this fails if the span of lines from the
previous section marker to the current position, inclusive, just barely
won't fit in the window.

Perhaps the moral is that `save-excursion' should not enclose any code,
including `recenter', that affects how buffers are displayed within windows.

Bill

-----Original Message-----

> (progn
>    (defun f (n)
>      (save-excursion (forward-line (- n)) (recenter 0)))
>    (let ((buffer (switch-to-buffer "foo"))
>          (height (1- (window-height (get-buffer-window "foo")))))
>      (insert-char 10 (* height 2))
>      (let ((pt (point)))
>        (f height)
>        (redisplay)
>        (message "height %s old %s new %s" height pt (point)))))

As mentioned by Martin, this is a misunderstanding about what save-excursion
does and what `point' is.

Every buffer can have many different `point's (it basically has one per
window, accessible via `window-point' and changeable via `set-window-point',
plus one for itself, called `point').
`save-excursion' preserves only `point'.
`recenter' changes `window-point'.

But `point' and `window-point' are linked (point is set to window-point and
vice-versa in various occasions), so they're often confused.

It seems your real problem is not that `point' changes but that the cursor
ends up in a different position than the one you wanted (the cursor
position, is represented by `window-point' rather than by `point'), right?

If so, you want to preserve window-point.  And there's nothing quite like
save-excursion to preserve window-point.  You can try save-window-excursion,
tho it will do a lot more than you asked for.
Or otherwise manually read window-point at the beginning and
set-window-point at the end.


        Stefan






^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 14:47     ` martin rudalics
@ 2012-08-10 16:18       ` Stefan Monnier
  2012-08-10 16:47         ` martin rudalics
  2012-08-10 19:01       ` Eli Zaretskii
  1 sibling, 1 reply; 25+ messages in thread
From: Stefan Monnier @ 2012-08-10 16:18 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, Bill Brodie

> But I don't have the slightest idea how calling
> 	 (set-window-start (selected-window) ,start t)
> would remedy this.  Eli will soon teach us a lesson here.

The redisplay tries to make sure (window-)point is visible, which can
either be done by changing (window-)point or by changing window-start.

The first happens when you use a scrolling operation (which sets
window-start), the other happens in response to normal navigation in
the buffer.

Knowing which to do when is not obvious, but IIUC if window-start has
not been changed, then the redisplay assumes that it is free to change
(window-)point, whereas otherwise it will assume that it should not
change (window-)point but can instead change window-start.


        Stefan





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 15:04     ` Stefan Monnier
  2012-08-10 15:46       ` Bill Brodie
@ 2012-08-10 16:46       ` martin rudalics
  1 sibling, 0 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-10 16:46 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 12170, Bill Brodie

 > As mentioned by Martin, this is a misunderstanding about what
 > save-excursion does and what `point' is.

As a matter of fact, I thought so initially but did not mention it.

 > Every buffer can have many different `point's (it basically has one per
 > window, accessible via `window-point' and changeable via
 > `set-window-point', plus one for itself, called `point').
 > `save-excursion' preserves only `point'.
 > `recenter' changes `window-point'.

The scenario shows up with one single window showing that buffer.

 > But `point' and `window-point' are linked (point is set to window-point
 > and vice-versa in various occasions), so they're often confused.
 >
 > It seems your real problem is not that `point' changes but that the
 > cursor ends up in a different position than the one you wanted (the
 > cursor position, is represented by `window-point' rather than by
 > `point'), right?
 >
 > If so, you want to preserve window-point.  And there's nothing quite
 > like save-excursion to preserve window-point.  You can try
 > save-window-excursion, tho it will do a lot more than you asked for.
 > Or otherwise manually read window-point at the beginning and
 > set-window-point at the end.

Neither of these cut it.  Maybe the purpose of `recenter' is to
overrule, if necessary, any further point movement until the next
redisplay.  But I still don't understand how putting in a call of
`set-window-start' with NOFORCE t can calm it.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 15:46       ` Bill Brodie
@ 2012-08-10 16:46         ` martin rudalics
  0 siblings, 0 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-10 16:46 UTC (permalink / raw)
  To: Bill Brodie; +Cc: 12170

 > Perhaps the moral is that `save-excursion' should not enclose any code,
 > including `recenter', that affects how buffers are displayed within windows.

Ideally, code should not call `recenter' unless it's sure that no
point-changing code follows it before the next redisplay.  Or better,
code should call `recenter' iff it definitely wants to overrule any
point-changing code that follows it.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 16:18       ` Stefan Monnier
@ 2012-08-10 16:47         ` martin rudalics
  2012-08-10 17:07           ` Stefan Monnier
  0 siblings, 1 reply; 25+ messages in thread
From: martin rudalics @ 2012-08-10 16:47 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 12170, Bill Brodie

 > Knowing which to do when is not obvious, but IIUC if window-start has
 > not been changed, then the redisplay assumes that it is free to change
 > (window-)point, whereas otherwise it will assume that it should not
 > change (window-)point but can instead change window-start.

IIUC redisplay_window respects the window start position specified in
w->start provided w->force_start or w->frozen_window_start_p hold.
Scrolling, recentering, and things like `move-to-window-line' set the
former (frozen_window_start_p seems purely internal).  The question is
whether these should prevail subsequent point movement or, subsequent
point movement should prevail and reset these flags.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 16:47         ` martin rudalics
@ 2012-08-10 17:07           ` Stefan Monnier
  0 siblings, 0 replies; 25+ messages in thread
From: Stefan Monnier @ 2012-08-10 17:07 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, Bill Brodie

>> Knowing which to do when is not obvious, but IIUC if window-start has
>> not been changed, then the redisplay assumes that it is free to change
>> (window-)point, whereas otherwise it will assume that it should not
>> change (window-)point but can instead change window-start.

> IIUC redisplay_window respects the window start position specified in
> w->start provided w->force_start or w->frozen_window_start_p hold.
> Scrolling, recentering, and things like `move-to-window-line' set the
> former (frozen_window_start_p seems purely internal).  The question is
> whether these should prevail subsequent point movement or, subsequent
> point movement should prevail and reset these flags.

Point movement can't convenient reset those flags because it's very
frequent (so speed matters) and because those flags are about windows,
whereas point movement is only indirectly related to windows (and only
if the selected window happens to display the current buffer).


        Stefan





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 13:40   ` Bill Brodie
  2012-08-10 14:47     ` martin rudalics
  2012-08-10 15:04     ` Stefan Monnier
@ 2012-08-10 18:58     ` Eli Zaretskii
  2012-08-11  9:31       ` martin rudalics
  2 siblings, 1 reply; 25+ messages in thread
From: Eli Zaretskii @ 2012-08-10 18:58 UTC (permalink / raw)
  To: Bill Brodie; +Cc: 12170

> From: "Bill Brodie" <wbrodie@panix.com>
> Date: Fri, 10 Aug 2012 09:40:24 -0400
> Cc: 12170@debbugs.gnu.org
> 
> (1) By "window height", I meant the number of lines displayed for the actual
> buffer, not counting the mode line or minibuffer.  It turns out this is one
> less than the value returned by `window-height'.

As documented, window-height returns the height of the window
including the mode line (and the header line, if there is any).  So
the above is expected.





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 14:47     ` martin rudalics
  2012-08-10 16:18       ` Stefan Monnier
@ 2012-08-10 19:01       ` Eli Zaretskii
  2012-08-11  9:32         ` martin rudalics
  1 sibling, 1 reply; 25+ messages in thread
From: Eli Zaretskii @ 2012-08-10 19:01 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, wbrodie

> Date: Fri, 10 Aug 2012 16:47:09 +0200
> From: martin rudalics <rudalics@gmx.at>
> Cc: 12170@debbugs.gnu.org
> 
>    (defun f (n)
>       (save-this-window-excursion (forward-line (- n)) (recenter 0)))
> 
>     (let ((buffer (switch-to-buffer "foo"))
>           (height (1- (window-height (get-buffer-window "foo")))))
>       (insert-char 10 (* height 2))
>       (let ((pt (point)))
>         (f height)
>         (redisplay)
>         (message "height %s old %s new %s" height pt (point)))))
> 
> so I'd suspect the culprit somewhere in redisplay_window's code

By saying "culprit" you mean you think this is a bug?  I think Stefan
explained why it isn't.

>    if (w->optional_new_start
> 
> w->optional_new_start is 1 from `recenter'
> 
>        && CHARPOS (startp) >= BEGV
>        && CHARPOS (startp) <= ZV)
>      {
>        w->optional_new_start = 0;
>        start_display (&it, w, startp);
>        move_it_to (&it, PT, 0, it.last_visible_y, -1,
> 		  MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
>        if (IT_CHARPOS (it) == PT)
> 	w->force_start = 1;
>        /* IT may overshoot PT if text at PT is invisible.  */
>        else if (IT_CHARPOS (it) > PT && CHARPOS (startp) <= PT)
> 	w->force_start = 1;
> 
> w->force_start 1 will cause redisplay to honor the start position set up
> by `recenter'

Only if point will be visible when window is displayed starting at
startp.

> But I don't have the slightest idea how calling
> 
> 	 (set-window-start (selected-window) ,start t)
> 
> would remedy this.  Eli will soon teach us a lesson here.

Not sure there's a lesson here to learn, but set-window-start sets the
w->force_start flag unconditionally, so it will hold even if point is
not in the displayed portion of the window (i.e., point will move).
Does that explain things?






^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 18:58     ` Eli Zaretskii
@ 2012-08-11  9:31       ` martin rudalics
  0 siblings, 0 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-11  9:31 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 12170, Bill Brodie

 >> (1) By "window height", I meant the number of lines displayed for the actual
 >> buffer, not counting the mode line or minibuffer.  It turns out this is one
 >> less than the value returned by `window-height'.
 >
 > As documented, window-height returns the height of the window
 > including the mode line (and the header line, if there is any).  So
 > the above is expected.

To avoid this confusion `window-total-height' should be used instead of
`window-height'.  The "number of lines displayed for the actual buffer"
is returned by the function `window-body-height'.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-10 19:01       ` Eli Zaretskii
@ 2012-08-11  9:32         ` martin rudalics
  2012-08-11 11:11           ` Eli Zaretskii
  0 siblings, 1 reply; 25+ messages in thread
From: martin rudalics @ 2012-08-11  9:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 12170, wbrodie

 >>    (defun f (n)
 >>       (save-this-window-excursion (forward-line (- n)) (recenter 0)))
 >>
 >>     (let ((buffer (switch-to-buffer "foo"))
 >>           (height (1- (window-height (get-buffer-window "foo")))))
 >>       (insert-char 10 (* height 2))
 >>       (let ((pt (point)))
 >>         (f height)
 >>         (redisplay)
 >>         (message "height %s old %s new %s" height pt (point)))))
 >>
 >> so I'd suspect the culprit somewhere in redisplay_window's code
 >
 > By saying "culprit" you mean you think this is a bug?

I suppose so, yes.

 > I think Stefan
 > explained why it isn't.

With the OP's last recipe, `point' moves to the position implied by the
`recenter' call.  With the `save-this-window-excursion' macro I gave,
`point' moves to the position implied by that macro instead and the
position implied by the `recenter' call is ignored.  So here something
unexplained happens.

If we wanted to document the current behavior, we'd have to tell users
that if they call any of the window-related movement functions like
`recenter', `move-to-window-line', or `set-window-start' with a nil
NOFORCE argument, then `point' may move after the next redisplay in
order to honor the window start position implied by the last of these
functions executed.  Do you agree so far?

If you agree, then we'd have to explain why a subsequent invocation of
`set-window-start' with NOFORCE t can override the setting of the window
start position implied by the last invocation of one of the functions
mentioned above.

 >> w->force_start 1 will cause redisplay to honor the start position set up
 >> by `recenter'
 >
 > Only if point will be visible when window is displayed starting at
 > startp.

I completely miss you here.

 >> But I don't have the slightest idea how calling
 >>
 >> 	 (set-window-start (selected-window) ,start t)
 >>
 >> would remedy this.  Eli will soon teach us a lesson here.
 >
 > Not sure there's a lesson here to learn, but set-window-start sets the
 > w->force_start flag unconditionally,

IMHO

   if (NILP (noforce))
     w->force_start = 1;

means "conditionally".

 > so it will hold even if point is
 > not in the displayed portion of the window (i.e., point will move).
 > Does that explain things?

Not yet ;-)

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11  9:32         ` martin rudalics
@ 2012-08-11 11:11           ` Eli Zaretskii
  2012-08-11 14:22             ` martin rudalics
  0 siblings, 1 reply; 25+ messages in thread
From: Eli Zaretskii @ 2012-08-11 11:11 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, wbrodie

> Date: Sat, 11 Aug 2012 11:32:29 +0200
> From: martin rudalics <rudalics@gmx.at>
> CC: wbrodie@panix.com, 12170@debbugs.gnu.org
> 
>  >>    (defun f (n)
>  >>       (save-this-window-excursion (forward-line (- n)) (recenter 0)))
>  >>
>  >>     (let ((buffer (switch-to-buffer "foo"))
>  >>           (height (1- (window-height (get-buffer-window "foo")))))
>  >>       (insert-char 10 (* height 2))
>  >>       (let ((pt (point)))
>  >>         (f height)
>  >>         (redisplay)
>  >>         (message "height %s old %s new %s" height pt (point)))))
>  >>
>  >> so I'd suspect the culprit somewhere in redisplay_window's code
>  >
>  > By saying "culprit" you mean you think this is a bug?
> 
> I suppose so, yes.
> 
>  > I think Stefan
>  > explained why it isn't.
> 
> With the OP's last recipe, `point' moves to the position implied by the
> `recenter' call.  With the `save-this-window-excursion' macro I gave,
> `point' moves to the position implied by that macro instead and the
> position implied by the `recenter' call is ignored.  So here something
> unexplained happens.

Your recipe also calls set-window-point, which moves point on its own,
and also does this:

  /* We have to make sure that redisplay updates the window to show
     the new value of point.  */
  if (!EQ (window, selected_window))
    ++windows_or_buffers_changed;

The windows_or_buffers_changed flag will force a thorough redisplay.

Given this, do we still have something unexplained?

> If we wanted to document the current behavior, we'd have to tell users
> that if they call any of the window-related movement functions like
> `recenter', `move-to-window-line', or `set-window-start' with a nil
> NOFORCE argument, then `point' may move after the next redisplay in
> order to honor the window start position implied by the last of these
> functions executed.  Do you agree so far?

I guess.

> If you agree, then we'd have to explain why a subsequent invocation of
> `set-window-start' with NOFORCE t can override the setting of the window
> start position implied by the last invocation of one of the functions
> mentioned above.

You didn't just call set-window-start, you also called
set-window-point.  If I remove that second call, the result of your
code is very different, and the window start position as the macro set
it is still in effect when control is returned.

>  >> w->force_start 1 will cause redisplay to honor the start position set up
>  >> by `recenter'
>  >
>  > Only if point will be visible when window is displayed starting at
>  > startp.
> 
> I completely miss you here.

I meant this code:

       w->optional_new_start = 0;
       start_display (&it, w, startp);
       move_it_to (&it, PT, 0, it.last_visible_y, -1,
		  MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
       if (IT_CHARPOS (it) == PT)
	w->force_start = 1;
       /* IT may overshoot PT if text at PT is invisible.  */
       else if (IT_CHARPOS (it) > PT && CHARPOS (startp) <= PT)
	w->force_start = 1;

It only sets the force_start flag if displaying the window starting at
startp will show point visible inside the window.  The call to
move_it_to moves in a simulated display lines, and stops either at
point or at the last pixel position visible in the window, whichever
happens first.  The subsequent test that IT_CHARPOS (it) == PT
verifies that it stopped at point and not because it reached the end
of the text displayed in the window.

Ergo, sometimes setting the window start position will not be
honored.  That's what the comment above all this means when it says:

  /* If someone specified a new starting point but did not insist,
     check whether it can be used.  */

"Did not insist" means that whoever set w->optional_new_start did not
also set w->force_start.  The "check whether it can be used" part
describes what I just explained.

>  > Not sure there's a lesson here to learn, but set-window-start sets the
>  > w->force_start flag unconditionally,
> 
> IMHO
> 
>    if (NILP (noforce))
>      w->force_start = 1;
> 
> means "conditionally".

Right.  So my current theory is that set-window-point is what makes
the difference.





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 11:11           ` Eli Zaretskii
@ 2012-08-11 14:22             ` martin rudalics
  2012-08-11 15:10               ` Eli Zaretskii
  0 siblings, 1 reply; 25+ messages in thread
From: martin rudalics @ 2012-08-11 14:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 12170, wbrodie

 > Your recipe also calls set-window-point, which moves point on its own,
 > and also does this:
 >
 >   /* We have to make sure that redisplay updates the window to show
 >      the new value of point.  */
 >   if (!EQ (window, selected_window))
 >     ++windows_or_buffers_changed;
 >
 > The windows_or_buffers_changed flag will force a thorough redisplay.
 >
 > Given this, do we still have something unexplained?

Yes, because `set-window-point' doesn't set windows_or_buffers_changed
in the case at hand since the window is the selected window.  Or am I
missing something?

 >> If you agree, then we'd have to explain why a subsequent invocation of
 >> `set-window-start' with NOFORCE t can override the setting of the window
 >> start position implied by the last invocation of one of the functions
 >> mentioned above.
 >
 > You didn't just call set-window-start, you also called
 > set-window-point.  If I remove that second call, the result of your
 > code is very different, and the window start position as the macro set
 > it is still in effect when control is returned.

But `set-window-point' should be equivalent to `goto-char' here because
the window is the selected window.

 >>  >> w->force_start 1 will cause redisplay to honor the start position set up
 >>  >> by `recenter'
 >>  >
 >>  > Only if point will be visible when window is displayed starting at
 >>  > startp.
 >>
 >> I completely miss you here.
 >
 > I meant this code:
 >
 >        w->optional_new_start = 0;
 >        start_display (&it, w, startp);
 >        move_it_to (&it, PT, 0, it.last_visible_y, -1,
 > 		  MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
 >        if (IT_CHARPOS (it) == PT)
 > 	w->force_start = 1;
 >        /* IT may overshoot PT if text at PT is invisible.  */
 >        else if (IT_CHARPOS (it) > PT && CHARPOS (startp) <= PT)
 > 	w->force_start = 1;
 >
 > It only sets the force_start flag if displaying the window starting at
 > startp will show point visible inside the window.  The call to
 > move_it_to moves in a simulated display lines, and stops either at
 > point or at the last pixel position visible in the window, whichever
 > happens first.  The subsequent test that IT_CHARPOS (it) == PT
 > verifies that it stopped at point and not because it reached the end
 > of the text displayed in the window.
 >
 > Ergo, sometimes setting the window start position will not be
 > honored.  That's what the comment above all this means when it says:
 >
 >   /* If someone specified a new starting point but did not insist,
 >      check whether it can be used.  */
 >
 > "Did not insist" means that whoever set w->optional_new_start did not
 > also set w->force_start.  The "check whether it can be used" part
 > describes what I just explained.

I believe you.  But it remains a mystery to me why `set-window-point'
should make a difference here.  As a matter of fact, if I do

(progn
   (defmacro save-this-window-excursion (&rest body)
     "..."
     (let ((start (make-symbol "start"))
       (point (make-symbol "point")))
       `(let ((,start (copy-marker (window-start)))
          (,point (copy-marker (window-point))))
      (save-selected-window
        (progn ,@body))
      (set-window-start (selected-window) ,start t)
      (with-current-buffer (window-buffer (selected-window))
        (goto-char ,point)))))

   (defun f (n)
      (save-this-window-excursion (forward-line (- n)) (recenter 0)))

    (let ((buffer (switch-to-buffer "foo"))
          (height (1- (window-height (get-buffer-window "foo")))))
      (insert-char 10 (* height 2))
      (let ((pt (point)))
        (f height)
        (redisplay)
        (message "height %s old %s new %s" height pt (point)))))

I get the same results as with `set-window-point'.  IMHO the
`set-window-start' call makes the difference but I don't understand why.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 14:22             ` martin rudalics
@ 2012-08-11 15:10               ` Eli Zaretskii
  2012-08-11 16:05                 ` martin rudalics
  2012-08-11 16:27                 ` Bill Brodie
  0 siblings, 2 replies; 25+ messages in thread
From: Eli Zaretskii @ 2012-08-11 15:10 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, wbrodie

> Date: Sat, 11 Aug 2012 16:22:39 +0200
> From: martin rudalics <rudalics@gmx.at>
> CC: wbrodie@panix.com, 12170@debbugs.gnu.org
> 
> (progn
>    (defmacro save-this-window-excursion (&rest body)
>      "..."
>      (let ((start (make-symbol "start"))
>        (point (make-symbol "point")))
>        `(let ((,start (copy-marker (window-start)))
>           (,point (copy-marker (window-point))))
>       (save-selected-window
>         (progn ,@body))
>       (set-window-start (selected-window) ,start t)
>       (with-current-buffer (window-buffer (selected-window))
>         (goto-char ,point)))))
> 
>    (defun f (n)
>       (save-this-window-excursion (forward-line (- n)) (recenter 0)))
> 
>     (let ((buffer (switch-to-buffer "foo"))
>           (height (1- (window-height (get-buffer-window "foo")))))
>       (insert-char 10 (* height 2))
>       (let ((pt (point)))
>         (f height)
>         (redisplay)
>         (message "height %s old %s new %s" height pt (point)))))
> 
> I get the same results as with `set-window-point'.  IMHO the
> `set-window-start' call makes the difference but I don't understand why.

Maybe.  If it's really important, I could try looking at this in the
debugger, although doing so to investigate such situations is
notoriously hard, because redisplay is entered several times.

IMO, save-excursion is simply not designed to make sure display isn't
changed in such cases.  It's for excursions into other portions of the
buffer for processing those other parts, without affecting display.
If the processing also scrolls the screen or calls recenter or
otherwise affects the display, all bets are off, because scrolling can
legitimately move point, and when that happens, it is no longer clear
that restoring point should take precedence over window-start etc.





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 15:10               ` Eli Zaretskii
@ 2012-08-11 16:05                 ` martin rudalics
  2012-08-11 16:31                   ` Eli Zaretskii
  2012-08-11 16:27                 ` Bill Brodie
  1 sibling, 1 reply; 25+ messages in thread
From: martin rudalics @ 2012-08-11 16:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 12170, wbrodie

 > Maybe.  If it's really important,

It's not.

 > I could try looking at this in the
 > debugger, although doing so to investigate such situations is
 > notoriously hard, because redisplay is entered several times.
 >
 > IMO, save-excursion is simply not designed to make sure display isn't
 > changed in such cases.

Agreed.  But `save-window-excursion' doesn't handle this case either.
So the difference should be in one of the four assignments

   w->start_at_line_beg = 0;
   w->update_mode_line = 1;
   w->last_modified = 0;
   w->last_overlay_modified = 0;

and I'd obviously vote for the first one.  Interesting side-effect.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 15:10               ` Eli Zaretskii
  2012-08-11 16:05                 ` martin rudalics
@ 2012-08-11 16:27                 ` Bill Brodie
  1 sibling, 0 replies; 25+ messages in thread
From: Bill Brodie @ 2012-08-11 16:27 UTC (permalink / raw)
  To: 'Eli Zaretskii', 'martin rudalics'; +Cc: 12170

From my point of view (the OP), I've generally used save-excursion to
assemble or manipulate information elsewhere in the buffer or in other
buffers, without explicit reference to window displays.  This seems like a
natural restriction.

I've modified my original code to perform the window operations outside the
form.  Thanks for the illuminating discussion.

-----Original Message-----
From: Eli Zaretskii [mailto:eliz@gnu.org] 

IMO, save-excursion is simply not designed to make sure display isn't
changed in such cases.  It's for excursions into other portions of the
buffer for processing those other parts, without affecting display.
If the processing also scrolls the screen or calls recenter or
otherwise affects the display, all bets are off, because scrolling can
legitimately move point, and when that happens, it is no longer clear
that restoring point should take precedence over window-start etc.






^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 16:05                 ` martin rudalics
@ 2012-08-11 16:31                   ` Eli Zaretskii
  2012-08-11 16:49                     ` Eli Zaretskii
  0 siblings, 1 reply; 25+ messages in thread
From: Eli Zaretskii @ 2012-08-11 16:31 UTC (permalink / raw)
  To: martin rudalics; +Cc: 12170, wbrodie

> Date: Sat, 11 Aug 2012 18:05:14 +0200
> From: martin rudalics <rudalics@gmx.at>
> CC: wbrodie@panix.com, 12170@debbugs.gnu.org
> 
> So the difference should be in one of the four assignments
> 
>    w->start_at_line_beg = 0;
>    w->update_mode_line = 1;
>    w->last_modified = 0;
>    w->last_overlay_modified = 0;
> 
> and I'd obviously vote for the first one.  Interesting side-effect.

FWIW, there are only 2 places in redisplay that look at
w->start_at_line_beg, but neither your nor Bill's recipe don't
exercise the code of either of these two places.





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 16:31                   ` Eli Zaretskii
@ 2012-08-11 16:49                     ` Eli Zaretskii
  2012-08-12 10:31                       ` martin rudalics
  0 siblings, 1 reply; 25+ messages in thread
From: Eli Zaretskii @ 2012-08-11 16:49 UTC (permalink / raw)
  To: rudalics; +Cc: 12170

> Date: Sat, 11 Aug 2012 19:31:53 +0300
> From: Eli Zaretskii <eliz@gnu.org>
> Cc: 12170@debbugs.gnu.org, wbrodie@panix.com
> 
> > Date: Sat, 11 Aug 2012 18:05:14 +0200
> > From: martin rudalics <rudalics@gmx.at>
> > CC: wbrodie@panix.com, 12170@debbugs.gnu.org
> > 
> > So the difference should be in one of the four assignments
> > 
> >    w->start_at_line_beg = 0;
> >    w->update_mode_line = 1;
> >    w->last_modified = 0;
> >    w->last_overlay_modified = 0;
> > 
> > and I'd obviously vote for the first one.  Interesting side-effect.
> 
> FWIW, there are only 2 places in redisplay that look at
> w->start_at_line_beg, but neither your nor Bill's recipe don't
> exercise the code of either of these two places.

As another FWIW, running the recipe with save-window-excursion shows
that set-window-configuration called after the body tries to set the
window start at position 1 and the window point at position 67.  But
the last position in a 33-line window that starts at position 1 is 33,
so these two values cannot be satisfied at once.





^ permalink raw reply	[flat|nested] 25+ messages in thread

* bug#12170: save-excursion fails boundary case with recenter
  2012-08-11 16:49                     ` Eli Zaretskii
@ 2012-08-12 10:31                       ` martin rudalics
  0 siblings, 0 replies; 25+ messages in thread
From: martin rudalics @ 2012-08-12 10:31 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 12170

 > As another FWIW, running the recipe with save-window-excursion shows
 > that set-window-configuration called after the body tries to set the
 > window start at position 1 and the window point at position 67.  But
 > the last position in a 33-line window that starts at position 1 is 33,
 > so these two values cannot be satisfied at once.

Due to the fact that we use markers when storing start and point
positions.  Usually the point position prevails.  But apparently
`recenter' is stronger.

martin





^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2012-08-12 10:31 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-08-10  1:23 bug#12170: save-excursion fails boundary case with recenter Bill Brodie
2012-08-10  9:34 ` martin rudalics
2012-08-10  9:50   ` Bastien
2012-08-10 13:03     ` martin rudalics
2012-08-10 13:40   ` Bill Brodie
2012-08-10 14:47     ` martin rudalics
2012-08-10 16:18       ` Stefan Monnier
2012-08-10 16:47         ` martin rudalics
2012-08-10 17:07           ` Stefan Monnier
2012-08-10 19:01       ` Eli Zaretskii
2012-08-11  9:32         ` martin rudalics
2012-08-11 11:11           ` Eli Zaretskii
2012-08-11 14:22             ` martin rudalics
2012-08-11 15:10               ` Eli Zaretskii
2012-08-11 16:05                 ` martin rudalics
2012-08-11 16:31                   ` Eli Zaretskii
2012-08-11 16:49                     ` Eli Zaretskii
2012-08-12 10:31                       ` martin rudalics
2012-08-11 16:27                 ` Bill Brodie
2012-08-10 15:04     ` Stefan Monnier
2012-08-10 15:46       ` Bill Brodie
2012-08-10 16:46         ` martin rudalics
2012-08-10 16:46       ` martin rudalics
2012-08-10 18:58     ` Eli Zaretskii
2012-08-11  9:31       ` martin rudalics

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).