all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* Add function to rotate/transpose all windows
@ 2024-09-24 13:45 pranshu sharma
  2024-09-24 13:53 ` Eli Zaretskii
                   ` (2 more replies)
  0 siblings, 3 replies; 111+ messages in thread
From: pranshu sharma @ 2024-09-24 13:45 UTC (permalink / raw)
  To: emacs-devel


An useful for function for C-x w map is to add a function to rotate
windows.

What I mean by this is basiclly rebuilt the window tree, but do vertical
split where a horizonal, and vice versa.

For example if we rotate the the frame with the windows A B C:
- | A | B |       | A |   |
- |-------|   - > |---| C |
- |   C   |       | B |   |

I think this is extremly useful, as sometimes for example I open a
window with a horizontal split, and the lines are rather long, then I
have to manually toggle the window split to vertical.

An example of this:
https://www.emacswiki.org/emacs/TransposeFrame
The code in this has the following limitations:
- Does not work in emacs 29.1
- No good way to transpose only specific parts of window tree



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

* Re: Add function to rotate/transpose all windows
  2024-09-24 13:45 Add function to rotate/transpose all windows pranshu sharma
@ 2024-09-24 13:53 ` Eli Zaretskii
  2024-09-25  8:05   ` martin rudalics
  2024-09-24 17:40 ` Petteri Hintsanen
  2024-09-24 19:34 ` Charles Choi
  2 siblings, 1 reply; 111+ messages in thread
From: Eli Zaretskii @ 2024-09-24 13:53 UTC (permalink / raw)
  To: pranshu sharma, martin rudalics; +Cc: emacs-devel

> From: pranshu sharma <pranshusharma366@gmail.com>
> Date: Tue, 24 Sep 2024 23:45:42 +1000
> 
> 
> An useful for function for C-x w map is to add a function to rotate
> windows.
> 
> What I mean by this is basiclly rebuilt the window tree, but do vertical
> split where a horizonal, and vice versa.
> 
> For example if we rotate the the frame with the windows A B C:
> - | A | B |       | A |   |
> - |-------|   - > |---| C |
> - |   C   |       | B |   |
> 
> I think this is extremly useful, as sometimes for example I open a
> window with a horizontal split, and the lines are rather long, then I
> have to manually toggle the window split to vertical.
> 
> An example of this:
> https://www.emacswiki.org/emacs/TransposeFrame
> The code in this has the following limitations:
> - Does not work in emacs 29.1
> - No good way to transpose only specific parts of window tree

Thanks.

Martin, any comments or suggestions?



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

* Re: Add function to rotate/transpose all windows
  2024-09-24 13:45 Add function to rotate/transpose all windows pranshu sharma
  2024-09-24 13:53 ` Eli Zaretskii
@ 2024-09-24 17:40 ` Petteri Hintsanen
  2024-09-24 19:34 ` Charles Choi
  2 siblings, 0 replies; 111+ messages in thread
From: Petteri Hintsanen @ 2024-09-24 17:40 UTC (permalink / raw)
  To: pranshu sharma; +Cc: emacs-devel

pranshu sharma <pranshusharma366@gmail.com> writes:

> An useful for function for C-x w map is to add a function to rotate
> windows.
>
> What I mean by this is basiclly rebuilt the window tree, but do vertical
> split where a horizonal, and vice versa.
>
> For example if we rotate the the frame with the windows A B C:
> - | A | B |       | A |   |
> - |-------|   - > |---| C |
> - |   C   |       | B |   |

I agree, this would be a useful feature.

Despite a hint of self-promotion I'd like to point out a minor mode I
wrote a long time ago: http://iki.fi/petterih/cwm-mode.html
It can do something like that, though not exactly.
Having a general facility built into Emacs would be better.

-- 
Petteri



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

* Re: Add function to rotate/transpose all windows
  2024-09-24 13:45 Add function to rotate/transpose all windows pranshu sharma
  2024-09-24 13:53 ` Eli Zaretskii
  2024-09-24 17:40 ` Petteri Hintsanen
@ 2024-09-24 19:34 ` Charles Choi
  2024-09-25  2:00   ` Emanuel Berg
  2024-09-25  7:00   ` pranshu sharma
  2 siblings, 2 replies; 111+ messages in thread
From: Charles Choi @ 2024-09-24 19:34 UTC (permalink / raw)
  To: emacs-devel; +Cc: pranshu sharma

Hi folks!

As a side note, I received an issue that a package that I had recently published on MELPA required a dependency on transpose-frame. Investigating further, it seems that transpose-frame was ad-hoc packaged and it is unclear who actually maintains this code.

https://github.com/emacsorphanage/transpose-frame/blob/master/transpose-frame.el

My observation is that what transpose-frame is trying to do is so useful, that it would nice to have such behavior be “built-in” to Emacs core. I am not saying that this package should be included “as-is” though. I leave it to the core team to determine how to best implement this behavior.


All my best - 
Charles

—
Charles Y. Choi, Ph.D.
kickingvegas@gmail.com





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

* Re: Add function to rotate/transpose all windows
  2024-09-24 19:34 ` Charles Choi
@ 2024-09-25  2:00   ` Emanuel Berg
  2024-09-25  7:00   ` pranshu sharma
  1 sibling, 0 replies; 111+ messages in thread
From: Emanuel Berg @ 2024-09-25  2:00 UTC (permalink / raw)
  To: emacs-devel

Charles Choi wrote:

> As a side note, I received an issue that a package that
> I had recently published on MELPA required a dependency on
> transpose-frame. Investigating further, it seems that
> transpose-frame was ad-hoc packaged and it is unclear who
> actually maintains this code.
>
> https://github.com/emacsorphanage/transpose-frame/blob/master/transpose-frame.el
>
> My observation is that what transpose-frame is trying to do
> is so useful, that it would nice to have such behavior be
> "built-in" to Emacs core. I am not saying that this package
> should be included "as-is" though. I leave it to the core
> team to determine how to best implement this behavior.

There is a lot of transpose already in core Emacs:

  transpose-chars
  transpose-lines
  transpose-paragraphs
  transpose-regions
  transpose-sentences
  transpose-sexps
  transpose-sexps-default-function
  transpose-subr
  transpose-subr-1
  transpose-words

Slime has a general list transposer:

(defun slime-transpose-lists (list-of-lists)
  (let ((ncols (length (car list-of-lists))))
    (cl-loop for col-index below ncols
             collect (cl-loop for row in list-of-lists
                              collect (elt row col-index)))))

And I have a transposer that works for nested elements, here:
  https://dataswamp.org/~incal/bad/class/elem/bad-rotate.el

-- 
underground experts united
https://dataswamp.org/~incal




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

* Re: Add function to rotate/transpose all windows
  2024-09-24 19:34 ` Charles Choi
  2024-09-25  2:00   ` Emanuel Berg
@ 2024-09-25  7:00   ` pranshu sharma
  1 sibling, 0 replies; 111+ messages in thread
From: pranshu sharma @ 2024-09-25  7:00 UTC (permalink / raw)
  To: Charles Choi; +Cc: emacs-devel

Charles Choi <kickingvegas@gmail.com> writes:

> Hi folks!
>
> As a side note, I received an issue that a package that I had recently
> published on MELPA required a dependency on
> transpose-frame. Investigating further, it seems that transpose-frame
> was ad-hoc packaged and it is unclear who actually maintains this
> code.
>

I managed to get the name of the author, Irie Shinsuke, from:
https://code.launchpad.net/~irie/+junk/transpose-frame.el

However I could not find the email.

The last comit was in early 2011.

> https://github.com/emacsorphanage/transpose-frame/blob/master/transpose-frame.el
>
> My observation is that what transpose-frame is trying to do is so
> useful, that it would nice to have such behavior be “built-in” to
> Emacs core. I am not saying that this package should be included
> “as-is” though. I leave it to the core team to determine how to best
> implement this behavior.
>
>
> All my best - 
> Charles
>
> —
> Charles Y. Choi, Ph.D.
> kickingvegas@gmail.com



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

* Re: Add function to rotate/transpose all windows
  2024-09-24 13:53 ` Eli Zaretskii
@ 2024-09-25  8:05   ` martin rudalics
  2024-09-25  8:34     ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-25  8:05 UTC (permalink / raw)
  To: Eli Zaretskii, pranshu sharma; +Cc: emacs-devel

 >> https://www.emacswiki.org/emacs/TransposeFrame
 >> The code in this has the following limitations:
 >> - Does not work in emacs 29.1

It works here when I remove the

(window-redisplay-end-trigger tree)

(set-window-redisplay-end-trigger window (pop config))

lines in such versions.

 >> - No good way to transpose only specific parts of window tree

Then the FRAME arguments should probably become WINDOW-OR-FRAME and
accept arbitrary internal windows as value.

 > Thanks.
 >
 > Martin, any comments or suggestions?

The problem of any such package is that Emacs never provided a suitable
balance between explicit management of objects like buffers or windows
(via 'kill-buffer' and 'delete-window') and the subsequent internal
handling of these objects.

In particular, the 'delete-window'/'split-window' paradigm may fail when
window objects have been stored in variables.  Take this excerpt from
TransposeFrame

                            (if (eq (overlay-get ol 'window) orig-window)
                                (overlay-put ol 'window window))))

If this gets called within a 'save-window-excursion', then the 'window'
property of overlays may change from 'orig-window' to 'window'.  When
'save-window-excursion' exits and restores 'orig-window' and deletes
'window', these overlays will now reference a dead window.  This problem
could be partially fixed by using the 'clone-of' parameter of the
window.  But that would fail when windows are rotated a second time.

Such problems could be fixed if we were able to reference windows by
their number (or an arbitrary name) and have Lisp references to a window
use that number instead of the object.  It might, however, take
considerable time to have such a concept enter the minds of people used
to storing window identities as Lisp objects.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-25  8:05   ` martin rudalics
@ 2024-09-25  8:34     ` pranshu sharma
  2024-09-25  9:31       ` martin rudalics
  2024-09-26 14:10       ` martin rudalics
  0 siblings, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-09-25  8:34 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>> https://www.emacswiki.org/emacs/TransposeFrame
>>> The code in this has the following limitations:
>>> - Does not work in emacs 29.1
>
> It works here when I remove the
>
> (window-redisplay-end-trigger tree)
>
> (set-window-redisplay-end-trigger window (pop config))
>
> lines in such versions.
>
>>> - No good way to transpose only specific parts of window tree
>
> Then the FRAME arguments should probably become WINDOW-OR-FRAME and
> accept arbitrary internal windows as value.
>
>> Thanks.
>>
>> Martin, any comments or suggestions?
>
> The problem of any such package is that Emacs never provided a suitable
> balance between explicit management of objects like buffers or windows
> (via 'kill-buffer' and 'delete-window') and the subsequent internal
> handling of these objects.
>
> In particular, the 'delete-window'/'split-window' paradigm may fail when
> window objects have been stored in variables.  Take this excerpt from
> TransposeFrame
>
>                            (if (eq (overlay-get ol 'window) orig-window)
>                                (overlay-put ol 'window window))))
>
> If this gets called within a 'save-window-excursion', then the 'window'
> property of overlays may change from 'orig-window' to 'window'.  When
> 'save-window-excursion' exits and restores 'orig-window' and deletes
> 'window', these overlays will now reference a dead window.  This problem
> could be partially fixed by using the 'clone-of' parameter of the
> window.  But that would fail when windows are rotated a second time.

From what I understand, the main problems are happening when destroying
and recreating the window arragnment.  How about adding a function that
toggles or changes the window arrangment non recursivly, which will deal
with all the backend details, and the main rotate function can just
recursivly call that on all windows.

>
> Such problems could be fixed if we were able to reference windows by
> their number (or an arbitrary name) and have Lisp references to a window
> use that number instead of the object.  It might, however, take
> considerable time to have such a concept enter the minds of people used
> to storing window identities as Lisp objects.
>
> martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-25  8:34     ` pranshu sharma
@ 2024-09-25  9:31       ` martin rudalics
  2024-09-25 10:50         ` pranshu sharma
  2024-09-26 14:10       ` martin rudalics
  1 sibling, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-25  9:31 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 >>From what I understand, the main problems are happening when destroying
 > and recreating the window arragnment.

Right.

 > How about adding a function that
 > toggles or changes the window arrangment non recursivly, which will deal
 > with all the backend details, and the main rotate function can just
 > recursivly call that on all windows.

How would you "deal with all the backend details"?  How would you
convince Emacs that the windows B and C in the right configuration
below

- | A | B |       | A |   |
- |-------|   - > |---| C |
- |   C   |       | B |   |

are the windows B and C from the left configuration (albeit with
different parents and sizes)?

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-25  9:31       ` martin rudalics
@ 2024-09-25 10:50         ` pranshu sharma
  2024-09-25 13:53           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-25 10:50 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>>From what I understand, the main problems are happening when destroying
>> and recreating the window arragnment.
>
> Right.
>
>> How about adding a function that
>> toggles or changes the window arrangment non recursivly, which will deal
>> with all the backend details, and the main rotate function can just
>> recursivly call that on all windows.
>
> How would you "deal with all the backend details"?  How would you
> convince Emacs that the windows B and C in the right configuration
> below
>
> - | A | B |       | A |   |
> - |-------|   - > |---| C |
> - |   C   |       | B |   |
>
> are the windows B and C from the left configuration (albeit with
> different parents and sizes)?

I thought you would know.

If it's not possible, then what's wrong with rebuilding the trees from
scratch and clearly documenting that it's being done.  I mean users will
still have to do the same thing manually, which will also destroy change
the window tree.

>
> martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-25 10:50         ` pranshu sharma
@ 2024-09-25 13:53           ` martin rudalics
  2024-09-25 15:31             ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-25 13:53 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > I thought you would know.

In that case I would have told you before.

 > If it's not possible, then what's wrong with rebuilding the trees from
 > scratch and clearly documenting that it's being done.  I mean users will
 > still have to do the same thing manually, which will also destroy change
 > the window tree.

I don't think that anything would be wrong with such an approach.  So if
you intend to rewrite transpose-frame.el, I can tell you what's needed
to get that done properly within the current limitations.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-25 13:53           ` martin rudalics
@ 2024-09-25 15:31             ` pranshu sharma
  0 siblings, 0 replies; 111+ messages in thread
From: pranshu sharma @ 2024-09-25 15:31 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:


> I don't think that anything would be wrong with such an approach.  So if
> you intend to rewrite transpose-frame.el, I can tell you what's needed
> to get that done properly within the current limitations.

Yeah, if you think that this is the only practical way without having to
do some big rewrite, then I'm willing to rewrite tranpose-frame.el.



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

* Re: Add function to rotate/transpose all windows
  2024-09-25  8:34     ` pranshu sharma
  2024-09-25  9:31       ` martin rudalics
@ 2024-09-26 14:10       ` martin rudalics
  2024-09-26 14:22         ` Eli Zaretskii
  2024-09-27 10:06         ` pranshu sharma
  1 sibling, 2 replies; 111+ messages in thread
From: martin rudalics @ 2024-09-26 14:10 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

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

 >>From what I understand, the main problems are happening when destroying
 > and recreating the window arragnment.  How about adding a function that
 > toggles or changes the window arrangment non recursivly, which will deal
 > with all the backend details, and the main rotate function can just
 > recursivly call that on all windows.

I attach a function 'resurrect-window' that rotate/transpose/flip
functions can call to resurrect the previous windows after having
deleted them.  Tested with:

(let ((dead (split-window nil nil t)))
   (set-window-buffer dead "*Messages*")
   (message "%s" (next-window))
   (sit-for 2)
   (delete-window dead)
   (let ((live (split-window)))
     (resurrect-window dead live)
     (message "%s" (next-window))))

martin

[-- Attachment #2: resurrect.diff --]
[-- Type: text/x-patch, Size: 5812 bytes --]

diff --git a/src/window.c b/src/window.c
index 34968ac824f..312619737f3 100644
--- a/src/window.c
+++ b/src/window.c
@@ -5368,10 +5368,14 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  w->del_pointm = marker_position (w->pointm);
+	  w->del_start = marker_position (w->start);
+
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
 	  unchain_marker (XMARKER (w->start));
+	  wset_old_buffer (w, w->contents);
 	  wset_buffer (w, Qnil);
 	  /* Add WINDOW to table of dead windows so when killing a buffer
 	     WINDOW mentions, all references to that buffer can be removed
@@ -5437,6 +5441,97 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 
   return Qnil;
 }
+
+
+DEFUN ("resurrect-window", Fresurrect_window, Sresurrect_window, 2, 2, 0,
+       doc: /* Resurrect dead window DEAD in lieu of live window LIVE.
+DEAD must denote a dead window that was a live window before it was
+deleted.  LIVE must denote a live window.  Put the window object of DEAD
+in the place of LIVE such that the resulting window inherits LIVE's
+geometric properties and its position within the window tree while all
+other properties including the buffer and its positions, decorations and
+parameters are inherited from DEAD.
+
+DEAD must have been on the same frame as LIVE and its old buffer must be
+still live.  If LIVE was selected or its frame's selected window, select
+DEAD or make it its frame's selected window instead.  */)
+  (Lisp_Object dead, Lisp_Object live)
+{
+  struct window *l = decode_live_window (live);
+  struct window *d = decode_any_window (dead);
+  Lisp_Object frame = WINDOW_FRAME (l);
+  struct frame *f = XFRAME (frame);
+  Lisp_Object tem;
+  bool selected = EQ (live, selected_window);
+  bool frame_selected = EQ (live, f->selected_window);
+
+  if (!NILP (d->contents))
+    error ("Attempt to resurrect undead window");
+  else if (!BUFFERP (d->old_buffer))
+    error ("Dead window has no old buffer");
+  else if (!BUFFER_LIVE_P (XBUFFER (d->old_buffer)))
+    error ("Dead window's old buffer is dead");
+  else if (!EQ (l->frame, d->frame))
+    error ("Live and dead widows must be on same frame");
+
+  block_input ();
+
+  /* Copy links and geometry of LIVE into DEAD.  */
+  memcpy (&d->next, &l->next,
+	  offsetof (struct window, contents)
+	  - offsetof (struct window, next));
+  memcpy (&d->pixel_left, &l->pixel_left,
+	  offsetof (struct window, hscroll)
+	  - offsetof (struct window, pixel_left));
+
+  /* Replace LIVE in window tree with DEAD.  */
+  if (!NILP (d->next))
+    wset_prev (XWINDOW (d->next), dead);
+
+  if (!NILP (d->prev))
+    wset_next (XWINDOW (d->prev), dead);
+
+  tem = d->parent;
+  if (!NILP (tem) && EQ (XWINDOW (tem)->contents, live))
+    wset_combination (XWINDOW (tem), XWINDOW (tem)->horizontal, dead);
+
+  /* Get DEAD back its old buffer and markers.  */
+  wset_buffer (d, d->old_buffer);
+  Fset_marker (d->start, make_fixnum (d->del_start), d->contents);
+  Fset_marker (d->pointm, make_fixnum (d->del_pointm), d->contents);
+
+  /* Deal with LIVE.  */
+  wset_next (l, Qnil);  /* Don't delete l->next too.  */
+  free_window_matrices (l);
+  l->del_pointm = marker_position (l->pointm);
+  l->del_start = marker_position (l->start);
+  unshow_buffer (l);
+  unchain_marker (XMARKER (l->pointm));
+  unchain_marker (XMARKER (l->old_pointm));
+  unchain_marker (XMARKER (l->start));
+  wset_old_buffer (l, l->contents);
+  wset_buffer (l, Qnil);
+  /* Add WINDOW to table of dead windows so when killing a buffer WINDOW
+     mentions, all references to that buffer can be removed and the
+     buffer be collected.  */
+  Fputhash (make_fixnum (l->sequence_number),
+	    live, window_dead_windows_table);
+
+  /* Selection status.  */
+  if (selected)
+    Fselect_window (dead, Qt);
+  else if (frame_selected)
+    fset_selected_window (f, dead);
+
+  /* Tell redisplay.  */
+  fset_redisplay (f);
+  Vwindow_list = Qnil;
+  adjust_frame_glyphs (f);
+  unblock_input ();
+  FRAME_WINDOW_CHANGE (f) = true;
+
+  return Qnil;
+}
 \f
 /***********************************************************************
 			Resizing Mini-Windows
@@ -7712,6 +7807,9 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      w->del_pointm = marker_position (w->pointm);
+      w->del_start = marker_position (w->start);
+
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -7720,6 +7818,7 @@ delete_all_child_windows (Lisp_Object window)
 	 only, we use this slot to save the buffer for the sake of
 	 possible resurrection in Fset_window_configuration.  */
       wset_combination_limit (w, w->contents);
+      wset_old_buffer (w, w->contents);
       wset_buffer (w, Qnil);
       /* Add WINDOW to table of dead windows so when killing a buffer
 	 WINDOW mentions, all references to that buffer can be removed
@@ -9146,6 +9245,7 @@ syms_of_window (void)
   defsubr (&Sget_buffer_window);
   defsubr (&Sdelete_other_windows_internal);
   defsubr (&Sdelete_window_internal);
+  defsubr (&Sresurrect_window);
   defsubr (&Sresize_mini_window_internal);
   defsubr (&Sset_window_buffer);
   defsubr (&Srun_window_configuration_change_hook);
diff --git a/src/window.h b/src/window.h
index 335e0a3453e..41ee7f6dc18 100644
--- a/src/window.h
+++ b/src/window.h
@@ -264,6 +264,10 @@ #define WINDOW_H_INCLUDED
     int total_cols;
     int total_lines;
 
+    /* Positions of pointm and start when window was deleted.  */
+    ptrdiff_t del_pointm;
+    ptrdiff_t del_start;
+
     /* Number of columns display within the window is scrolled to the left.  */
     ptrdiff_t hscroll;
 

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

* Re: Add function to rotate/transpose all windows
  2024-09-26 14:10       ` martin rudalics
@ 2024-09-26 14:22         ` Eli Zaretskii
  2024-09-27 17:29           ` martin rudalics
  2024-09-27 10:06         ` pranshu sharma
  1 sibling, 1 reply; 111+ messages in thread
From: Eli Zaretskii @ 2024-09-26 14:22 UTC (permalink / raw)
  To: martin rudalics; +Cc: pranshusharma366, emacs-devel

> Date: Thu, 26 Sep 2024 16:10:48 +0200
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> From: martin rudalics <rudalics@gmx.at>
> 
> I attach a function 'resurrect-window' that rotate/transpose/flip
> functions can call to resurrect the previous windows after having
> deleted them.  Tested with:
> 
> (let ((dead (split-window nil nil t)))
>    (set-window-buffer dead "*Messages*")
>    (message "%s" (next-window))
>    (sit-for 2)
>    (delete-window dead)
>    (let ((live (split-window)))
>      (resurrect-window dead live)
>      (message "%s" (next-window))))

Thanks.

> +DEFUN ("resurrect-window", Fresurrect_window, Sresurrect_window, 2, 2, 0,
> +       doc: /* Resurrect dead window DEAD in lieu of live window LIVE.
> +DEAD must denote a dead window that was a live window before it was
> +deleted.  LIVE must denote a live window.  Put the window object of DEAD
> +in the place of LIVE such that the resulting window inherits LIVE's
> +geometric properties and its position within the window tree while all
> +other properties including the buffer and its positions, decorations and
> +parameters are inherited from DEAD.
> +
> +DEAD must have been on the same frame as LIVE and its old buffer must be
> +still live.  If LIVE was selected or its frame's selected window, select
> +DEAD or make it its frame's selected window instead.  */)

This doc string needs to be more detailed in what it means to "put the
window object of DEAD in the place of LIVE".  For example, it
currently keeps silent about what happens to LIVE after the call.

> +  /* Add WINDOW to table of dead windows so when killing a buffer WINDOW
> +     mentions, all references to that buffer can be removed and the
> +     buffer be collected.  */

There's no WINDOW argument.  I think you meant LIVE instead.

Finally, this needs to be documented in the ELisp manual and called
out in NEWS.



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

* Re: Add function to rotate/transpose all windows
  2024-09-26 14:10       ` martin rudalics
  2024-09-26 14:22         ` Eli Zaretskii
@ 2024-09-27 10:06         ` pranshu sharma
  2024-09-27 17:29           ` martin rudalics
  1 sibling, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-27 10:06 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>>From what I understand, the main problems are happening when destroying
>> and recreating the window arragnment.  How about adding a function that
>> toggles or changes the window arrangment non recursivly, which will deal
>> with all the backend details, and the main rotate function can just
>> recursivly call that on all windows.
>
> I attach a function 'resurrect-window' that rotate/transpose/flip
> functions can call to resurrect the previous windows after having
> deleted them.

Thanks, I tested it and it works.

I'll get back when I've rewrote transpose frame



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

* Re: Add function to rotate/transpose all windows
  2024-09-26 14:22         ` Eli Zaretskii
@ 2024-09-27 17:29           ` martin rudalics
  2024-09-28  7:52             ` pranshu sharma
                               ` (2 more replies)
  0 siblings, 3 replies; 111+ messages in thread
From: martin rudalics @ 2024-09-27 17:29 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: pranshusharma366, emacs-devel

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

 > This doc string needs to be more detailed in what it means to "put the
 > window object of DEAD in the place of LIVE".  For example, it
 > currently keeps silent about what happens to LIVE after the call.

The fact that LIVE existed at all was a misfeature.  I now put the dead
window right into the new window created by 'split-window' with the help
of a new argument.  Patch attached.  Tested with

(let ((dead (split-window nil nil t)))
   (set-window-buffer dead "*Messages*")
   (message "%s" (next-window))
   (sit-for 2)
   (delete-window dead)
   (let ((live (split-window nil nil nil nil dead)))
     (message "%s" (next-window))))

 > Finally, this needs to be documented in the ELisp manual and called
 > out in NEWS.

Sure.  I'll wait until the OP approves the concept and was able to make
use of it.

Thanks, martin

[-- Attachment #2: resurrect.diff --]
[-- Type: text/x-patch, Size: 12834 bytes --]

diff --git a/lisp/window.el b/lisp/window.el
index 07ea9584908..b96a7863a72 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -5509,7 +5509,7 @@ window--combination-resizable
       (setq sibling (window-next-sibling sibling)))
     (/ size (1+ number))))
 
-(defun split-window (&optional window size side pixelwise)
+(defun split-window (&optional window size side pixelwise refer)
   "Make a new window adjacent to WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 Return the new window which is always a live window.
@@ -5552,11 +5552,21 @@ split-window
 root of that atomic window.  The new window does not become a
 member of that atomic window.
 
-If WINDOW is live, properties of the new window like margins and
-scrollbars are inherited from WINDOW.  If WINDOW is an internal
-window, these properties as well as the buffer displayed in the
-new window are inherited from the window selected on WINDOW's
-frame.  The selected window is not changed by this function."
+If the optional fifth argument REFER is non-nil, it has to denote a
+dead, former live window on the same frame as OLD or an arbitrary live
+window.  In the first case, REFER will become the new window with
+properties like buffer, start and point, decorations and parameters as
+to the last time when it was live.  In the latter case the new window
+will inherit properties like buffer, start and point, decorations and
+parameters from REFER.
+
+If REFER is nil or omitted, then if WINDOW is live, any such properties
+are inherited from WINDOW.  If, however, WINDOW is an internal window,
+the new window will inherit these properties from the window selected on
+WINDOW's frame.
+
+The selected window and the selected window on WINDOW's frame are
+not changed by this function."
   (setq window (window-normalize-window window))
   (let* ((side (cond
 		((not side) 'below)
@@ -5596,7 +5606,7 @@ split-window
        ((and (window-parameter window 'window-atom)
 	     (setq atom-root (window-atom-root window))
 	     (not (eq atom-root window)))
-	(throw 'done (split-window atom-root size side pixelwise)))
+	(throw 'done (split-window atom-root size side pixelwise refer)))
        ;; If WINDOW is a side window or its first or last child is a
        ;; side window, throw an error unless `window-combination-resize'
        ;; equals 'side.
@@ -5635,8 +5645,8 @@ split-window
 		   (window-combined-p window horizontal)))
 	     ;; 'old-pixel-size' is the current pixel size of WINDOW.
 	     (old-pixel-size (window-size window horizontal t))
-	     ;; 'new-size' is the specified or calculated size of the
-	     ;; new window.
+	     ;; 'new-pixel-size' is the specified or calculated size
+	     ;; of the new window.
 	     new-pixel-size new-parent new-normal)
 	(cond
 	 ((not pixel-size)
@@ -5757,8 +5767,9 @@ split-window
 	   window (- (if new-parent 1.0 (window-normal-size window horizontal))
 		     new-normal)))
 
-	(let* ((new (split-window-internal window new-pixel-size side new-normal)))
-	  (window--pixel-to-total frame horizontal)
+	(let ((new (split-window-internal
+		    window new-pixel-size side new-normal refer)))
+          (window--pixel-to-total frame horizontal)
 	  ;; Assign window-side parameters, if any.
 	  (cond
 	   ((eq window-combination-resize 'side)
diff --git a/src/window.c b/src/window.c
index 34968ac824f..402bb0459e3 100644
--- a/src/window.c
+++ b/src/window.c
@@ -5073,7 +5073,7 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
 }
 
 
-DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 4, 0,
+DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 5, 0,
        doc: /* Split window OLD.
 Second argument PIXEL-SIZE specifies the number of pixels of the
 new window.  It must be a positive integer.
@@ -5088,32 +5088,30 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 the right side of WINDOW.  SIDE `left' means the new window shall be
 located on the left of WINDOW.  In both cases PIXEL-SIZE specifies the
 width of the new window including space reserved for fringes and the
-scrollbar or a divider column.
+scroll bar or a divider column.
 
 Fourth argument NORMAL-SIZE specifies the normal size of the new window
-according to the SIDE argument.
+according to the SIDE argument.  Optional fifth argument REFER is as for
+'split-window'.
 
 The new pixel and normal sizes of all involved windows must have been
 set correctly.  See the code of `split-window' for how this is done.  */)
-  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size)
+  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size,
+   Lisp_Object refer)
 {
   /* OLD (*o) is the window we have to split.  (*p) is either OLD's
      parent window or an internal window we have to install as OLD's new
-     parent.  REFERENCE (*r) must denote a live window, or is set to OLD
-     provided OLD is a leaf window, or to the frame's selected window.
-     NEW (*n) is the new window created with some parameters taken from
-     REFERENCE (*r).  */
-  Lisp_Object new, frame, reference;
-  struct window *o, *p, *n, *r, *c;
-  struct frame *f;
+     parent.  NEW (*n) is the new window created or adopted with
+     properties from REFER (*r), if specified.  */
+  struct window *o = decode_valid_window (old);
+  Lisp_Object frame = WINDOW_FRAME (o);
+  struct frame *f = XFRAME (frame);
+  struct window *p, *n, *r, *c;
   bool horflag
     /* HORFLAG is true when we split side-by-side, false otherwise.  */
     = EQ (side, Qt) || EQ (side, Qleft) || EQ (side, Qright);
-
-  CHECK_WINDOW (old);
-  o = XWINDOW (old);
-  frame = WINDOW_FRAME (o);
-  f = XFRAME (frame);
+  Lisp_Object new;
+  bool dead = false;
 
   CHECK_FIXNUM (pixel_size);
   EMACS_INT total_size
@@ -5131,14 +5129,38 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	   ? WINDOW_VERTICAL_COMBINATION_P (XWINDOW (o->parent))
 	   : WINDOW_HORIZONTAL_COMBINATION_P (XWINDOW (o->parent))));
 
-  /* We need a live reference window to initialize some parameters.  */
-  if (WINDOW_LIVE_P (old))
-    /* OLD is live, use it as reference window.  */
-    reference = old;
+  /* Set up reference window.  */
+  if (NILP (refer))
+    {
+      if (WINDOW_LIVE_P (old))
+	/* OLD is live, use it as reference window.  */
+	refer = old;
+      else
+	/* Use the frame's selected window as reference window.  */
+	refer = FRAME_SELECTED_WINDOW (f);
+
+      r = XWINDOW (refer);
+    }
   else
-    /* Use the frame's selected window as reference window.  */
-    reference = FRAME_SELECTED_WINDOW (f);
-  r = XWINDOW (reference);
+    {
+      r = decode_any_window (refer);
+
+      if (NILP (r->contents))
+	/* Presumably a dead, former live window.  Check whether its
+	   content can be used.  */
+	{
+	  if (!BUFFERP (r->old_buffer))
+	    error ("Dead reference window was not a live window");
+	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	    error ("Dead reference window's old buffer is dead");
+	  else if (!EQ (r->frame, frame))
+	    error ("Dead referenec window was on other frame");
+
+	  dead = true;
+	}
+      else if (!WINDOW_LIVE_P (refer))
+	error ("Reference window must not be internal");
+    }
 
   /* The following bugs are caught by `split-window'.  */
   if (MINI_WINDOW_P (o))
@@ -5195,7 +5217,12 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     p = XWINDOW (o->parent);
 
   fset_redisplay (f);
-  new = make_window ();
+
+  if (dead)
+    new = refer;
+  else
+    new = make_window ();
+
   n = XWINDOW (new);
   wset_frame (n, frame);
   wset_parent (n, o->parent);
@@ -5219,19 +5246,22 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
       wset_next (o, new);
     }
 
-  n->window_end_valid = false;
-  n->last_cursor_vpos = 0;
+  if (!dead)
+    {
+      n->window_end_valid = false;
+      n->last_cursor_vpos = 0;
 
-  /* Get special geometry settings from reference window.  */
-  n->left_margin_cols = r->left_margin_cols;
-  n->right_margin_cols = r->right_margin_cols;
-  n->left_fringe_width = r->left_fringe_width;
-  n->right_fringe_width = r->right_fringe_width;
-  n->fringes_outside_margins = r->fringes_outside_margins;
-  n->scroll_bar_width = r->scroll_bar_width;
-  n->scroll_bar_height = r->scroll_bar_height;
-  wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
-  wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+      /* Get special geometry settings from reference window.  */
+      n->left_margin_cols = r->left_margin_cols;
+      n->right_margin_cols = r->right_margin_cols;
+      n->left_fringe_width = r->left_fringe_width;
+      n->right_fringe_width = r->right_fringe_width;
+      n->fringes_outside_margins = r->fringes_outside_margins;
+      n->scroll_bar_width = r->scroll_bar_width;
+      n->scroll_bar_height = r->scroll_bar_height;
+      wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
+      wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+    }
 
   /* Directly assign orthogonal coordinates and sizes.  */
   if (horflag)
@@ -5267,10 +5297,44 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   wset_new_normal (n, normal_size);
 
   block_input ();
+
+  if (dead)
+    {
+      /* Get dead window back its old buffer and markers.  */
+      wset_buffer (n, n->old_buffer);
+      Fset_marker (n->start, make_fixnum (n->del_start), n->contents);
+      Fset_marker (n->pointm, make_fixnum (n->del_pointm), n->contents);
+      Vwindow_list = Qnil;
+    }
+  else
+    {
+      /* Note: n->contents is Qnil throughout this call, so n will be
+	 neither considered a leaf nor an internal window.  */
+      Lisp_Object buffer = r->contents;
+      struct buffer *b = XBUFFER (buffer);
+
+      /* Provisorially set new's buffer to that of the reference window,
+	 resize the parent, reset new's buffer to nil and do the real
+	 set_window_buffer.  */
+      wset_buffer (n, buffer);
+      set_marker_both (n->pointm, buffer, BUF_PT (b), BUF_PT_BYTE (b));
+      set_marker_restricted
+	(n->start, make_fixnum (b->last_window_start), buffer);
+    }
+
   window_resize_apply (p, horflag);
+
+  if (!dead)
+    {
+      /* Set buffer of NEW to buffer of reference window.  We have to do it
+	 here so the sizes of NEW are in place.  But be sure to do it before
+	 adjusting the frame glyphs - otherwise Emacs may inexplicably loop
+	 forever.  */
+      wset_buffer (n, Qnil);
+      set_window_buffer (new, r->contents, true, true);
+    }
+
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  */
-  set_window_buffer (new, r->contents, true, true);
   FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();
 
@@ -5368,10 +5432,16 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  if (MARKERP (w->pointm))
+	    w->del_pointm = marker_position (w->pointm);
+	  if (MARKERP (w->start))
+	    w->del_start = marker_position (w->start);
+
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
 	  unchain_marker (XMARKER (w->start));
+	  wset_old_buffer (w, w->contents);
 	  wset_buffer (w, Qnil);
 	  /* Add WINDOW to table of dead windows so when killing a buffer
 	     WINDOW mentions, all references to that buffer can be removed
@@ -7712,6 +7782,11 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      if (MARKERP (w->pointm))
+	w->del_pointm = marker_position (w->pointm);
+      if (MARKERP (w->start))
+	w->del_start = marker_position (w->start);
+
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -7720,6 +7795,7 @@ delete_all_child_windows (Lisp_Object window)
 	 only, we use this slot to save the buffer for the sake of
 	 possible resurrection in Fset_window_configuration.  */
       wset_combination_limit (w, w->contents);
+      wset_old_buffer (w, w->contents);
       wset_buffer (w, Qnil);
       /* Add WINDOW to table of dead windows so when killing a buffer
 	 WINDOW mentions, all references to that buffer can be removed
diff --git a/src/window.h b/src/window.h
index 335e0a3453e..41ee7f6dc18 100644
--- a/src/window.h
+++ b/src/window.h
@@ -264,6 +264,10 @@ #define WINDOW_H_INCLUDED
     int total_cols;
     int total_lines;
 
+    /* Positions of pointm and start when window was deleted.  */
+    ptrdiff_t del_pointm;
+    ptrdiff_t del_start;
+
     /* Number of columns display within the window is scrolled to the left.  */
     ptrdiff_t hscroll;
 

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

* Re: Add function to rotate/transpose all windows
  2024-09-27 10:06         ` pranshu sharma
@ 2024-09-27 17:29           ` martin rudalics
  0 siblings, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-09-27 17:29 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > Thanks, I tested it and it works.

I have rewritten this by giving 'split-window' a new argument.  See the
patch I attached to my other mail.

 > I'll get back when I've rewrote transpose frame

You might want to write your own variant of 'window--subtree' where you
would record the normal sizes of windows instead of their edges.  This
would be more accurate and give you immediately a factor to apply when
splitting a window.  And for rotating you might want to start with
'frame-first-window' as the sole window and then use 'above' and 'left'
as SIDE arguments of 'split-window' as you see fit.

Good luck, martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-27 17:29           ` martin rudalics
@ 2024-09-28  7:52             ` pranshu sharma
  2024-09-28  9:26               ` martin rudalics
  2024-09-28  7:58             ` pranshu sharma
  2024-09-28  8:18             ` Eli Zaretskii
  2 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-28  7:52 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:


> Sure.  I'll wait until the OP approves the concept and was able to make
> use of it.

Hello Martin,

I managed to make remake the transpose frame function without using
reseruct window, and instead used swap-window-states.  I still have to
do some cleaning up and error handling tho, eg is still returns error
when you only have one window.

However one thing I'm stuck with it getting it to work with root C-x 2
C-x 3 kinda splits (basiclly where (listp (caddar (window-tree))) is
true).  However if you start with no windows, split window once, go to
split window, they you can go crazy with splitting in any direction
(even the C-x 2 C-x 3) and it will work when you call transpose-frame.

This also different from the transpose-windows.el in that it does not
start on a blank slate(transpose-windows.el calls delete-other-windows
in the transpose-frame-set-arrangement), so transposing partial window
trees much easier but I haven't implimented it yet.

---------------------------
;; is there already another inbuilt funcion like this?
(defun deepmap(func ls)
  (if (null ls)
      ()
    (cons (if (listp (car ls))
	      (deepmap func (car ls))
	    (funcall func (car ls)))
	  (deepmap func (cdr ls)))))

(defun transpose-frame ()
    (interactive)
    (let ((fwin (car (window-tree))))
      (toggle-window-split
       ;; We gotta get sizes now, cuz if not then window split mess em
       ;; up
       (deepmap (lambda (e) (if (windowp e)
				(cons
				 e (window-edges e))
			      e))
		(car (window-tree)))
       fwin
       t)
      ))

(defun toggle-window-split (subtree cwin &optional nokill)
  (pcase-let* ((`(,eee . ,flen) (if (car subtree)
			(cons 1 (window-width cwin))
		      (cons 0 (window-height cwin))))
	       (ilen (float (- (nth (+ 2 eee) (cadr subtree))
			       (nth (+ eee 0) (cadr subtree))))))
    (mapcar
     (pcase-lambda (`(,win . ,size))
       (if (listp win)
	   (progn
	     (toggle-window-split win (split-window cwin
						    (- (round (* flen  (/ size ilen))))
						    (car subtree))))
	 (progn
	   (let ((newwin (split-window cwin
				       (- (round (* flen (/ size ilen))))
				       (car subtree))))
	     (window-swap-states newwin win nil)
	     (delete-window win)))))
     (mapcar
      (lambda (e)
	(pcase-let ((`(,edges ,window?)
		     (if (windowp (car e))
			 (list (cdr e) (car e))
		       (list (cadr e) e))))
	  (cons window? (- (nth (+ 2 eee) edges)
			   (nth eee edges)))))
      (nreverse (cdddr subtree))))
    (unless nokill
      (if (windowp (caaddr subtree))
	  (delete-window (caaddr subtree))
	(toggle-window-split (caddr subtree) cwin)))))

---------------------------



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

* Re: Add function to rotate/transpose all windows
  2024-09-27 17:29           ` martin rudalics
  2024-09-28  7:52             ` pranshu sharma
@ 2024-09-28  7:58             ` pranshu sharma
  2024-09-28  8:18             ` Eli Zaretskii
  2 siblings, 0 replies; 111+ messages in thread
From: pranshu sharma @ 2024-09-28  7:58 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel


Accidently sent wrong definition of transpose-frame.  The other
functions still fine.

The correct one is:
--------------------------
(defun transpose-frame ()
    (interactive)
    (let ((fwin (car (window-tree))))
      (while (not (windowp fwin))
	 (setq fwin (caddr fwin)))
      (toggle-window-split
       ;; We gotta get sizes now, cuz if not then window split mess em
       ;; up
       (deepmap (lambda (e) (if (windowp e)
				(cons
				 e (window-edges e))
			      e))
		(car (window-tree)))
       fwin
       t)
      ))
--------------------------



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

* Re: Add function to rotate/transpose all windows
  2024-09-27 17:29           ` martin rudalics
  2024-09-28  7:52             ` pranshu sharma
  2024-09-28  7:58             ` pranshu sharma
@ 2024-09-28  8:18             ` Eli Zaretskii
  2024-09-28  9:40               ` martin rudalics
  2 siblings, 1 reply; 111+ messages in thread
From: Eli Zaretskii @ 2024-09-28  8:18 UTC (permalink / raw)
  To: martin rudalics; +Cc: pranshusharma366, emacs-devel

> Date: Fri, 27 Sep 2024 19:29:21 +0200
> Cc: pranshusharma366@gmail.com, emacs-devel@gnu.org
> From: martin rudalics <rudalics@gmx.at>
> 
>  > This doc string needs to be more detailed in what it means to "put the
>  > window object of DEAD in the place of LIVE".  For example, it
>  > currently keeps silent about what happens to LIVE after the call.
> 
> The fact that LIVE existed at all was a misfeature.  I now put the dead
> window right into the new window created by 'split-window' with the help
> of a new argument.  Patch attached.

Thanks, but does it really make a lot of sense to make this a
side-effect of splitting a window?  (If it makes sense due to
technical reasons, such as commonality of code of the implementation,
we could have a common internal subroutine with 2 separate APIs
exposed to Lisp.)

Or maybe it _will_ make a lot of sense if you reword the doc string so
that it explains why what we do with REFER is a variant of splitting a
window.  Something like

  Instead of making a new window, this function can reuse an existing
  dead window...

Btw, "dead window" is mentioned only once in the ELisp manual, and
even that in passing, so it is not a very clear terminology, and might
need clarifications in this case.

> +If the optional fifth argument REFER is non-nil, it has to denote a
> +dead, former live window on the same frame as OLD or an arbitrary live
> +window.                                       ^^^

What is "OLD" here?  More generally, I don't think I understand what
you wanted to say by "or an arbitrary live window".

> In the first case, REFER will become the new window with
> +properties like buffer, start and point, decorations and parameters as
> +to the last time when it was live.  In the latter case the new window
> +will inherit properties like buffer, start and point, decorations and
> +parameters from REFER.

I suggest to describe each case separately, i.e. split the previous
sentence ("it has to denote ... or ...") into two, and explain
separately what happens in each case, instead of complicating with the
former and the latter.  Especially as there are additional cases (nil
or omitted), which add to the confusion:

> +If REFER is nil or omitted, then if WINDOW is live, any such properties
> +are inherited from WINDOW.  If, however, WINDOW is an internal window,
> +the new window will inherit these properties from the window selected on
> +WINDOW's frame.

> +	  if (!BUFFERP (r->old_buffer))
> +	    error ("Dead reference window was not a live window");
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This seems to imply that the function expects a dead window to be a
live window(??).

> +	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
> +	    error ("Dead reference window's old buffer is dead");

Will users understand what is "old buffer" of a window?

> +	  else if (!EQ (r->frame, frame))
> +	    error ("Dead referenec window was on other frame");
                         ^^^^^^^^^
Typo.  Also, I'd say "was on a frame other that that of window being
split" or something like that.

> +	  dead = true;
> +	}
> +      else if (!WINDOW_LIVE_P (refer))
> +	error ("Reference window must not be internal");

Are we sure "internal" here will be understood?  How about using "leaf
window" instead?

More generally, the text of these error messages is not easily
correlated to the problematic argument, because it neither mentions
the argument by its exact name, nor mentions the problematic window by
any other specific reference.

> +      /* Provisorially set new's buffer to that of the reference window,
            ^^^^^^^^^^^^^
Did you mean "provisionally"? or maybe "temporarily"?

Also, "new" should be up-cased.

> +	 resize the parent, reset new's buffer to nil and do the real
> +	 set_window_buffer.  */

Likewise.

> +      /* Set buffer of NEW to buffer of reference window.  We have to do it
> +	 here so the sizes of NEW are in place.  But be sure to do it before
> +	 adjusting the frame glyphs - otherwise Emacs may inexplicably loop
> +	 forever.  */

This should be more explicit what code below adjusts the frame's
glyphs, because without that this very good comment is less useful
than it could be.

>  	  unchain_marker (XMARKER (w->start));
> +	  wset_old_buffer (w, w->contents);

What is this about?

> @@ -7720,6 +7795,7 @@ delete_all_child_windows (Lisp_Object window)
>  	 only, we use this slot to save the buffer for the sake of
>  	 possible resurrection in Fset_window_configuration.  */
>        wset_combination_limit (w, w->contents);
> +      wset_old_buffer (w, w->contents);

And this?



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

* Re: Add function to rotate/transpose all windows
  2024-09-28  7:52             ` pranshu sharma
@ 2024-09-28  9:26               ` martin rudalics
  2024-09-28 10:53                 ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-28  9:26 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > I managed to make remake the transpose frame function without using
 > reseruct window, and instead used swap-window-states.

I don't know 'swap-window-states' but if it uses 'window-state-get' and
'window-state-put' (which I happen to know quite intimately) the result
won't be any better than with the concept you cited earlier.

 > However one thing I'm stuck with it getting it to work with root C-x 2
 > C-x 3 kinda splits (basiclly where (listp (caddar (window-tree))) is
 > true).  However if you start with no windows, split window once, go to
 > split window, they you can go crazy with splitting in any direction
 > (even the C-x 2 C-x 3) and it will work when you call transpose-frame.

I don't understand what you mean here.

 > This also different from the transpose-windows.el in that it does not
 > start on a blank slate(transpose-windows.el calls delete-other-windows
 > in the transpose-frame-set-arrangement), so transposing partial window
 > trees much easier but I haven't implimented it yet.

Whatever you do someone has to delete all "other" windows and start with
one specific live window that you subsequently split.

Try with the 'split-window' I posted earlier so you don't have to care
about buffers, decorations and the rest and can concentrate on geometry.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-28  8:18             ` Eli Zaretskii
@ 2024-09-28  9:40               ` martin rudalics
  2024-09-28 11:35                 ` Eli Zaretskii
  2024-09-28 13:22                 ` pranshu sharma
  0 siblings, 2 replies; 111+ messages in thread
From: martin rudalics @ 2024-09-28  9:40 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: pranshusharma366, emacs-devel

 > Thanks, but does it really make a lot of sense to make this a
 > side-effect of splitting a window?

It makes sense because (1) we do not have to make a new window that gets
deleted right away and (2) the geometry of the new window will be
handled by 'split-window' directly and not by some obscure copying
routine (I only later noted that 'resurrect-window' got me warnings
about memcpy combined with offsetof writing beyond the bounds of window
structures).

 > (If it makes sense due to
 > technical reasons, such as commonality of code of the implementation,
 > we could have a common internal subroutine with 2 separate APIs
 > exposed to Lisp.)

We can write a separate 'split-window-reuse-existing' function and have
it call a 'split-window-reuse-existing-internal' routine so we don't
compromise the present 'split-window' at all.  It's up to you what you
like more.

 > Or maybe it _will_ make a lot of sense if you reword the doc string so
 > that it explains why what we do with REFER is a variant of splitting a
 > window.  Something like
 >
 >    Instead of making a new window, this function can reuse an existing
 >    dead window...

OK.

 > Btw, "dead window" is mentioned only once in the ELisp manual, and
 > even that in passing, so it is not a very clear terminology, and might
 > need clarifications in this case.

The problem is that in Emacs a live window is a window that shows a
buffer.  Which implies that internal windows are dead.  I once tried to
convince Chong that this terminology is misleading but he didn't allow
me to change it.

 >> +If the optional fifth argument REFER is non-nil, it has to denote a
 >> +dead, former live window on the same frame as OLD or an arbitrary live
 >> +window.                                       ^^^
 >
 > What is "OLD" here?

It should be WINDOW.  OLD is the term used by 'split-window-internal'.

 > More generally, I don't think I understand what
 > you wanted to say by "or an arbitrary live window".

That's a separate functionality to give users more control of the window
the new window inherits properties from.  It's useful when splitting an
internal window and the _new_ window should get its initial buffer and
other properties from any but the frame's selected window.

 >> In the first case, REFER will become the new window with
 >> +properties like buffer, start and point, decorations and parameters as
 >> +to the last time when it was live.  In the latter case the new window
 >> +will inherit properties like buffer, start and point, decorations and
 >> +parameters from REFER.
 >
 > I suggest to describe each case separately, i.e. split the previous
 > sentence ("it has to denote ... or ...") into two, and explain
 > separately what happens in each case, instead of complicating with the
 > former and the latter.  Especially as there are additional cases (nil
 > or omitted), which add to the confusion:

I'll try to do that.

 >> +If REFER is nil or omitted, then if WINDOW is live, any such properties
 >> +are inherited from WINDOW.  If, however, WINDOW is an internal window,
 >> +the new window will inherit these properties from the window selected on
 >> +WINDOW's frame.
 >
 >> +	  if (!BUFFERP (r->old_buffer))
 >> +	    error ("Dead reference window was not a live window");
 >                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 > This seems to imply that the function expects a dead window to be a
 > live window(??).

In our misleading terminology a dead window can be either (i) a former
live window or (ii) an internal or former internal window.  I have to
exclude (ii) because an internal window should never become a leaf
window and vice-versa.

 >> +	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
 >> +	    error ("Dead reference window's old buffer is dead");
 >
 > Will users understand what is "old buffer" of a window?

It's the buffer returned by 'window-old-buffer'.  I will have to explain
this in more detail.

 >> +	  else if (!EQ (r->frame, frame))
 >> +	    error ("Dead referenec window was on other frame");
 >                           ^^^^^^^^^
 > Typo.  Also, I'd say "was on a frame other that that of window being
 > split" or something like that.

OK.

 >> +	  dead = true;
 >> +	}
 >> +      else if (!WINDOW_LIVE_P (refer))
 >> +	error ("Reference window must not be internal");
 >
 > Are we sure "internal" here will be understood?  How about using "leaf
 > window" instead?

But it's the opposite - a "non-leaf window".  If you (or anyone else)
have any proposals on how to improve the nomenclature in this area, I'll
be all ears.

 > More generally, the text of these error messages is not easily
 > correlated to the problematic argument, because it neither mentions
 > the argument by its exact name, nor mentions the problematic window by
 > any other specific reference.

So the error messages should explain in more detail what when wrong.

 >> +      /* Provisorially set new's buffer to that of the reference window,
 >              ^^^^^^^^^^^^^
 > Did you mean "provisionally"? or maybe "temporarily"?

Both would fit the bill.  But this is code that I didn't intend to
submit is part of a completely different changeset where the sizes
of an individual window would be needed to set up the size hints of
frames correctly in set_window_buffer.

 >> +      /* Set buffer of NEW to buffer of reference window.  We have to do it
 >> +	 here so the sizes of NEW are in place.  But be sure to do it before
 >> +	 adjusting the frame glyphs - otherwise Emacs may inexplicably loop
 >> +	 forever.  */
 >
 > This should be more explicit what code below adjusts the frame's
 > glyphs, because without that this very good comment is less useful
 > than it could be.

Again part of that other changeset.

 >>   	  unchain_marker (XMARKER (w->start));
 >> +	  wset_old_buffer (w, w->contents);
 >
 > What is this about?

When we make a new window and delete it before redisplay runs
window_change_functions for the first time, the old buffer of the window
is nil.  But that would have caused the "Dead reference window's old
buffer is dead" error with the earlier proposed 'resurrect-window' while
in fact the window (made by 'split-window' right before) did have an old
buffer.  It has no significance for the new 'split-window'.

 >> @@ -7720,6 +7795,7 @@ delete_all_child_windows (Lisp_Object window)
 >>   	 only, we use this slot to save the buffer for the sake of
 >>   	 possible resurrection in Fset_window_configuration.  */
 >>         wset_combination_limit (w, w->contents);
 >> +      wset_old_buffer (w, w->contents);
 >
 > And this?

Same explanation.  The code for Fdelete_window_internal and
delete_all_child_windows should be refactored so that they would call a
common delete_leaf_window.

Thanks for the careful reading, martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-28  9:26               ` martin rudalics
@ 2024-09-28 10:53                 ` pranshu sharma
  2024-09-28 14:48                   ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-28 10:53 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> I don't know 'swap-window-states' but if it uses 'window-state-get' and
> 'window-state-put' (which I happen to know quite intimately) the result
> won't be any better than with the concept you cited earlier.
>

I meant window-swap-states, and it does use window-state-get and put.


>> However one thing I'm stuck with it getting it to work with root C-x 2
>> C-x 3 kinda splits (basiclly where (listp (caddar (window-tree))) is
>> true).  However if you start with no windows, split window once, go to
>> split window, they you can go crazy with splitting in any direction
>> (even the C-x 2 C-x 3) and it will work when you call transpose-frame.
>
> I don't understand what you mean here.

Say you save window layout:
- +----+--+
- | A  |C |
- |____|__|
- |  B    |
- +-------+

(car (window-tree)) returns:


(t (0 0 137 30)
   ;; Following sexp is returned by (caddar (window-tree))
   (nil (0 0 137 15)
	#<window A>
	#<window C>)
   #<window B>)

In a scinario like this, where (caddar (window-tree)) is a list, the
function does not work as it will not have any anchor point.

However transpose-frame will work on the following layout, as it can
anchor of A

- +------+----+--+
- |      | B  |C |
- |  A   |____|__|
- |      |  C    |
- +------+-------+


>> This also different from the transpose-windows.el in that it does not
>> start on a blank slate(transpose-windows.el calls delete-other-windows
>> in the transpose-frame-set-arrangement), so transposing partial window
>> trees much easier but I haven't implimented it yet.
>
> Whatever you do someone has to delete all "other" windows and start with
> one specific live window that you subsequently split.

wdym?

> Try with the 'split-window' I posted earlier so you don't have to care
> about buffers, decorations and the rest and can concentrate on geometry.

just compiled emacs an tested it works, I'll do that



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

* Re: Add function to rotate/transpose all windows
  2024-09-28  9:40               ` martin rudalics
@ 2024-09-28 11:35                 ` Eli Zaretskii
  2024-09-28 14:58                   ` martin rudalics
  2024-09-28 13:22                 ` pranshu sharma
  1 sibling, 1 reply; 111+ messages in thread
From: Eli Zaretskii @ 2024-09-28 11:35 UTC (permalink / raw)
  To: martin rudalics; +Cc: pranshusharma366, emacs-devel

> Date: Sat, 28 Sep 2024 11:40:27 +0200
> Cc: pranshusharma366@gmail.com, emacs-devel@gnu.org
> From: martin rudalics <rudalics@gmx.at>
> 
>  >> +	  if (!BUFFERP (r->old_buffer))
>  >> +	    error ("Dead reference window was not a live window");
>  >                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>  > This seems to imply that the function expects a dead window to be a
>  > live window(??).
> 
> In our misleading terminology a dead window can be either (i) a former
> live window or (ii) an internal or former internal window.  I have to
> exclude (ii) because an internal window should never become a leaf
> window and vice-versa.

So maybe this is better:

   Dead REFER window was not a leaf window"

>  >> +      else if (!WINDOW_LIVE_P (refer))
>  >> +	error ("Reference window must not be internal");
>  >
>  > Are we sure "internal" here will be understood?  How about using "leaf
>  > window" instead?
> 
> But it's the opposite - a "non-leaf window".

Yes, so I suggested replacing "must not be internal" with "must be a
leaf window".  Is that wrong?

>  >>   	  unchain_marker (XMARKER (w->start));
>  >> +	  wset_old_buffer (w, w->contents);
>  >
>  > What is this about?
> 
> When we make a new window and delete it before redisplay runs
> window_change_functions for the first time, the old buffer of the window
> is nil.  But that would have caused the "Dead reference window's old
> buffer is dead" error with the earlier proposed 'resurrect-window' while
> in fact the window (made by 'split-window' right before) did have an old
> buffer.  It has no significance for the new 'split-window'.

So this fixes a related but different bug?



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

* Re: Add function to rotate/transpose all windows
  2024-09-28  9:40               ` martin rudalics
  2024-09-28 11:35                 ` Eli Zaretskii
@ 2024-09-28 13:22                 ` pranshu sharma
  2024-09-28 14:21                   ` Eli Zaretskii
  2024-09-28 14:49                   ` martin rudalics
  1 sibling, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-09-28 13:22 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:
>
> It makes sense because (1) we do not have to make a new window that gets
> deleted right away and (2) the geometry of the new window will be
> handled by 'split-window' directly and not by some obscure copying
> routine (I only later noted that 'resurrect-window' got me warnings
> about memcpy combined with offsetof writing beyond the bounds of window
> structures).

Is there a technical reason for the `refer' argument to neccasarily be
_dead_ window, it would be very helpful if you could pass a living
window.



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

* Re: Add function to rotate/transpose all windows
  2024-09-28 13:22                 ` pranshu sharma
@ 2024-09-28 14:21                   ` Eli Zaretskii
  2024-09-28 14:49                   ` martin rudalics
  1 sibling, 0 replies; 111+ messages in thread
From: Eli Zaretskii @ 2024-09-28 14:21 UTC (permalink / raw)
  To: pranshu sharma; +Cc: rudalics, emacs-devel

> From: pranshu sharma <pranshusharma366@gmail.com>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Sat, 28 Sep 2024 23:22:47 +1000
> 
> Is there a technical reason for the `refer' argument to neccasarily be
> _dead_ window, it would be very helpful if you could pass a living
> window.

My reading of the latest version of the patch posted by Martin is that
this is indeed possible.



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

* Re: Add function to rotate/transpose all windows
  2024-09-28 10:53                 ` pranshu sharma
@ 2024-09-28 14:48                   ` martin rudalics
  2024-09-29  7:36                     ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-28 14:48 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > Say you save window layout:
 > - +----+--+
 > - | A  |C |
 > - |____|__|
 > - |  B    |
 > - +-------+
 >
 > (car (window-tree)) returns:
 >
 >
 > (t (0 0 137 30)
 >     ;; Following sexp is returned by (caddar (window-tree))
 >     (nil (0 0 137 15)
 > 	#<window A>
 > 	#<window C>)
 >     #<window B>)
 >
 > In a scinario like this, where (caddar (window-tree)) is a list, the
 > function does not work as it will not have any anchor point.

Do you mean that your function doesn't work because A has to be split
twice - once to produce B and then to produce C?

 > However transpose-frame will work on the following layout, as it can
 > anchor of A
 >
 > - +------+----+--+
 > - |      | B  |C |
 > - |  A   |____|__|
 > - |      |  C    |
 > - +------+-------+

What is the anchor point here?  Does it work because you have to split A
once only and then deal with the split off window only?  I don't know
how to help you but I suppose your function should work by extracting
from the window tree all windows on the same level and recursively apply
itself on the respective tails of the window tree at that level.

 >> Whatever you do someone has to delete all "other" windows and start with
 >> one specific live window that you subsequently split.
 >
 > wdym?

The standard case is where you rotate the entire window tree of a frame.
In that case you would call 'delete-other-windows' and start splitting.
But in your initial posting you said that TransposeFrame has "No good
way to transpose only specific parts of window tree".  Suppose you have
the configuration you just mentioned

  +------+----+--+
  |      | B  |C |
  |  A   |____|__|
  |      |  D    |
  +------+-------+

and want to leave A in place.  So you have to delete the other windows C
and D first and then start splitting B (alternatively, you could
temporarily bind the 'no-delete-other-windows' parameter of A to t and
reset it after calling 'delete-other-windows' on B).

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-28 13:22                 ` pranshu sharma
  2024-09-28 14:21                   ` Eli Zaretskii
@ 2024-09-28 14:49                   ` martin rudalics
  1 sibling, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-09-28 14:49 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > Is there a technical reason for the `refer' argument to neccasarily be
 > _dead_ window, it would be very helpful if you could pass a living
 > window.

You can pass a live window to it.  But in that case it will only use
that window's properties for the new window 'split-window' made.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-28 11:35                 ` Eli Zaretskii
@ 2024-09-28 14:58                   ` martin rudalics
  2024-09-28 15:28                     ` Eli Zaretskii
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-28 14:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: pranshusharma366, emacs-devel

 > So maybe this is better:
 >
 >     Dead REFER window was not a leaf window"

OK.

 >>   >> +      else if (!WINDOW_LIVE_P (refer))
 >>   >> +	error ("Reference window must not be internal");
 >>   >
 >>   > Are we sure "internal" here will be understood?  How about using "leaf
 >>   > window" instead?
 >>
 >> But it's the opposite - a "non-leaf window".
 >
 > Yes, so I suggested replacing "must not be internal" with "must be a
 > leaf window".  Is that wrong?

No.  We could also say "must display a buffer".

 >
 >>   >>   	  unchain_marker (XMARKER (w->start));
 >>   >> +	  wset_old_buffer (w, w->contents);
 >>   >
 >>   > What is this about?
 >>
 >> When we make a new window and delete it before redisplay runs
 >> window_change_functions for the first time, the old buffer of the window
 >> is nil.  But that would have caused the "Dead reference window's old
 >> buffer is dead" error with the earlier proposed 'resurrect-window' while
 >> in fact the window (made by 'split-window' right before) did have an old
 >> buffer.  It has no significance for the new 'split-window'.
 >
 > So this fixes a related but different bug?

It wasn't a bug so far because the "old buffer" was not defined for dead
windows.  'window-old-buffer' is described as

   Return the old buffer displayed by WINDOW.
   WINDOW must be a live window and defaults to the selected one.

   The return value is the buffer shown in WINDOW at the last time window
   change functions were run.  It is nil if WINDOW was created after
   that.  It is t if WINDOW has been restored from a window configuration
   after that.

What the patch does is to now make it work for dead windows in the sense
that its value is that of the buffer shown in the window at the time the
window was deleted.  In general, this _is_ the buffer stored in the
old_buffer slot.  But if we manage to delete a window whose old buffer
is nil or t _before_ running window change functions on it for the first
time, it isn't.

But I just noticed that even 'window-old-buffer' can be affected by the
patch if we (i) make a window (ii) delete it before it's seen by the
change functions and (iii) resurrect it.  In that (unlikely) scenario
the next change functions will consider the window as one seen by the
last change functions although these have never seen it.  I have to add
another slot del_buffer to the window structure.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-28 14:58                   ` martin rudalics
@ 2024-09-28 15:28                     ` Eli Zaretskii
  2024-10-07  8:33                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Eli Zaretskii @ 2024-09-28 15:28 UTC (permalink / raw)
  To: martin rudalics; +Cc: pranshusharma366, emacs-devel

> Date: Sat, 28 Sep 2024 16:58:24 +0200
> Cc: pranshusharma366@gmail.com, emacs-devel@gnu.org
> From: martin rudalics <rudalics@gmx.at>
> 
>  >>   >> +      else if (!WINDOW_LIVE_P (refer))
>  >>   >> +	error ("Reference window must not be internal");
>  >>   >
>  >>   > Are we sure "internal" here will be understood?  How about using "leaf
>  >>   > window" instead?
>  >>
>  >> But it's the opposite - a "non-leaf window".
>  >
>  > Yes, so I suggested replacing "must not be internal" with "must be a
>  > leaf window".  Is that wrong?
> 
> No.  We could also say "must display a buffer".

I think we should say both, to be very clear.



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

* Re: Add function to rotate/transpose all windows
  2024-09-28 14:48                   ` martin rudalics
@ 2024-09-29  7:36                     ` pranshu sharma
  2024-09-29  8:40                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-29  7:36 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel


>> In a scinario like this, where (caddar (window-tree)) is a list, the
>> function does not work as it will not have any anchor point.
>
> Do you mean that your function doesn't work because A has to be split
> twice - once to produce B and then to produce C?

Something like that, but I fixed now, see code below

> What is the anchor point here?  Does it work because you have to split A
> once only and then deal with the split off window only?  I don't know
> how to help you but I suppose your function should work by extracting
> from the window tree all windows on the same level and recursively apply
> itself on the respective tails of the window tree at that level.

The problem was that I was splitting off another window as I was not
already desyroying the whole tree.

Thanks for your explenation, I found out I was only over complicating it
by not destroying windows, so I gave up that approach.

This time instead of calling delete-other-windows, I recusivly walked
window subtree and deleted them.


The following code implements transpose-frame and which works fine, I
tested with some random window layouts and it worked for all.  It uses
the split-window refer argument.

The code:
-----------------------------------------
(defun transpose-frame (arg)
  "Transpose frame, or if arg is non-nil, selected window."
  (interactive "P")
  (when-let* ((win-tree (car (window--subtree
			      (if arg
				  (window-parent (selected-window))
				(frame-root-window)))))
	      (fwin (if arg
			(selected-window)
		      (let ((win (window-child (frame-root-window))))
			(while (not (window-live-p win))
			  (setq win (window-child win)))
			win)))
	      (_ (not (windowp win-tree))))
      (toggle-window-split
       (let* (delist
	     (res (deepmap (lambda (e) (if (windowp e)
					    (prog1 (cons e (window-edges e))
					      (unless (equal fwin e)
						(push e delist)))
					  e))
			    win-tree)))
         (mapc 'delete-window delist)
	 res)
       fwin)))


;; Is there already another inbuilt funcion like this?
(defun deepmap(func ls)
  (if (null ls)
      ()
    (cons (if (listp (car ls))
	      (deepmap func (car ls))
	    (funcall func (car ls)))
	  (deepmap func (cdr ls)))))


(defun toggle-window-split (subtree cwin)
  (pcase-let* ((`(,eee . ,flen) (if (car subtree)
				    (cons 1 (window-width cwin))
				  (cons 0 (window-height cwin))))
	       (ilen (float (- (nth (+ 2 eee) (cadr subtree))
			       (nth (+ eee 0) (cadr subtree))))))
    (mapc
     (pcase-lambda (`(,win . ,size))
       (if (listp win)	
	   (toggle-window-split win (split-window cwin
						  (- (round (* flen  (/ size ilen))))
						  (car subtree)
						  nil
						  (seq-some
						   (lambda (x)
						     (and (windowp x) x))
						   (flatten-list win))))
	 (split-window cwin (- (round (* flen (/ size ilen))))
		       (car subtree) nil
		       win)))
     (mapcar
      (lambda (e)
	(pcase-let ((`(,edges ,window?)
		     (if (windowp (car e))
			 (list (cdr e) (car e))
		       (list (cadr e) e))))
	  (cons window? (- (nth (+ 2 eee) edges)
			   (nth eee edges)))))
      (nreverse (cdddr subtree))))
    (unless (windowp (caaddr subtree))
      (toggle-window-split (caddr subtree) cwin))))
-----------------------------------------



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

* Re: Add function to rotate/transpose all windows
  2024-09-29  7:36                     ` pranshu sharma
@ 2024-09-29  8:40                       ` martin rudalics
  2024-09-29  9:23                         ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-29  8:40 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > The following code implements transpose-frame and which works fine, I
 > tested with some random window layouts and it worked for all.  It uses
 > the split-window refer argument.

Thank you.

 > (defun transpose-frame (arg)
 >   "Transpose frame, or if arg is non-nil, selected window."

ARG (upper case please) should be renamed to make it more descriptive
and you should explain what transposing the selected window means.

 > 	     (res (deepmap (lambda (e) (if (windowp e)
 > 					    (prog1 (cons e (window-edges e))
 > 					      (unless (equal fwin e)
 > 						(push e delist)))

IIUC here you "flatten" the return value of 'window-tree'.  Why do you
do that?  Can't 'transpose-frame' work directly on the return value of a
(possibly modified) 'window-tree'?

 >					    (prog1 (cons e (window-edges e))

On my maximized frame calling 'transpose-frame' repeatedly shrinks the
right/bottom windows until they cannot be shrunk any more.  I wouldn't
use 'window-edges' here.  Make your own version of 'window-tree' (or
give that an optional argument) so it returns the normal sizes of
windows instead of the edges and use the normal sizes as factor for the
SIZE argument of ‘split-window’ and make the PIXELWISE argument non-nil.

Rounding window sizes can be awfully tricky and we should try to avoid
its effects as good as we can.  When transposing windows on a GUI you
will always have to take into account that the width of a window
includes things like fringes and scroll bars (where the size of the
latter is toolkit dependent) which often make the width not an integral
multiple of the frame's character width.  A similar argument goes for
window heights, for example, if a mode or tab line is displayed with a
different font or some sort of box around it.

Also I expected your functions to run on subwindows of a frame's windows
and permit to rotate configurations clockwise or counter clockwise as
the version from 2011 did?  Do you intend to provide those?

martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-29  8:40                       ` martin rudalics
@ 2024-09-29  9:23                         ` pranshu sharma
  2024-09-29 14:48                           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-29  9:23 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> The following code implements transpose-frame and which works fine, I
>> tested with some random window layouts and it worked for all.  It uses
>> the split-window refer argument.
>
> Thank you.
>
>> (defun transpose-frame (arg)
>>   "Transpose frame, or if arg is non-nil, selected window."
>
> ARG (upper case please) should be renamed to make it more descriptive
> and you should explain what transposing the selected window means.

Ok, will do

>
>> 	     (res (deepmap (lambda (e) (if (windowp e)
>> 					    (prog1 (cons e (window-edges e))
>> 					      (unless (equal fwin e)
>> 						(push e delist)))
>
> IIUC here you "flatten" the return value of 'window-tree'.  Why do you
> do that?  Can't 'transpose-frame' work directly on the return value of a
> (possibly modified) 'window-tree'?

This is definetly a big hack.  The reason why this is needed is
because the parent window might not actually be a window, it might also
be another split, so it would have to recursivly descent find the first
window.

The only real alternative I can see is that if toggle-window-split
returns a list of spltis to do, and then they are mapped over and done
by toggle-window-split one level higher.

I'll look into how transpose-frame.el deals with this.

>>					    (prog1 (cons e (window-edges e))
>
> On my maximized frame calling 'transpose-frame' repeatedly shrinks the
> right/bottom windows until they cannot be shrunk any more.

Hmm, show your window-tree (or the sequence of splits you did), I tested
with a lot of splits and didn't find this problem.

> I wouldn't use 'window-edges' here.  Make your own version of
> 'window-tree' (or give that an optional argument) so it returns the
> normal sizes of windows instead of the edges and use the normal sizes
> as factor for the SIZE argument of ‘split-window’ and make the
> PIXELWISE argument non-nil.

I see, this should definetly make the function better.

> Rounding window sizes can be awfully tricky and we should try to avoid
> its effects as good as we can.  When transposing windows on a GUI you
> will always have to take into account that the width of a window
> includes things like fringes and scroll bars (where the size of the
> latter is toolkit dependent) which often make the width not an integral
> multiple of the frame's character width.  A similar argument goes for
> window heights, for example, if a mode or tab line is displayed with a
> different font or some sort of box around it.
>
> Also I expected your functions to run on subwindows of a frame's windows
> and permit to rotate configurations clockwise or counter clockwise as
> the version from 2011 did?  Do you intend to provide those?

They should be very easy(just changing up the arguments of split-window
to correct combo of above, below, right, left), I'm just
trying to first get the other aspects streamlined, but yes, I do intend
on adding them.

I'll also add flip if you want.

>
> martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-29  9:23                         ` pranshu sharma
@ 2024-09-29 14:48                           ` martin rudalics
  2024-09-30  6:29                             ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-29 14:48 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

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

 > This is definetly a big hack.  The reason why this is needed is
 > because the parent window might not actually be a window, it might also
 > be another split, so it would have to recursivly descent find the first
 > window.

Hmm... I seem to understand.  TransposeFrame uses the buffer of the
window it splits so there you have a lot of windows showing the frame's
selected window initially and eventually see them set to their original
buffers.  We want to set a window's final buffer immediately at the time
of splitting.

 > The only real alternative I can see is that if toggle-window-split
 > returns a list of spltis to do, and then they are mapped over and done
 > by toggle-window-split one level higher.
 >
 > I'll look into how transpose-frame.el deals with this.

Note that TransposeFrame also may revert the cdr of the list it deals
with.  Back then the 'above and 'left values for SIDE were not known
yet.

 >>> 					    (prog1 (cons e (window-edges e))
 >>
 >> On my maximized frame calling 'transpose-frame' repeatedly shrinks the
 >> right/bottom windows until they cannot be shrunk any more.
 >
 > Hmm, show your window-tree (or the sequence of splits you did), I tested
 > with a lot of splits and didn't find this problem.

I'll attach a capture of the "initial" split and the "final" split after
a couple of invocations of 'transpose-frame'.

martin

[-- Attachment #2: initial.png --]
[-- Type: image/png, Size: 84203 bytes --]

[-- Attachment #3: final.png --]
[-- Type: image/png, Size: 85594 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-09-29 14:48                           ` martin rudalics
@ 2024-09-30  6:29                             ` pranshu sharma
  2024-09-30  8:57                               ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-09-30  6:29 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel


Hello Martin,

After some work, the code is now complete and uses pixels instead colums
of.  It can do everything the orignal transpose frame could (flip,
transpose, rotate), and results are the same.

Some function are like opposites, eg flip-frame-horizontally and
flip-frame-verticly, I thought about merging them and using prefix arg,
but then prefix arg already in use for if or not to apply to partial
subtree instead frame.

English is not my first language, so I was unable to make the docstrings
for the more complex function, and I don't know how to explain what
transpose does.

Also I think your error was coming from scroll and tool bars(should be
fixed now cuz pixels), but since terminal does not have them I couldnt
run into too small for splitting error on terminal.

-----------------------------

(defun window--subtree-with-size (window &optional next)
  "Like `window--subtree' but each window is replaced with the
list: (WINDOW PIXEL-HEIGHT PIXEL-WIDTH), and window-edges is replaced
with the list: (PIXEL-HEIGHT PIXEL-WIDTH)."
  (let (list)
    (while window
      (setq list
	    (cons
	     (cond
	      ((window-top-child window)
	       (cons t (cons (list (window-pixel-height window) (window-pixel-width window))
			     (window--subtree-with-size (window-top-child window) t))))
	      ((window-left-child window)
	       (cons nil (cons (list (window-pixel-height window) (window-pixel-width window))
			       (window--subtree-with-size (window-left-child window) t))))
	      (t (list window (window-pixel-height window) (window-pixel-width window))))
	     list))
      (setq window (when next (window-next-sibling window))))
    (nreverse list)))


(defun rotate-frame-anticlockwise (subtree-only)
  "If SUBTREE-ONLY is nil, rotate the whole frame anti clockwise else only
apply on the subtree of selected window."
  (interactive "P")
  (apply-rearrange-frame subtree-only '(right . above) nil))

(defun rotate-frame-clockwise (subtree-only)
  "Like `rotate-frame-anticlockwise' but rotate clockwise instead."
  (interactive "P")
  (apply-rearrange-frame subtree-only '(left . below) nil))

(defun flip-frame-horizontally (subtree-only)
  "If SUBTREE-ONLY is nil, rotate the whole frame anti clockwise else only
apply on the subtree of selected window."
  (interactive "P")
  (apply-rearrange-frame subtree-only '(below . left) t))

(defun flip-frame-verticly (subtree-only)
  "Like `flip-frame-horizontally' but flip verticlly instead."
  (interactive "P")
  (apply-rearrange-frame subtree-only '(above . right) t))

(defun transpose-frame (subtree-only)
  (interactive "P")
  (apply-rearrange-frame subtree-only '(right . below) nil))


(defun apply-rearrange-frame (subtree-only conf norm-size)
  "Transpose frame, or if SUBTREE-ONLY is non-nil, selected window."
  (if (eq (next-window) (selected-window))
      (message "No windows to transpose.")
    (let* ((win-tree (car (window--subtree-with-size
			   (if subtree-only
			       (window-parent (selected-window))
			     (frame-root-window)))))
	   (fwin (if subtree-only
		     (selected-window)
		   (let ((win (window-child (frame-root-window))))
		     (while (not (window-live-p win))
		       (setq win (window-child win)))
		     win))))
      (mapc (lambda (win)
	      (when (and (windowp win)
			 (not (eq win fwin)))
		(delete-window win)))
	    (flatten-list win-tree))
      (toggle-window-split win-tree fwin conf norm-size)
      (select-window fwin))))

(defun toggle-window-split (subtree cwin conf norm-size)
  (pcase-let ((`(,ilen . ,flen) (if (car subtree)
				    (cons (float (car (cadr subtree)))
					  (float (window-pixel-width cwin)))
				  (cons (float (cadr (cadr subtree)))
					(float (window-pixel-height cwin))))))
    (mapc
     (pcase-lambda (`(,win . ,size))
       (let ((split-size (- (if norm-size
				size
			      (round (* flen  (/ size ilen))))))
	     (split-type
	      (funcall (if (car subtree) 'car 'cdr) conf)))
	 (if (listp win)	
	     (toggle-window-split win (split-window cwin
						    split-size
						    split-type
						    t
						    (seq-some
						     (lambda (x)
						       (and (windowp x) x))
						     (flatten-list win)))
				  conf norm-size)
	   (split-window cwin split-size
			 split-type t
			 win))))
     (mapcar
      (lambda (e)
	(pcase-let* ((`(,size ,window?)
		      (if (windowp (car e))
			  (list (cdr e) (car e))
			(list (cadr e) e))))
	  (cons window? (if (car subtree)
			    (car size)
			  (cadr size)))))
      (nreverse (cdddr subtree))))
    (unless (windowp (caaddr subtree))
      (toggle-window-split (caddr subtree) cwin conf norm-size))))




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

* Re: Add function to rotate/transpose all windows
  2024-09-30  6:29                             ` pranshu sharma
@ 2024-09-30  8:57                               ` martin rudalics
  2024-10-01  9:17                                 ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-09-30  8:57 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > Hello Martin,
 >
 > After some work,

... remarkable work ...

 > the code is now complete and uses pixels instead colums
 > of.  It can do everything the orignal transpose frame could (flip,
 > transpose, rotate), and results are the same.
 >
 > Some function are like opposites, eg flip-frame-horizontally and
 > flip-frame-verticly, I thought about merging them and using prefix arg,
 > but then prefix arg already in use for if or not to apply to partial
 > subtree instead frame.
 >
 > English is not my first language, so I was unable to make the docstrings
 > for the more complex function, and I don't know how to explain what
 > transpose does.

Let's hope someone will eventually proofread this as soon as we agree on
a final version.

 > Also I think your error was coming from scroll and tool bars(should be
 > fixed now cuz pixels), but since terminal does not have them I couldnt
 > run into too small for splitting error on terminal.

I rotated my windows a number of times and see no shrinking any more.
Even with pixelwise resizing it's impossible to exclude that windows
shrink over time but that's unavoidable unless one uses some heuristics.
So I think that your code handles this well now.

 > (defun window--subtree-with-size (window &optional next)
 >    "Like `window--subtree' but each window is replaced with the
 > list: (WINDOW PIXEL-HEIGHT PIXEL-WIDTH), and window-edges is replaced
 > with the list: (PIXEL-HEIGHT PIXEL-WIDTH)."

As a rule, the first line of a doc-string should be a complete sentence
and be at most 72 columns wide (see Appendix D.6 of the Elisp manual).
And you should describe all arguments like WINDOW and NEXT.

Alternatively, we could give 'window-tree' an optional PIXELWISE
argument so it returns the pixel edges of windows.

 >    (let (list)
 >      (while window
 >        (setq list
 > 	    (cons
 > 	     (cond
 > 	      ((window-top-child window)
 > 	       (cons t (cons (list (window-pixel-height window) (window-pixel-width window))

Lines should not exceed 80 characters and you probably want to use
'cons' instead of 'list' here.

 > 			     (window--subtree-with-size (window-top-child window) t))))
 > 	      ((window-left-child window)
 > 	       (cons nil (cons (list (window-pixel-height window) (window-pixel-width window))
 > 			       (window--subtree-with-size (window-left-child window) t))))
 > 	      (t (list window (window-pixel-height window) (window-pixel-width window))))
 > 	     list))
 >        (setq window (when next (window-next-sibling window))))
 >      (nreverse list)))
 >
 >
 > (defun rotate-frame-anticlockwise (subtree-only)
 >    "If SUBTREE-ONLY is nil, rotate the whole frame anti clockwise else only
 > apply on the subtree of selected window."
 >    (interactive "P")
 >    (apply-rearrange-frame subtree-only '(right . above) nil))

I think you should rename all these into something like
'rotate-windows-anticlockwise' and 'transpose-windows' (we are already
using plurals in 'transpose-lines' and 'transpose-chars').  And we do
not rotate the entire frame - the minibuffer window stays in place, for
example.  So I would try to say

   (defun rotate-windows-anticlockwise (&optional frame window)
     "Rotate windows of FRAME anticlockwise by 90 degrees.
   FRAME must be a live frame and defaults to the selected frame.  By
   default rotate the root window of FRAME (or its main window if it
   differs from the root window).  If WINDOW is non-nil ..."

Which means that we need the additional features:

(1) Rotate windows on any live frame.

(2) Run the function on 'window-main-window' of FRAME unless WINDOW is
defined and never rotate a side window.  Rotating side windows would
cause completely undefined behavior because their slots are expected to
never change.  Hence, if WINDOW is specified and is a side window, say
that you cannot rotate side windows.

(3) "subtree of selected window" is an undefined concept.  You should
say something like "to transpose all windows in the same combination as
WINDOW" instead.  But note that with a slightly more complex layout, no
average user will know what that combination is (think of C-x 2 C-x 3
C-x o C-x o C-x 3).  We could eventually try to flash all windows that
would be affected by the change but that would be non-trivial.

 > (defun apply-rearrange-frame (subtree-only conf norm-size)

I'd call this something like 'transpose-windows--rearrange' so you can
change its behavior whenever you want.

 >    "Transpose frame, or if SUBTREE-ONLY is non-nil, selected window."
 >    (if (eq (next-window) (selected-window))
 >        (message "No windows to transpose.")
 >      (let* ((win-tree (car (window--subtree-with-size
 > 			   (if subtree-only
 > 			       (window-parent (selected-window))
 > 			     (frame-root-window)))))

Is fwin not the same as what 'frame-first-window' returns?  If not,
please telly why in a comment.

 > 	   (fwin (if subtree-only
 > 		     (selected-window)
 > 		   (let ((win (window-child (frame-root-window))))
 > 		     (while (not (window-live-p win))
 > 		       (setq win (window-child win)))
 > 		     win))))
 >        (mapc (lambda (win)
 > 	      (when (and (windowp win)
 > 			 (not (eq win fwin)))
 > 		(delete-window win)))

Please tell in a comment why you flatten win-tree her.

 > 	    (flatten-list win-tree))
 >        (toggle-window-split win-tree fwin conf norm-size)
 >        (select-window fwin))))
 >
 > (defun toggle-window-split (subtree cwin conf norm-size)

Should become window--... and have its arguments described.

 >    (pcase-let ((`(,ilen . ,flen) (if (car subtree)
 > 				    (cons (float (car (cadr subtree)))
 > 					  (float (window-pixel-width cwin)))
 > 				  (cons (float (cadr (cadr subtree)))
 > 					(float (window-pixel-height cwin))))))
 >      (mapc
 >       (pcase-lambda (`(,win . ,size))
 >         (let ((split-size (- (if norm-size
 > 				size
 > 			      (round (* flen  (/ size ilen))))))
 > 	     (split-type
 > 	      (funcall (if (car subtree) 'car 'cdr) conf)))
 > 	 (if (listp win)
 > 	     (toggle-window-split win (split-window cwin
 > 						    split-size
 > 						    split-type
 > 						    t
 > 						    (seq-some
 > 						     (lambda (x)
 > 						       (and (windowp x) x))
 > 						     (flatten-list win)))
 > 				  conf norm-size)

Here I'd write

	     (let ((refer (seq-some
			   (lambda (x)
			     (and (windowp x) x))
			   (flatten-list win))))
	       (toggle-window-split
		win (split-window cwin split-size split-type t refer)
		conf norm-size))

but I won't argue about styles.

 >        (nreverse (cdddr subtree))))
 >      (unless (windowp (caaddr subtree))
 >        (toggle-window-split (caddr subtree) cwin conf norm-size))))

Whenever using things like cdddr, caaddr or caddr, please say in a
comment what these are supposed to be.  You have condensed the original
version of your code considerably so please think about people who
eventually want to understand what the code does.

Many thanks, martin



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

* Re: Add function to rotate/transpose all windows
  2024-09-30  8:57                               ` martin rudalics
@ 2024-10-01  9:17                                 ` pranshu sharma
  2024-10-02  9:04                                   ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-01  9:17 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> Alternatively, we could give 'window-tree' an optional PIXELWISE
> argument so it returns the pixel edges of windows.

You decide

>
> Lines should not exceed 80 characters and you probably want to use
> 'cons' instead of 'list' here.
>
Done
>
> I think you should rename all these into something like
> 'rotate-windows-anticlockwise' and 'transpose-windows' (we are already
> using plurals in 'transpose-lines' and 'transpose-chars').  And we do
> not rotate the entire frame - the minibuffer window stays in place, for
> example.  So I would try to say
>
>   (defun rotate-windows-anticlockwise (&optional frame window)
>     "Rotate windows of FRAME anticlockwise by 90 degrees.
>   FRAME must be a live frame and defaults to the selected frame.  By
>   default rotate the root window of FRAME (or its main window if it
>   differs from the root window).  If WINDOW is non-nil ..."
>
> Which means that we need the additional features:
>
> (1) Rotate windows on any live frame.
>
> (2) Run the function on 'window-main-window' of FRAME unless WINDOW is
> defined and never rotate a side window.  Rotating side windows would
> cause completely undefined behavior because their slots are expected to
> never change.  Hence, if WINDOW is specified and is a side window, say
> that you cannot rotate side windows.
>
> (3) "subtree of selected window" is an undefined concept.  You should
> say something like "to transpose all windows in the same combination as
> WINDOW" instead.  But note that with a slightly more complex layout, no
> average user will know what that combination is (think of C-x 2 C-x 3
> C-x o C-x o C-x 3).  We could eventually try to flash all windows that
> would be affected by the change but that would be non-trivial.
>

Done, I just said all 'windows of' instead.

Also instead of seperate window and frame args, I just added added one
window-or-frame arg.


> I'd call this something like 'transpose-windows--rearrange' so you can
> change its behavior whenever you want.
>

Yes, good idea

> Is fwin not the same as what 'frame-first-window' returns?  If not,
> please telly why in a comment.

It was, I just found out the function exists, thx

> Please tell in a comment why you flatten win-tree her.
>
>> 	    (flatten-list win-tree))
>>        (toggle-window-split win-tree fwin conf norm-size)
>>        (select-window fwin))))
>>
>> (defun toggle-window-split (subtree cwin conf norm-size)
>
> Should become window--... and have its arguments described.

I just did as subroutine style, same as something like
`window--atom-check-1'.

> Here I'd write
>
> 	     (let ((refer (seq-some
> 			   (lambda (x)
> 			     (and (windowp x) x))
> 			   (flatten-list win))))
> 	       (toggle-window-split
> 		win (split-window cwin split-size split-type t refer)
> 		conf norm-size))
>
> but I won't argue about styles.

Although this would be cleaner, but it means the instance of refer would
be stored in memory unnecessarily while the old toggle-window-split
would be going recursivly.

> Whenever using things like cdddr, caaddr or caddr, please say in a
> comment what these are supposed to be.  You have condensed the original
> version of your code considerably so please think about people who
> eventually want to understand what the code does.
>

Yeah I did that, after all, I found out while doing this orignally how
hard the code in in trasnpose-frame.el was to read.  Well even without
the comments(which I added now), the code is more readable now thanks
you adding refer argument to split-window.


---------------------------------------------
(defun window--subtree-with-size (window &optional next)
  "Like `window--subtree' but each window is replaced with the
list: (WINDOW (PIXEL-HEIGHT . PIXEL-WIDTH)), and window-edges is replaced
with the cons cell: (PIXEL-HEIGHT . PIXEL-WIDTH)."
  (let (list)
    (while window
      (setq list
	    (cons
	     (cond
	      ((window-top-child window)
	       (cons t (cons (cons (window-pixel-height window)
				   (window-pixel-width window))
			     (window--subtree-with-size
			      (window-top-child window) t))))
	      ((window-left-child window)
	       (cons nil (cons (cons (window-pixel-height window)
				     (window-pixel-width window))
			       (window--subtree-with-size
				(window-left-child window) t))))
	      (t (list window (cons (window-pixel-height window)
				    (window-pixel-width window)))))
	     list))
      (setq window (when next (window-next-sibling window))))
    (nreverse list)))

(defun rotate-windows-anticlockwise (&optional frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW 90 degrees anticlockwise.
See `rotate-windows-clockwise' for more."
  (interactive `(,(and prefix-arg (window-parent (selected-window)))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (frame-root-window frame-or-window))))
    (transpose-windows--rearrange window '(right . above) nil)))

(defun rotate-windows-clockwise (&optional frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW clockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame. If FRAME-OR-WINDOW is a frame, rotate from the root
window of the frame, otherwise rotate from FRAME-OR-WINDOW."
  (interactive `(,(and prefix-arg (window-parent (selected-window)))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (frame-root-window frame-or-window))))
    (transpose-windows--rearrange window '(left . below) nil)))

(defun flip-windows-horizontally (&optional frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame. If FRAME-OR-WINDOW is a frame, flip from the root
window of the frame, otherwise flip from FRAME-OR-WINDOW."
  (interactive `(,(and prefix-arg (window-parent (selected-window)))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (frame-root-window frame-or-window))))
    (transpose-windows--rearrange window '(below . left) t)))


(defun flip-windows-vertically (&optional frame-or-window)
  "Vertically flip windows of FRAME-OR-WINDOW.
See `flip-windows-horizontally' for more."
  (interactive `(,(and prefix-arg (window-parent (selected-window)))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (frame-root-window frame-or-window))))
    (transpose-windows--rearrange window '(above . right) t)))

(defun transpose-windows (&optional frame-or-window)
  "Transpose windows of FRAME-OR-WINDOW.
Windows are rearanged such that where an horizontal split was used, an
vertical one is instead, and vice versa. FRAME-OR-WINDOW must be a live
frame or window and defaults to the selected frame. If FRAME-OR-WINDOW
is a frame, transpose from the root window of the frame, otherwise
transpose from FRAME-OR-WINDOW."
  (interactive `(,(and prefix-arg (window-parent (selected-window)))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (frame-root-window frame-or-window))))
    (transpose-windows--rearrange window '(right . below) nil)))


(defun transpose-windows--rearrange (frame-or-window conf norm-size)
  "Rearrange windows of FRAME-OR-WINDOW recursively.
CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
horizontal split is called when splitting a window that was previously
horizontally split, and VERTICAL-SPLIT for a window that was previously
vertically split.  When is NORM-SIZE non-nil, the size argument of the
window-split is converted from vertical to horizontal or vice versa,
with the same proportion of the total split."
  (let ((rwin (if (framep frame-or-window)
		  (frame-root-window frame-root-window)
		frame-or-window)))
    (if (or (not rwin)
	    (zerop (window-child-count rwin)))
	(message "Not enough windows")
      (let* ((fwin (frame-first-window rwin))
	     (selwin (frame-selected-window frame-or-window))
	     (win-tree (car (window--subtree-with-size rwin))))
	;; All child windows need to be recursively deleted.
	(mapc (lambda (win)
		(when (and (windowp win)
			   (not (eq win fwin)))
		  (delete-window win)))
	      ;; We know for sure that first 2 in the list are not
	      ;; windows.
	      (cddr (flatten-list win-tree)))
	(transpose-windows--rearrange-1 win-tree fwin conf norm-size)
	;; Go back to previously selected window.
	(select-window selwin)))))

(defun transpose-windows--rearrange-1 (subtree cwin conf norm-size)
  "Subroutine of `transpose-windows--rearrange'."
  ;; `ilen' is the max size a window could be of given the split type.
  ;; `flen' is max size the window could be converted to the opposite
  ;; of the given split type.
  (pcase-let ((`(,ilen . ,flen) (if (car subtree)
				    (cons (float (car (cadr subtree)))
					  (float (window-pixel-width cwin)))
				  (cons (float (cdr (cadr subtree)))
					(float (window-pixel-height cwin))))))
    (mapc
     (pcase-lambda (`(,win . ,size))
       (let ((split-size (- (if norm-size
				size
			      (round (* flen  (/ size ilen))))))
	     (split-type
	      (funcall (if (car subtree) 'car 'cdr) conf)))
	 (if (listp win)
	     ;; `win' is a window subtree.
	     (transpose-windows--rearrange-1 win (split-window cwin
						    split-size
						    split-type
						    t
						    (seq-some
						     (lambda (x)
						       (and (windowp x) x))
						     (flatten-list win)))
					     conf norm-size)
	   ;; `win' is a window.
	   (split-window cwin split-size
			 split-type t
			 win))))
     (mapcar
      (lambda (e)
	(let ((window? (if (windowp (car e)) (car e) e)))
	  (cons window?
		;; The relevent size of the window.
		(if (car subtree)
		    (car (cadr e))
		  (cdr (cadr e))))))
      ;; By using cdddr, we ignore over window split type, sizes and
      ;; the first window (it's implicitly created).
      (nreverse (cdddr subtree))))
    ;; (caaddr subtree) is the first window.
    (unless (windowp (caaddr subtree))
      (transpose-windows--rearrange-1 (caddr subtree) cwin conf norm-size))))



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

* Re: Add function to rotate/transpose all windows
  2024-10-01  9:17                                 ` pranshu sharma
@ 2024-10-02  9:04                                   ` martin rudalics
  2024-10-03  7:06                                     ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-02  9:04 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 >> Alternatively, we could give 'window-tree' an optional PIXELWISE
 >> argument so it returns the pixel edges of windows.
 >
 > You decide

Better not - the fact that 'window-tree' does not report the edges of
live windows constitutes bad design.  Let's not support it.  Rather
let's make it a normal function called 'window-tree-pixel-sizes'.  I
would write its doc string as

   (defun window-tree-pixel-sizes (window &optional next)
     "Return pixel sizes of all windows rooted at WINDOW.
   The return value is a list where each window is represented either by a
   triple whose first element is either t for an internal window that is a
   horizontal combination, nil for an internal window that is a vertical
   combination, or the window itself for a live window.  The second element
   is a cons of the pixel height and pixel width of the window.  The third
   element is specified for internal windows only and recursively lists
   that window's child windows using the same triple structure."

but you would have to check whether it really does what I wrote.

 > (defun rotate-windows-anticlockwise (&optional frame-or-window)
 >   "Rotate windows of FRAME-OR-WINDOW 90 degrees anticlockwise.
 > See `rotate-windows-clockwise' for more."

I think it's better to write out the full doc-string here first and
in a final sentence say

   See `rotate-windows-clockwise' for how to rotate windows in the
   opposite direction.

 > (defun rotate-windows-clockwise (&optional frame-or-window)
 >   "Rotate windows of FRAME-OR-WINDOW clockwise by 90 degrees.
 > FRAME-OR-WINDOW must be a live frame or window and defaults to the
 > selected frame. If FRAME-OR-WINDOW is a frame, rotate from the root
 > window of the frame, otherwise rotate from FRAME-OR-WINDOW."

Please add two spaces after the end of each sentence like

   selected frame.  If FRAME-OR-WINDOW is a frame, ...

Also I would omit the "from" in "rotate from".  And I would add a cross
reference to 'rotate-windows-anticlockwise' at the end.

 > (defun flip-windows-horizontally (&optional frame-or-window)
 >   "Horizontally flip windows of FRAME-OR-WINDOW.
 > FRAME-OR-WINDOW must be a live frame or window and defaults to the
 > selected frame. If FRAME-OR-WINDOW is a frame, flip from the root
 > window of the frame, otherwise flip from FRAME-OR-WINDOW."

Here you should probably first say what "flip" means.  It's obvious when
you have a frame with two side-by-side live windows.  It's already less
obvious with three side-by-side windows and even less so with more
complex layouts.

 > (defun transpose-windows (&optional frame-or-window)
 >   "Transpose windows of FRAME-OR-WINDOW.
 > Windows are rearanged such that where an horizontal split was used, an
 > vertical one is instead, and vice versa. FRAME-OR-WINDOW must be a live
 > frame or window and defaults to the selected frame. If FRAME-OR-WINDOW
 > is a frame, transpose from the root window of the frame, otherwise
 > transpose from FRAME-OR-WINDOW."

Please us active voice like

   Rearrange windows such that where a horizontal split was used a
   vertical one is used instead, and vice versa.

and again remove the "from" in "transpose from".

 > (defun transpose-windows--rearrange (frame-or-window conf norm-size)

I'd call this just 'window--transpose'.

 > "Rearrange windows of FRAME-OR-WINDOW recursively.
 > CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
 > horizontal split is called when splitting a window that was previously
 > horizontally split, and VERTICAL-SPLIT for a window that was previously
 > vertically split.

CONF is confusing.  Why is it a cons cell in the first place?  Wouldn't
a simple boolean - t for horizontal and nil for vertical - suffice?  If
not, please explain why.

 > When is NORM-SIZE non-nil, the size argument of the

I think it's just the pixel size and not the normal size, right?  Also
you should write it as "When NORM-SIZE is non-nil ..."

 > 		  (frame-root-window frame-root-window)

This doesn't look right.

 > 	(message "Not enough windows")

Better is "No windows to transpose".

 >       (let* ((fwin (frame-first-window rwin))

Note that 'frame-first-window' returns the first live window on its
frame.  What if you want to flip some child windows only?

 > 	(select-window selwin)))))

should become (set-frame-selected-window selwin) for the case that you
rotate windows on a non-selected frame.

 > (defun transpose-windows--rearrange-1 (subtree cwin conf norm-size)

And this I'd call 'window--transpose-1'

 >   "Subroutine of `transpose-windows--rearrange'."

Again please describe all arguments in the doc-string.

And please handle the case where a frame contains side windows.  Just
replace 'frame-root-window' with 'window--main-window' everywhere and
talk about "main window" instead of "root window".

Thanks, martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-02  9:04                                   ` martin rudalics
@ 2024-10-03  7:06                                     ` pranshu sharma
  2024-10-03  8:17                                       ` martin rudalics
  2024-10-08 18:35                                       ` Juri Linkov
  0 siblings, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-10-03  7:06 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>> Alternatively, we could give 'window-tree' an optional PIXELWISE
>>> argument so it returns the pixel edges of windows.
>>
>> You decide
>
> Better not - the fact that 'window-tree' does not report the edges of
> live windows constitutes bad design.  Let's not support it.  Rather
> let's make it a normal function called 'window-tree-pixel-sizes'.  I
> would write its doc string as
>
>   (defun window-tree-pixel-sizes (window &optional next)
>     "Return pixel sizes of all windows rooted at WINDOW.
>   The return value is a list where each window is represented either by a
>   triple whose first element is either t for an internal window that is a
>   horizontal combination, nil for an internal window that is a vertical
>   combination, or the window itself for a live window.  The second element
>   is a cons of the pixel height and pixel width of the window.  The third
>   element is specified for internal windows only and recursively lists
>   that window's child windows using the same triple structure."
>
> but you would have to check whether it really does what I wrote.

yes this does exactly that, thanks

>> (defun rotate-windows-anticlockwise (&optional frame-or-window)
>>   "Rotate windows of FRAME-OR-WINDOW 90 degrees anticlockwise.
>> See `rotate-windows-clockwise' for more."
>
> I think it's better to write out the full doc-string here first and
> in a final sentence say
>
>   See `rotate-windows-clockwise' for how to rotate windows in the
>   opposite direction.

k

>
>> (defun flip-windows-horizontally (&optional frame-or-window)
>>   "Horizontally flip windows of FRAME-OR-WINDOW.
>> FRAME-OR-WINDOW must be a live frame or window and defaults to the
>> selected frame. If FRAME-OR-WINDOW is a frame, flip from the root
>> window of the frame, otherwise flip from FRAME-OR-WINDOW."
>
> Here you should probably first say what "flip" means.  It's obvious when
> you have a frame with two side-by-side live windows.  It's already less
> obvious with three side-by-side windows and even less so with more
> complex layouts.

I explained it just using the metaphor of a reflection

>> (defun transpose-windows--rearrange (frame-or-window conf norm-size)
>
> I'd call this just 'window--transpose'.

done

>
>> "Rearrange windows of FRAME-OR-WINDOW recursively.
>> CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
>> horizontal split is called when splitting a window that was previously
>> horizontally split, and VERTICAL-SPLIT for a window that was previously
>> vertically split.
>
> CONF is confusing.  Why is it a cons cell in the first place?  Wouldn't
> a simple boolean - t for horizontal and nil for vertical - suffice?  If
> not, please explain why.

You can also have below split and abvoe split, not just nil and t

>
>> When is NORM-SIZE non-nil, the size argument of the
>
> I think it's just the pixel size and not the normal size, right?  Also
> you should write it as "When NORM-SIZE is non-nil ..."

It's a bolean that states if or not the height or width should be
converted to width or height respecitlcly. I changed the name.

> Note that 'frame-first-window' returns the first live window on its
> frame.  What if you want to flip some child windows only?

I didnt read docstring properly when I rewrote function, mb, played
cardi b bit too loud.  I kind of assumed since it accepts
frame-or-window it would return first child.  But fixed that now.

>> 	(select-window selwin)))))
>
> should become (set-frame-selected-window selwin) for the case that you
> rotate windows on a non-selected frame.

Thanks, I was looking for this function

>
>> (defun transpose-windows--rearrange-1 (subtree cwin conf norm-size)
>
> And this I'd call 'window--transpose-1'
>
> Again please describe all arguments in the doc-string.

I already explained the arguments in transpose-windows--rearrange.  As
far as I can tell most other -1 postfix don't explain their arguments eg
window--resize-reset-1.

btw, what are the plans for the keybindings? imo they should be added
top level in C-x w map, as there are lots of spots avaiable, and there
are only 5 total new functions.


(defun window-tree-pixel-sizes (window &optional next)
  "Return pixel sizes of all windows rooted at WINDOW.
The return value is a list where each window is represented either by a
triple whose first element is either t for an internal window that is a
horizontal combination, nil for an internal window that is a vertical
combination, or the window itself for a live window.  The second element
is a cons of the pixel height and pixel width of the window.  The third
element is specified for internal windows only and recursively lists
that window's child windows using the same triple structure."
  (let (list)
    (while window
      (setq list
	    (cons
	     (cond
	      ((window-top-child window)
	       (cons t (cons (cons (window-pixel-height window)
				   (window-pixel-width window))
			     (window-tree-pixel-sizes
			      (window-top-child window) t))))
	      ((window-left-child window)
	       (cons nil (cons (cons (window-pixel-height window)
				     (window-pixel-width window))
			       (window-tree-pixel-sizes
				(window-left-child window) t))))
	      (t (list window (cons (window-pixel-height window)
				    (window-pixel-width window)))))
	     list))
      (setq window (when next (window-next-sibling window))))
    (nreverse list)))

(defun rotate-windows-anticlockwise (&optional frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW anti-clockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
of the frame, otherwise rotate FRAME-OR-WINDOW.  See
`rotate-windows-clockwise' for how to rotate windows in the opposite
direction"
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (transpose-windows--rearrange window '(right . above) nil)))

(defun rotate-windows-clockwise (&optional frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW clockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
of the frame, otherwise rotate FRAME-OR-WINDOW.  See
`rotate-windows-anticlockwise' for how to rotate windows in the opposite
direction"
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (transpose-windows--rearrange window '(left . below) nil)))

(defun flip-windows-horizontally (&optional frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.  When the windows are
flipped horzontally, the window layout is made to it's reflection from
the side edge.  FRAME-OR-WINDOW must be a live frame or window and
defaults to the selected frame. If FRAME-OR-WINDOW is a frame, flip from
the main window of the frame, otherwise flip from FRAME-OR-WINDOW.  See
`flip-windows-vertically' for how to flip windows vertically."
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (transpose-windows--rearrange window '(below . left) t)))


(defun flip-windows-vertically (&optional frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.  When the windows are
flipped vertically, the window layout is made to it's reflection from
the top edge.  FRAME-OR-WINDOW must be a live frame or window and
defaults to the selected frame. If FRAME-OR-WINDOW is a frame, flip from
the main window of the frame, otherwise flip from FRAME-OR-WINDOW.  See
`flip-windows-horizontally' for how to flip windows horizontally."
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (transpose-windows--rearrange window '(above . right) t)))

(defun transpose-windows (&optional frame-or-window)
  "Transpose windows of FRAME-OR-WINDOW.
Rearrange windows such that where a horizontal split was used a vertical
one is used instead, and vice versa.  FRAME-OR-WINDOW must be a live
frame or window and defaults to the selected frame.  If FRAME-OR-WINDOW
is a frame, transpose the main window of the frame, otherwise
transpose FRAME-OR-WINDOW."
  (interactive `(,(and current-prefix-arg (window-parent (selected-window)))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (transpose-windows--rearrange window '(right . below) nil)))


(defun transpose-windows--rearrange (frame-or-window conf do-not-convert-size)
  "Rearrange windows of FRAME-OR-WINDOW recursively.
CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
HORIZONTAL-SPLIT will be used as the third argument of `split-window'
when splitting a window that was previously horizontally split, and
VERTICAL-SPLIT as third argument `split-window' for a window that was
previously vertically split.  When is DO-NOT-CONVERT-SIZE non-nil, the
size argument of the window-split is converted from vertical to
horizontal or vice versa, with the same proportion of the total split."
  (pcase-let ((`(,rwin . ,frame)
	       (if (framep frame-or-window)
		   (cons (window-main-window frame-or-window) frame-or-window)
		 (cons frame-or-window (window-frame frame-or-window)))))
    (if (or (not rwin)
	    (zerop (window-child-count rwin)))
	(message "No windows to transpose")
      (let* ((fwin rwin)
	     (selwin (frame-selected-window frame-or-window))
	     (win-tree (car (window-tree-pixel-sizes rwin))))
	(while (not (window-live-p fwin))
	  (setq fwin (window-child fwin)))
	;; All child windows need to be recursively deleted.
	(mapc (lambda (win)
		(when (and (windowp win)
			   (not (eq win fwin)))
		  (delete-window win)))
	      ;; We know for sure that first 2 in the list are not
	      ;; windows.
	      (cddr (flatten-list win-tree)))
	(window--transpose-1 win-tree fwin conf do-not-convert-size)
	;; Go back to previously selected window.
	(set-frame-selected-window frame selwin)))))

(defun window--transpose-1 (subtree cwin conf do-not-convert-size)
  "Subroutine of `transpose-windows--rearrange'."
  ;; `ilen' is the max size a window could be of given the split type.
  ;; `flen' is max size the window could be converted to the opposite
  ;; of the given split type.
  (pcase-let ((`(,ilen . ,flen) (if (car subtree)
				    (cons (float (car (cadr subtree)))
					  (float (window-pixel-width cwin)))
				  (cons (float (cdr (cadr subtree)))
					(float (window-pixel-height cwin))))))
    (mapc
     (pcase-lambda (`(,win . ,size))
       (let ((split-size (- (if do-not-convert-size
				size
			      (round (* flen  (/ size ilen))))))
	     (split-type
	      (funcall (if (car subtree) 'car 'cdr) conf)))
	 (if (listp win)
	     ;; `win' is a window subtree.
	     (window--transpose-1 win (split-window cwin
						    split-size
						    split-type
						    t
						    (seq-some
						     (lambda (x)
						       (and (windowp x) x))
						     (flatten-list win)))
				  conf do-not-convert-size)
	   ;; `win' is a window.
	   (split-window cwin split-size
			 split-type t
			 win))))
     (mapcar
      (lambda (e)
	(let ((window? (if (windowp (car e)) (car e) e)))
	  (cons window?
		;; The relevent size of the window.
		(if (car subtree)
		    (car (cadr e))
		  (cdr (cadr e))))))
      ;; By using cdddr, we ignore over window split type, sizes and
      ;; the first window (it's implicitly created).
      (nreverse (cdddr subtree))))
    ;; (caaddr subtree) is the first window.
    (unless (windowp (caaddr subtree))
      (window--transpose-1 (caddr subtree) cwin conf do-not-convert-size))))



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

* Re: Add function to rotate/transpose all windows
  2024-10-03  7:06                                     ` pranshu sharma
@ 2024-10-03  8:17                                       ` martin rudalics
  2024-10-03 10:09                                         ` pranshu sharma
  2024-10-08 18:35                                       ` Juri Linkov
  1 sibling, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-03  8:17 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 >>> (defun transpose-windows--rearrange (frame-or-window conf norm-size)
 >>
 >> I'd call this just 'window--transpose'.
 >
 > done

Not really IIUC.

 >> CONF is confusing.  Why is it a cons cell in the first place?  Wouldn't
 >> a simple boolean - t for horizontal and nil for vertical - suffice?  If
 >> not, please explain why.
 >
 > You can also have below split and abvoe split, not just nil and t

Aha...  So the values are the possible values of the SIDE argument of
'split-window'.

 > I already explained the arguments in transpose-windows--rearrange.  As
 > far as I can tell most other -1 postfix don't explain their arguments eg
 > window--resize-reset-1.

Then please say "the arguments are the same as for `window--transpose'".

 > btw, what are the plans for the keybindings? imo they should be added
 > top level in C-x w map, as there are lots of spots avaiable, and there
 > are only 5 total new functions.

Once we have installed it, we'll consult Juri Linkov.  He knows more
about keybindings in this area.  BTW, you could have a look at the
function 'window-swap-states'.  IIUC it should then be rewritten in
terms of the 'flip-windows-...' functions to get rid of window states
and the overlay rigmarole.

 > (defun rotate-windows-anticlockwise (&optional frame-or-window)
 >    "Rotate windows of FRAME-OR-WINDOW anti-clockwise by 90 degrees.
 > FRAME-OR-WINDOW must be a live frame or window and defaults to the
 > selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
 > of the frame, otherwise rotate FRAME-OR-WINDOW.  See
 > `rotate-windows-clockwise' for how to rotate windows in the opposite
 > direction"
 >    (interactive `(,(and current-prefix-arg (window-parent))))

Please test what happens if you have a keybinding for this and you hit
that key in the minibuffer window, for example, after typing C-h f.

 > (defun flip-windows-horizontally (&optional frame-or-window)
 >    "Horizontally flip windows of FRAME-OR-WINDOW.  When the windows are
 > flipped horzontally, the window layout is made to it's reflection from
 > the side edge.  FRAME-OR-WINDOW must be a live frame or window and
 > defaults to the selected frame. If FRAME-OR-WINDOW is a frame, flip from

Still a space missing after the "."

 > (defun flip-windows-vertically (&optional frame-or-window)
 >    "Horizontally flip windows of FRAME-OR-WINDOW.  When the windows are

Newline missing after ".".

 > flipped vertically, the window layout is made to it's reflection from
 > the top edge.  FRAME-OR-WINDOW must be a live frame or window and
 > defaults to the selected frame. If FRAME-OR-WINDOW is a frame, flip from

Space missing after ".".

 > (defun transpose-windows--rearrange (frame-or-window conf do-not-convert-size)

Rename to 'window--transpose' as you claimed above.

 >    "Rearrange windows of FRAME-OR-WINDOW recursively.
 > CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
 > HORIZONTAL-SPLIT will be used as the third argument of `split-window'
 > when splitting a window that was previously horizontally split, and
 > VERTICAL-SPLIT as third argument `split-window' for a window that was

... third argument _of_ ...

 > previously vertically split.  When is DO-NOT-CONVERT-SIZE non-nil, the

Should become "If DO-NOT-CONVERT-SIZE is non-nil, ..."

 > size argument of the window-split is converted from vertical to
 > horizontal or vice versa, with the same proportion of the total split."
 >    (pcase-let ((`(,rwin . ,frame)
 > 	       (if (framep frame-or-window)
 > 		   (cons (window-main-window frame-or-window) frame-or-window)
 > 		 (cons frame-or-window (window-frame frame-or-window)))))
 >      (if (or (not rwin)
 > 	    (zerop (window-child-count rwin)))

Actually

(zerop (window-child-count rwin))

is equivalent to

(window-live-p rwin)

and the latter should be cheaper.

 > 		;; The relevent size of the window.

I think "respective size" is what you mean.

 >        ;; By using cdddr, we ignore over window split type, sizes and

Rather "ignore window split type" without the "over".

 >        ;; the first window (it's implicitly created).
 >        (nreverse (cdddr subtree))))
 >      ;; (caaddr subtree) is the first window.
 >      (unless (windowp (caaddr subtree))
 >        (window--transpose-1 (caddr subtree) cwin conf do-not-convert-size))))

What I forgot to ask so far: Have you completed copyright assignment
for Emacs?  I can't look into this myself.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-03  8:17                                       ` martin rudalics
@ 2024-10-03 10:09                                         ` pranshu sharma
  2024-10-03 14:18                                           ` martin rudalics
  2024-10-03 15:12                                           ` Eli Zaretskii
  0 siblings, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-10-03 10:09 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>> I'd call this just 'window--transpose'.
>>
>> done
>
> Not really IIUC.

sorry, it is done now

> Then please say "the arguments are the same as for
> `window--transpose'".

yes, done

> Once we have installed it, we'll consult Juri Linkov.  He knows more
> about keybindings in this area.  BTW, you could have a look at the
> function 'window-swap-states'.  IIUC it should then be rewritten in
> terms of the 'flip-windows-...' functions to get rid of window states
> and the overlay rigmarole.

Hmm, the only way I can imagine using the flip thing is modifying window
tree to swap 2 windows in the subtree, and then set conf to (below
. left) and do-not-convert-size to nil.  This will just rebuild the
whole window tree.

>
> Please test what happens if you have a keybinding for this and you hit
> that key in the minibuffer window, for example, after typing C-h f.
>
It just acts as if you are calling it without prefix arg, I think that
is expected behaviour imo.  It just goes back to orignal if
read-minibuffer-restore-windows is non nil.

> Still a space missing after the "."
>
>> (defun flip-windows-vertically (&optional frame-or-window)
>>    "Horizontally flip windows of FRAME-OR-WINDOW.  When the windows are
>
> Newline missing after ".".
>
>> flipped vertically, the window layout is made to it's reflection from
>> the top edge.  FRAME-OR-WINDOW must be a live frame or window and
>> defaults to the selected frame. If FRAME-OR-WINDOW is a frame, flip from
>
> Space missing after ".".
>> (defun transpose-windows--rearrange (frame-or-window conf do-not-convert-size)
> ... third argument _of_ ...
>
>> previously vertically split.  When is DO-NOT-CONVERT-SIZE non-nil, the
>

ah puncutation comments, I just failed my english writing exam on
wednesday :(

> Actually
>
> (zerop (window-child-count rwin))
>
> is equivalent to
>
> (window-live-p rwin)
>
> and the latter should be cheaper.

Thanks, I applied that change


> What I forgot to ask so far: Have you completed copyright assignment
> for Emacs?  I can't look into this myself.

I sent the form to assign@gnu.org, 15 days ago, still waiting for reply.
Does it normally take this long?

Code:
----------------------------------------

(defun window-tree-pixel-sizes (window &optional next)
  "Return pixel sizes of all windows rooted at WINDOW.
The return value is a list where each window is represented either by a
triple whose first element is either t for an internal window that is a
horizontal combination, nil for an internal window that is a vertical
combination, or the window itself for a live window.  The second element
is a cons of the pixel height and pixel width of the window.  The third
element is specified for internal windows only and recursively lists
that window's child windows using the same triple structure."
  (let (list)
    (while window
      (setq list
	    (cons
	     (cond
	      ((window-top-child window)
	       (cons t (cons (cons (window-pixel-height window)
				   (window-pixel-width window))
			     (window-tree-pixel-sizes
			      (window-top-child window) t))))
	      ((window-left-child window)
	       (cons nil (cons (cons (window-pixel-height window)
				     (window-pixel-width window))
			       (window-tree-pixel-sizes
				(window-left-child window) t))))
	      (t (list window (cons (window-pixel-height window)
				    (window-pixel-width window)))))
	     list))
      (setq window (when next (window-next-sibling window))))
    (nreverse list)))

(defun rotate-windows-anticlockwise (&optional frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW anticlockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
of the frame, otherwise rotate FRAME-OR-WINDOW.  See
`rotate-windows-clockwise' for how to rotate windows in the opposite
direction"
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (window--transpose window '(right . above) nil)))

(defun rotate-windows-clockwise (&optional frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW clockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
of the frame, otherwise rotate FRAME-OR-WINDOW.  See
`rotate-windows-anticlockwise' for how to rotate windows in the opposite
direction"
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (window--transpose window '(left . below) nil)))

(defun flip-windows-horizontally (&optional frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.
When the windows are flipped horzontally, the window layout is made to
it's reflection from the side edge.  FRAME-OR-WINDOW must be a live
frame or window and defaults to the selected frame.  If FRAME-OR-WINDOW
is a frame, flip from the main window of the frame, otherwise flip from
FRAME-OR-WINDOW.  See `flip-windows-vertically' for how to flip windows
vertically."
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (window--transpose window '(below . left) t)))

(defun flip-windows-vertically (&optional frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.
When the windows are flipped vertically, the window layout is made to
it's reflection from the top edge.  FRAME-OR-WINDOW must be a live frame
or window and defaults to the selected frame.  If FRAME-OR-WINDOW is a
frame, flip from the main window of the frame, otherwise flip from
FRAME-OR-WINDOW.  See `flip-windows-horizontally' for how to flip
windows horizontally."
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (window--transpose window '(above . right) t)))

(defun transpose-windows (&optional frame-or-window)
  "Transpose windows of FRAME-OR-WINDOW.
Rearrange windows such that where a horizontal split was used a vertical
one is used instead, and vice versa.  FRAME-OR-WINDOW must be a live
frame or window and defaults to the selected frame.  If FRAME-OR-WINDOW
is a frame, transpose the main window of the frame, otherwise
transpose FRAME-OR-WINDOW."
  (interactive `(,(and current-prefix-arg (window-parent))))
  (let ((window (if (windowp frame-or-window)
		    frame-or-window
		  (window-main-window frame-or-window))))
    (window--transpose window '(right . below) nil)))


(defun window--transpose (frame-or-window conf do-not-convert-size)
  "Rearrange windows of FRAME-OR-WINDOW recursively.
CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
HORIZONTAL-SPLIT will be used as the third argument of `split-window'
when splitting a window that was previously horizontally split, and
VERTICAL-SPLIT as third argument of `split-window' for a window that was
previously vertically split.  If DO-NOT-CONVERT-SIZE non-nil, the size
argument of the window-split is converted from vertical to horizontal or
vice versa, with the same proportion of the total split."
  (pcase-let ((`(,rwin . ,frame)
	       (if (framep frame-or-window)
		   (cons (window-main-window frame-or-window) frame-or-window)
		 (cons frame-or-window (window-frame frame-or-window)))))
    (if (or (not rwin)
	    (window-live-p rwin))
	(message "No windows to transpose")
      (let* ((fwin rwin)
	     (selwin (frame-selected-window frame-or-window))
	     (win-tree (car (window-tree-pixel-sizes rwin))))
	(while (not (window-live-p fwin))
	  (setq fwin (window-child fwin)))
	;; All child windows need to be recursively deleted.
	(mapc (lambda (win)
		(when (and (windowp win)
			   (not (eq win fwin)))
		  (delete-window win)))
	      ;; We know for sure that first 2 in the list are not
	      ;; windows.
	      (cddr (flatten-list win-tree)))
	(window--transpose-1 win-tree fwin conf do-not-convert-size)
	;; Go back to previously selected window.
	(set-frame-selected-window frame selwin)))))

(defun window--transpose-1 (subtree cwin conf do-not-convert-size)
  "Subroutine of `window--transpose'.
SUBTREE must be in the format of the result of
`window-tree-pixel-sizes'.  CWIN is the current window through which the
window splits are made.  The CONF and DO-NOT-CONVERT-SIZE arguments are
the same as the ones in `window--transpose'."
  ;; `ilen' is the max size a window could be of given the split type.
  ;; `flen' is max size the window could be converted to the opposite
  ;; of the given split type.
  (pcase-let ((`(,ilen . ,flen) (if (car subtree)
				    (cons (float (car (cadr subtree)))
					  (float (window-pixel-width cwin)))
				  (cons (float (cdr (cadr subtree)))
					(float (window-pixel-height cwin))))))
    (mapc
     (pcase-lambda (`(,win . ,size))
       (let ((split-size (- (if do-not-convert-size
				size
			      (round (* flen  (/ size ilen))))))
	     (split-type
	      (funcall (if (car subtree) 'car 'cdr) conf)))
	 (if (listp win)
	     ;; `win' is a window subtree.
	     (window--transpose-1 win (split-window cwin
						    split-size
						    split-type
						    t
						    (seq-some
						     (lambda (x)
						       (and (windowp x) x))
						     (flatten-list win)))
				  conf do-not-convert-size)
	   ;; `win' is a window.
	   (split-window cwin split-size
			 split-type t
			 win))))
     (mapcar
      (lambda (e)
	(let ((window? (if (windowp (car e)) (car e) e)))
	  (cons window?
		;; The respective size of the window.
		(if (car subtree)
		    (car (cadr e))
		  (cdr (cadr e))))))
      ;; By using cdddr, we ignore window split type, sizes and the
      ;; first window (it's implicitly created).
      (nreverse (cdddr subtree))))
    ;; (caaddr subtree) is the first window.
    (unless (windowp (caaddr subtree))
      (window--transpose-1 (caddr subtree) cwin conf do-not-convert-size))))



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

* Re: Add function to rotate/transpose all windows
  2024-10-03 10:09                                         ` pranshu sharma
@ 2024-10-03 14:18                                           ` martin rudalics
  2024-10-04  5:50                                             ` pranshu sharma
  2024-10-03 15:12                                           ` Eli Zaretskii
  1 sibling, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-03 14:18 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 >> BTW, you could have a look at the
 >> function 'window-swap-states'.  IIUC it should then be rewritten in
 >> terms of the 'flip-windows-...' functions to get rid of window states
 >> and the overlay rigmarole.
 >
 > Hmm, the only way I can imagine using the flip thing is modifying window
 > tree to swap 2 windows in the subtree, and then set conf to (below
 > . left) and do-not-convert-size to nil.  This will just rebuild the
 > whole window tree.

I thought when the two windows are neighbors (which they often enough
are) we could flip them.  But I think the solution is to (1) add for
each live window its number in 'window-state-get' (we need a function
'window-number' for that) and (2) in 'window-state-put', whenever we
split a window, try to find out whether a window with the respective
number is in 'window-dead-windows-table' and reuse it in that case.

 > I sent the form to assign@gnu.org, 15 days ago, still waiting for reply.
 > Does it normally take this long?

It can take longer.  Don't worry.  They'll reply before Emacs 31 gets
out.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-03 10:09                                         ` pranshu sharma
  2024-10-03 14:18                                           ` martin rudalics
@ 2024-10-03 15:12                                           ` Eli Zaretskii
  1 sibling, 0 replies; 111+ messages in thread
From: Eli Zaretskii @ 2024-10-03 15:12 UTC (permalink / raw)
  To: pranshu sharma; +Cc: rudalics, emacs-devel

> From: pranshu sharma <pranshusharma366@gmail.com>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Thu, 03 Oct 2024 20:09:43 +1000
> 
> > What I forgot to ask so far: Have you completed copyright assignment
> > for Emacs?  I can't look into this myself.
> 
> I sent the form to assign@gnu.org, 15 days ago, still waiting for reply.

Please ping them and CC me.



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

* Re: Add function to rotate/transpose all windows
  2024-10-03 14:18                                           ` martin rudalics
@ 2024-10-04  5:50                                             ` pranshu sharma
  2024-10-04  8:08                                               ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-04  5:50 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> Hmm, the only way I can imagine using the flip thing is modifying window
>> tree to swap 2 windows in the subtree, and then set conf to (below
>> . left) and do-not-convert-size to nil.  This will just rebuild the
>> whole window tree.
>
> I thought when the two windows are neighbors (which they often enough
> are) we could flip them.

Flip windows would work to swap when they are sibling windows, and
neigbor windows can sometimes not be siblings.

> But I think the solution is to (1) add for each live window its number
> in 'window-state-get' (we need a function 'window-number' for that)
> and (2) in 'window-state-put', whenever we split a window, try to find
> out whether a window with the respective number is in
> 'window-dead-windows-table' and reuse it in that case.

I can't comment much cuz I'm not really familiar with these functions,
but one thing that pops out is would maintaining a dead windows table
take up lot of resources?

Also do you think a function like 'window-rebuild-tree' would be useful,
and it accepts the a tree like the output of window-tree-pixel-sizes.
rn this is possible by using the following arguments for
window--transpose-1:
(window--transpose-1 tree-you-want (below . left) t)
However this is not really obvious.

I think it could be useful to add a wrapper with more obvious name



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

* Re: Add function to rotate/transpose all windows
  2024-10-04  5:50                                             ` pranshu sharma
@ 2024-10-04  8:08                                               ` martin rudalics
  2024-10-04 15:10                                                 ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-04  8:08 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 >> I thought when the two windows are neighbors (which they often enough
 >> are) we could flip them.
 >
 > Flip windows would work to swap when they are sibling windows, and
 > neigbor windows can sometimes not be siblings.

Right.  I meant siblings (it was the typical two windows frame I had in
mind).

 > I can't comment much cuz I'm not really familiar with these functions,
 > but one thing that pops out is would maintaining a dead windows table
 > take up lot of resources?

No.  It's already here via the variable 'window-dead-windows-table'.

 > Also do you think a function like 'window-rebuild-tree' would be useful,
 > and it accepts the a tree like the output of window-tree-pixel-sizes.
 > rn this is possible by using the following arguments for
 > window--transpose-1:
 > (window--transpose-1 tree-you-want (below . left) t)
 > However this is not really obvious.
 >
 > I think it could be useful to add a wrapper with more obvious name

You would have to tell me in more detail what 'window-rebuild-tree'
would do.  One thing 'window-tree-pixel-sizes' should then possibly do
is to include the identity of internal windows so 'window-rebuild-tree'
could resurrect them as well.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-04  8:08                                               ` martin rudalics
@ 2024-10-04 15:10                                                 ` pranshu sharma
  2024-10-05 14:43                                                   ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-04 15:10 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> I can't comment much cuz I'm not really familiar with these functions,
>> but one thing that pops out is would maintaining a dead windows table
>> take up lot of resources?
>
> No.  It's already here via the variable 'window-dead-windows-table'.

I see

>> Also do you think a function like 'window-rebuild-tree' would be useful,
>> and it accepts the a tree like the output of window-tree-pixel-sizes.
>> rn this is possible by using the following arguments for
>> window--transpose-1:
>> (window--transpose-1 tree-you-want (below . left) t)
>> However this is not really obvious.
>>
>> I think it could be useful to add a wrapper with more obvious name
>
> You would have to tell me in more detail what 'window-rebuild-tree'
> would do.

Say window window-tree-pixel-sizes retruns:

((t (918 . 562) (#<window 7 on *scratch*> (442 . 562)) (#<window 23 on
*scratch*> (476 . 562))))

Then if you C-x 1, and then:
(window-rebuild-tree
  '((t (918 . 562) (#<window 7 on *scratch*> (442 . 562)) (#<window 23 on
   *scratch*> (476 . 562)))))

It should rebuild the whole thing to the orignal

> One thing 'window-tree-pixel-sizes' should then possibly do is to
> include the identity of internal windows so 'window-rebuild-tree'
> could resurrect them as well.

idk what you mean by this, is the identity not included in window object
itself, as in does refer argument not work with it?

also I found that when using tabs from tab-bar-mode, calling functions
like flip windows 2 times in a row will fuck up the tabs.  I tested it
with orignal transpose-frame.el and it does not have this problem, mayeb
it is something to do with the C new patch refer argument code?



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

* Re: Add function to rotate/transpose all windows
  2024-10-04 15:10                                                 ` pranshu sharma
@ 2024-10-05 14:43                                                   ` martin rudalics
  2024-10-06  2:54                                                     ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-05 14:43 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 >> You would have to tell me in more detail what 'window-rebuild-tree'
 >> would do.
 >
 > Say window window-tree-pixel-sizes retruns:
 >
 > ((t (918 . 562) (#<window 7 on *scratch*> (442 . 562)) (#<window 23 on
 > *scratch*> (476 . 562))))
 >
 > Then if you C-x 1, and then:
 > (window-rebuild-tree
 >    '((t (918 . 562) (#<window 7 on *scratch*> (442 . 562)) (#<window 23 on
 >     *scratch*> (476 . 562)))))
 >
 > It should rebuild the whole thing to the orignal

Yes.  That would be useful to (1) make a window state and store it
somewhere (2) arbitrarily alter the frame's configuration and (3) put
the state back into its frame.  It's obviously the same thing
'save-window-excursion' does but there's a twist: When a frame gets
deleted, you cannot restore the window configuration in it.
'window-build-tree' could do it and 'undelete-frame' could use it.  It
has one drawback: We can't collect the dead windows as long as the saved
state needs them.  BTW I have no idea why 'undelete-frame-mode' does not
try to resurrect a deleted frame as we do with windows.

 >> One thing 'window-tree-pixel-sizes' should then possibly do is to
 >> include the identity of internal windows so 'window-rebuild-tree'
 >> could resurrect them as well.
 >
 > idk what you mean by this, is the identity not included in window object
 > itself, as in does refer argument not work with it?

No.  Look at your your example above: All we know about the parent
window is what type of combination it is and what sizes it has.

 > also I found that when using tabs from tab-bar-mode, calling functions
 > like flip windows 2 times in a row will fuck up the tabs.

In what sense does it "fuck up the tabs"?

 > I tested it
 > with orignal transpose-frame.el and it does not have this problem, mayeb
 > it is something to do with the C new patch refer argument code?

'tab-bar-mode' uses window configurations.  Maybe that's the cause.

martin




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

* Re: Add function to rotate/transpose all windows
  2024-10-05 14:43                                                   ` martin rudalics
@ 2024-10-06  2:54                                                     ` pranshu sharma
  2024-10-06 15:02                                                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-06  2:54 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> Say window window-tree-pixel-sizes retruns:
>>
>> ((t (918 . 562) (#<window 7 on *scratch*> (442 . 562)) (#<window 23 on
>> *scratch*> (476 . 562))))
>>
>> Then if you C-x 1, and then:
>> (window-rebuild-tree
>>    '((t (918 . 562) (#<window 7 on *scratch*> (442 . 562)) (#<window 23 on
>>     *scratch*> (476 . 562)))))
>>
>> It should rebuild the whole thing to the orignal
>
> Yes.  That would be useful to (1) make a window state and store it
> somewhere (2) arbitrarily alter the frame's configuration and (3) put
> the state back into its frame.  It's obviously the same thing
> 'save-window-excursion' does but there's a twist: When a frame gets
> deleted, you cannot restore the window configuration in it.
> 'window-build-tree' could do it and 'undelete-frame' could use it.  It
> has one drawback: We can't collect the dead windows as long as the saved
> state needs them.  BTW I have no idea why 'undelete-frame-mode' does not
> try to resurrect a deleted frame as we do with windows.

I was thinking more about making it easier to programticly change the
layout, as window-configurations are in C source, which is far from
userland.  This would make it possible to do stuff like recursivly go
through window tree and do whatver you want, eg make vertical split
windows a bit larger.


>>> One thing 'window-tree-pixel-sizes' should then possibly do is to
>>> include the identity of internal windows so 'window-rebuild-tree'
>>> could resurrect them as well.
>>
>> idk what you mean by this, is the identity not included in window object
>> itself, as in does refer argument not work with it?
>
> No.  Look at your your example above: All we know about the parent
> window is what type of combination it is and what sizes it has.

Do you mean the windows lile #<window nnn>, that are not included in
(window-tree) and return nil in window-live-p?

If so, what do we need to know about them apart from sizes,
transpose-windows and that know nothing about them and still work.

>> also I found that when using tabs from tab-bar-mode, calling functions
>> like flip windows 2 times in a row will fuck up the tabs.
>
> In what sense does it "fuck up the tabs"?
>

It just makes them dissapear in gui, and in terminal it makes them blank.

Try this:
C-x t n
C-x 2
C-x 3
M-x flip-windows
C-x z



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

* Re: Add function to rotate/transpose all windows
  2024-10-06  2:54                                                     ` pranshu sharma
@ 2024-10-06 15:02                                                       ` martin rudalics
  2024-10-06 15:52                                                         ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-06 15:02 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > I was thinking more about making it easier to programticly change the
 > layout, as window-configurations are in C source, which is far from
 > userland.  This would make it possible to do stuff like recursivly go
 > through window tree and do whatver you want, eg make vertical split
 > windows a bit larger.

Hmm..  You can't do that by simply resizing windows?

 >> No.  Look at your your example above: All we know about the parent
 >> window is what type of combination it is and what sizes it has.
 >
 > Do you mean the windows lile #<window nnn>, that are not included in
 > (window-tree) and return nil in window-live-p?

Yes.

 > If so, what do we need to know about them apart from sizes,
 > transpose-windows and that know nothing about them and still work.

Because internal windows usually don't have properties.  The only
exception are window parameters.  With emacs -Q do C-x 2, then do

(set-window-parameter (window-parent) 'foo 3)

M-x transpose-windows

(window-parameter (window-parent) 'foo)

Gets me nil here so the parameter is lost.

 > It just makes them dissapear in gui, and in terminal it makes them blank.
 >
 > Try this:
 > C-x t n
 > C-x 2
 > C-x 3
 > M-x flip-windows
 > C-x z

Works here (with 'flip-windows-horizontally') as expected.  At the end
there are two tabs, one for a one window configuration and one for a
three windows one.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-06 15:02                                                       ` martin rudalics
@ 2024-10-06 15:52                                                         ` pranshu sharma
  2024-10-07  8:33                                                           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-06 15:52 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

>> I was thinking more about making it easier to programticly change the
>> layout, as window-configurations are in C source, which is far from
>> userland.  This would make it possible to do stuff like recursivly go
>> through window tree and do whatver you want, eg make vertical split
>> windows a bit larger.
>
> Hmm..  You can't do that by simply resizing windows?
>
>>> No.  Look at your your example above: All we know about the parent
>>> window is what type of combination it is and what sizes it has.
>>
>> Do you mean the windows lile #<window nnn>, that are not included in
>> (window-tree) and return nil in window-live-p?
>
> Yes.
>
>> If so, what do we need to know about them apart from sizes,
>> transpose-windows and that know nothing about them and still work.
>
> Because internal windows usually don't have properties.  The only
> exception are window parameters.  With emacs -Q do C-x 2, then do
>
> (set-window-parameter (window-parent) 'foo 3)
>
> M-x transpose-windows
>
> (window-parameter (window-parent) 'foo)
>
> Gets me nil here so the parameter is lost.

I see, seems like this is low level, complex and will require lot of
work to do right.  I'm out.  Let just focus on getting the orignal
functions added.

>> It just makes them dissapear in gui, and in terminal it makes them blank.
>>
>> Try this:
>> C-x t n
>> C-x 2
>> C-x 3
>> M-x flip-windows
>> C-x z
>
> Works here (with 'flip-windows-horizontally') as expected.  At the end
> there are two tabs, one for a one window configuration and one for a
> three windows one.

I'm on master branch if that changes anything.  Also you have to call
flip-windows-horizontally at least twice for it to work.

I attached demo of this.


[-- Attachment #2: out.mp4 --]
[-- Type: video/mp4, Size: 738207 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-09-28 15:28                     ` Eli Zaretskii
@ 2024-10-07  8:33                       ` martin rudalics
  0 siblings, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-07  8:33 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: pranshusharma366, emacs-devel

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

I now rewrote 'split-window' and 'split-window-internal' and attach the
new versions.  I don't mention "dead windows" in docs and error messages
any more.  I can't use "leaf windows" because that is nowhere specified
(with one exception in E.9.2 the Elisp manual talks about "leaf nodes"
only).  Internal windows OTOH are mentioned because they are defined and
frequently used in the manual.  Please have a look.

ChangeLog is below, a NEWS entry will be added later.

martin

Let 'split-window' optionally reuse dead window instead of making new one

* lisp/window.el (split-window): New argument REFER to reuse dead
window instead of making a new one.
* src/window.c (Fwindow_old_buffer): Make it work for any window
including deleted ones.  In doc-string say that for a deleted
window it returns the buffer the window had at the time it was
deleted.
(make_parent_window): Reset old_buffer slot installed by memcpy.
(Fsplit_window_internal): Handle new argument REFER inherited
from 'split-window'.
(Fdelete_window_internal, delete_all_child_windows): Set
old_buffer slot of the window to delete to the window's buffer.
* doc/lispref/windows.texi (Splitting Windows): Describe new
argument REFER.
(Window Hooks): Mention that for a dead window 'window-old-buffer'
returns the buffer the window had when it was deleted.

[-- Attachment #2: split-window.diff --]
[-- Type: text/x-patch, Size: 17820 bytes --]

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 541c91ddae2..149cc47de62 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -1354,7 +1354,7 @@ Splitting Windows
 Examples of such windows are side windows (@pxref{Side Windows}) and
 atomic windows (@pxref{Atomic Windows}).
 
-@defun split-window &optional window size side pixelwise
+@defun split-window &optional window size side pixelwise refer
 This function creates a new live window next to the window
 @var{window}.  If @var{window} is omitted or @code{nil}, it defaults
 to the selected window.  That window is split, and reduced in
@@ -1402,10 +1402,24 @@ Splitting Windows
 to interpret @var{size} in units of pixels, instead of lines and
 columns.
 
-If @var{window} is a live window, the new window inherits various
-properties from it, including margins and scroll bars.  If
-@var{window} is an internal window, the new window inherits the
-properties of the window selected within @var{window}'s frame.
+If the optional fifth argument @var{refer} is non-@code{nil}, it has to
+denote either a deleted, former live window on the same frame as
+@var{window} or an arbitrary live window.  If @var{refer} is a deleted
+window, this function does not make a new window but rather resurrects
+@var{refer} and inserts it into the window tree at the position and with
+the sizes the new window would have been given.  Buffer, start and point
+positions of @var{refer} are restored to the values they had immediately
+before @var{refer} was deleted the last time.  Decorations and
+parameters remain unaltered from their values before @var{refer} was
+deleted.  An error is thrown if @var{refer}'s buffer has been deleted
+after @var{refer} itself was deleted.
+
+If @var{refer} is a live window, the new window will inherit properties
+like buffer, start and point and some decorations from @var{refer}.  If
+@var{refer} is @code{nil} or omitted, then if @var{window} is live, any
+such properties are inherited from @var{window}.  If, however,
+@var{window} is an internal window, the new window will inherit these
+properties from the window selected on @var{window}'s frame.
 
 The behavior of this function may be altered by the window parameters
 of @var{window}, so long as the variable
@@ -7103,13 +7117,16 @@ Window Hooks
 window.
 
 @defun window-old-buffer &optional window
-This function returns the buffer shown in @var{window} at the last
-time window change functions were run for @var{window}'s frame.  If it
-returns @code{nil}, @var{window} has been created after that.  If it
-returns @code{t}, @var{window} was not shown at that time but has been
-restored from a previously saved window configuration afterwards.
-Otherwise, the return value is the buffer shown by @var{window} at
-that time.
+This function returns the buffer shown in @var{window} at the last time
+window change functions were run for @var{window}'s frame.  If it
+returns @code{nil}, @var{window} is either an internal window or has
+been created after that.  If it returns @code{t}, @var{window} was not
+shown at that time but has been restored from a previously saved window
+configuration afterwards.  Otherwise, the return value is the buffer
+shown by @var{window} at that time.  As a special case, if @var{window}
+has been deleted, this function returns the last buffer @var{window} had
+shown at that time.  @var{window} can be any window and defaults to the
+selected one.
 @end defun
 
 @defun window-old-pixel-width &optional window
diff --git a/lisp/window.el b/lisp/window.el
index 07ea9584908..52191cff1dd 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -5509,7 +5509,7 @@ window--combination-resizable
       (setq sibling (window-next-sibling sibling)))
     (/ size (1+ number))))
 
-(defun split-window (&optional window size side pixelwise)
+(defun split-window (&optional window size side pixelwise refer)
   "Make a new window adjacent to WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 Return the new window which is always a live window.
@@ -5552,11 +5552,26 @@ split-window
 root of that atomic window.  The new window does not become a
 member of that atomic window.
 
-If WINDOW is live, properties of the new window like margins and
-scrollbars are inherited from WINDOW.  If WINDOW is an internal
-window, these properties as well as the buffer displayed in the
-new window are inherited from the window selected on WINDOW's
-frame.  The selected window is not changed by this function."
+If the optional fifth argument REFER is non-nil, it has to denote either
+a deleted, former live window on the same frame as WINDOW or an
+arbitrary live window.  If REFER is a deleted window, do not make a new
+window but rather make REFER live again and insert it into the window
+tree at the position and with the sizes the new window would have been
+given.  Buffer, start and point positions of REFER are set to the values
+they had immediately before REFER was deleted the last time.
+Decorations and parameters remain unaltered from their values before
+REFER was deleted.  Throw an error if REFER's buffer has been deleted
+after REFER itself was deleted.
+
+If REFER is a live window, the new window will inherit properties like
+buffer, start and point and some decorations from REFER.  If REFER is
+nil or omitted, then if WINDOW is live, any such properties are
+inherited from WINDOW.  If, however, WINDOW is an internal window, the
+new window will inherit these properties from the window selected on
+WINDOW's frame.
+
+The selected window and the selected window on WINDOW's frame are
+not changed by this function."
   (setq window (window-normalize-window window))
   (let* ((side (cond
 		((not side) 'below)
@@ -5596,7 +5611,7 @@ split-window
        ((and (window-parameter window 'window-atom)
 	     (setq atom-root (window-atom-root window))
 	     (not (eq atom-root window)))
-	(throw 'done (split-window atom-root size side pixelwise)))
+	(throw 'done (split-window atom-root size side pixelwise refer)))
        ;; If WINDOW is a side window or its first or last child is a
        ;; side window, throw an error unless `window-combination-resize'
        ;; equals 'side.
@@ -5635,8 +5650,8 @@ split-window
 		   (window-combined-p window horizontal)))
 	     ;; 'old-pixel-size' is the current pixel size of WINDOW.
 	     (old-pixel-size (window-size window horizontal t))
-	     ;; 'new-size' is the specified or calculated size of the
-	     ;; new window.
+	     ;; 'new-pixel-size' is the specified or calculated size
+	     ;; of the new window.
 	     new-pixel-size new-parent new-normal)
 	(cond
 	 ((not pixel-size)
@@ -5757,8 +5772,9 @@ split-window
 	   window (- (if new-parent 1.0 (window-normal-size window horizontal))
 		     new-normal)))
 
-	(let* ((new (split-window-internal window new-pixel-size side new-normal)))
-	  (window--pixel-to-total frame horizontal)
+	(let ((new (split-window-internal
+		    window new-pixel-size side new-normal refer)))
+          (window--pixel-to-total frame horizontal)
 	  ;; Assign window-side parameters, if any.
 	  (cond
 	   ((eq window-combination-resize 'side)
diff --git a/src/window.c b/src/window.c
index 34968ac824f..94ea3491a11 100644
--- a/src/window.c
+++ b/src/window.c
@@ -652,15 +652,16 @@ DEFUN ("window-buffer", Fwindow_buffer, Swindow_buffer, 0, 1, 0,
 
 DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
        doc: /* Return the old buffer displayed by WINDOW.
-WINDOW must be a live window and defaults to the selected one.
+WINDOW can be any window and defaults to the selected one.
 
 The return value is the buffer shown in WINDOW at the last time window
-change functions were run.  It is nil if WINDOW was created after
-that.  It is t if WINDOW has been restored from a window configuration
-after that.  */)
+change functions were run or WINDOW is a former live window that was
+deleted.  It is nil if WINDOW was created after that.  It is t if WINDOW
+has been restored from a window configuration after that.  It is always
+nil if WINDOW is an internal window.  */)
   (Lisp_Object window)
 {
-  struct window *w = decode_live_window (window);
+  struct window *w = decode_any_window (window);
 
   return (NILP (w->old_buffer)
 	  /* A new window.  */
@@ -668,8 +669,8 @@ DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
 	  : (w->change_stamp != WINDOW_XFRAME (w)->change_stamp)
 	  /* A window restored from a configuration.  */
 	  ? Qt
-	  /* A window that was live the last time seen by window
-	     change functions.  */
+	  /* A window that was live the last time seen by window change
+	     functions or was deleted.  */
 	  : w->old_buffer);
 }
 
@@ -4521,6 +4522,7 @@ make_parent_window (Lisp_Object window, bool horflag)
   wset_pointm (p, Qnil);
   wset_old_pointm (p, Qnil);
   wset_buffer (p, Qnil);
+  wset_old_buffer (p, Qnil);
   wset_combination (p, horflag, window);
   wset_combination_limit (p, Qnil);
   /* Reset any previous and next buffers of p which have been installed
@@ -5073,7 +5075,7 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
 }
 
 
-DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 4, 0,
+DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 5, 0,
        doc: /* Split window OLD.
 Second argument PIXEL-SIZE specifies the number of pixels of the
 new window.  It must be a positive integer.
@@ -5088,32 +5090,33 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 the right side of WINDOW.  SIDE `left' means the new window shall be
 located on the left of WINDOW.  In both cases PIXEL-SIZE specifies the
 width of the new window including space reserved for fringes and the
-scrollbar or a divider column.
+scroll bar or a divider column.
 
 Fourth argument NORMAL-SIZE specifies the normal size of the new window
-according to the SIDE argument.
+according to the SIDE argument.  Optional fifth argument REFER is as for
+'split-window'.
 
 The new pixel and normal sizes of all involved windows must have been
 set correctly.  See the code of `split-window' for how this is done.  */)
-  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size)
-{
-  /* OLD (*o) is the window we have to split.  (*p) is either OLD's
-     parent window or an internal window we have to install as OLD's new
-     parent.  REFERENCE (*r) must denote a live window, or is set to OLD
-     provided OLD is a leaf window, or to the frame's selected window.
-     NEW (*n) is the new window created with some parameters taken from
-     REFERENCE (*r).  */
-  Lisp_Object new, frame, reference;
-  struct window *o, *p, *n, *r, *c;
-  struct frame *f;
+  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side,
+   Lisp_Object normal_size, Lisp_Object refer)
+{
+  /* OLD (*o) is the window to split.  REFER (*r) is a reference window,
+     either an arbitrary live window or a former live, now deleted
+     window on the same frame as OLD.  NEW (*n) is the new window
+     created anew or resurrected from REFER (*r), if specified.  *p
+     refers either to OLD's parent window that will become NEW's parent
+     window too or to a new internal window that becomes OLD's and NEW's
+     new parent.  */
+  struct window *o = decode_valid_window (old);
+  Lisp_Object frame = WINDOW_FRAME (o);
+  struct frame *f = XFRAME (frame);
+  struct window *p, *n, *r, *c;
   bool horflag
     /* HORFLAG is true when we split side-by-side, false otherwise.  */
     = EQ (side, Qt) || EQ (side, Qleft) || EQ (side, Qright);
-
-  CHECK_WINDOW (old);
-  o = XWINDOW (old);
-  frame = WINDOW_FRAME (o);
-  f = XFRAME (frame);
+  Lisp_Object new;
+  bool dead = false;
 
   CHECK_FIXNUM (pixel_size);
   EMACS_INT total_size
@@ -5131,14 +5134,38 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	   ? WINDOW_VERTICAL_COMBINATION_P (XWINDOW (o->parent))
 	   : WINDOW_HORIZONTAL_COMBINATION_P (XWINDOW (o->parent))));
 
-  /* We need a live reference window to initialize some parameters.  */
-  if (WINDOW_LIVE_P (old))
-    /* OLD is live, use it as reference window.  */
-    reference = old;
+  /* Set up reference window.  */
+  if (NILP (refer))
+    {
+      if (WINDOW_LIVE_P (old))
+	/* OLD is live, use it as reference window.  */
+	refer = old;
+      else
+	/* Use the frame's selected window as reference window.  */
+	refer = FRAME_SELECTED_WINDOW (f);
+
+      r = XWINDOW (refer);
+    }
   else
-    /* Use the frame's selected window as reference window.  */
-    reference = FRAME_SELECTED_WINDOW (f);
-  r = XWINDOW (reference);
+    {
+      r = decode_any_window (refer);
+
+      if (NILP (r->contents))
+	/* Presumably a deleted, former live window.  Check whether its
+	   contents can be used.  */
+	{
+	  if (!BUFFERP (r->old_buffer))
+	    error ("Deleted window REFER was not a live window (did not show a buffer)");
+	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	    error ("The buffer formerly shown by deleted window REFER has been deleted");
+	  else if (!EQ (r->frame, frame))
+	    error ("Deleted window REFER was not on the same frame as the window to split");
+
+	  dead = true;
+	}
+      else if (!WINDOW_LIVE_P (refer))
+	error ("REFER is not a live window (does not show a buffer)");
+    }
 
   /* The following bugs are caught by `split-window'.  */
   if (MINI_WINDOW_P (o))
@@ -5195,7 +5222,12 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     p = XWINDOW (o->parent);
 
   fset_redisplay (f);
-  new = make_window ();
+
+  if (dead)
+    new = refer;
+  else
+    new = make_window ();
+
   n = XWINDOW (new);
   wset_frame (n, frame);
   wset_parent (n, o->parent);
@@ -5222,16 +5254,19 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   n->window_end_valid = false;
   n->last_cursor_vpos = 0;
 
-  /* Get special geometry settings from reference window.  */
-  n->left_margin_cols = r->left_margin_cols;
-  n->right_margin_cols = r->right_margin_cols;
-  n->left_fringe_width = r->left_fringe_width;
-  n->right_fringe_width = r->right_fringe_width;
-  n->fringes_outside_margins = r->fringes_outside_margins;
-  n->scroll_bar_width = r->scroll_bar_width;
-  n->scroll_bar_height = r->scroll_bar_height;
-  wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
-  wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+  if (!dead)
+    {
+      /* Get special geometry settings from reference window.  */
+      n->left_margin_cols = r->left_margin_cols;
+      n->right_margin_cols = r->right_margin_cols;
+      n->left_fringe_width = r->left_fringe_width;
+      n->right_fringe_width = r->right_fringe_width;
+      n->fringes_outside_margins = r->fringes_outside_margins;
+      n->scroll_bar_width = r->scroll_bar_width;
+      n->scroll_bar_height = r->scroll_bar_height;
+      wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
+      wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+    }
 
   /* Directly assign orthogonal coordinates and sizes.  */
   if (horflag)
@@ -5260,6 +5295,7 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	sum = sum + XFIXNUM (c->new_total);
       c = NILP (c->next) ? 0 : XWINDOW (c->next);
     }
+
   wset_new_total (n, make_fixnum ((horflag
 				   ? p->total_cols
 				   : p->total_lines)
@@ -5267,10 +5303,30 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   wset_new_normal (n, normal_size);
 
   block_input ();
+
+  if (dead)
+    {
+      /* Get dead window back its old buffer and markers.  */
+      wset_buffer (n, n->old_buffer);
+      set_marker_restricted
+	(n->start, make_fixnum (XMARKER (n->start)->charpos), n->contents);
+      set_marker_restricted
+	(n->pointm, make_fixnum (XMARKER (n->pointm)->charpos), n->contents);
+      set_marker_restricted
+	(n->old_pointm, make_fixnum (XMARKER (n->old_pointm)->charpos),
+	 n->contents);
+
+      Vwindow_list = Qnil;
+      /* Remove window from the table of dead windows.  */
+      Fremhash (make_fixnum (n->sequence_number),
+		window_dead_windows_table);
+    }
+
   window_resize_apply (p, horflag);
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  */
+
   set_window_buffer (new, r->contents, true, true);
+
   FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();
 
@@ -5368,6 +5424,8 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  /* Store WINDOW's buffer in old_buffer.  */
+	  wset_old_buffer (w, w->contents);
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
@@ -7712,6 +7770,8 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      /* Store WINDOW's buffer in old_buffer.  */
+      wset_old_buffer (w, w->contents);
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -9064,12 +9124,9 @@ syms_of_window (void)
     doc: /* Hash table of dead windows.
 Each entry in this table maps a window number to a window object.
 Entries are added by `delete-window-internal' and are removed by the
-garbage collector.
-
-This table is maintained by code in window.c and is made visible in
-Elisp for testing purposes only.  */);
+garbage collector.  */);
   window_dead_windows_table
-    = CALLN (Fmake_hash_table, QCweakness, Qt);
+    = CALLN (Fmake_hash_table, QCweakness, Qvalue);
 
   defsubr (&Sselected_window);
   defsubr (&Sold_selected_window);

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

* Re: Add function to rotate/transpose all windows
  2024-10-06 15:52                                                         ` pranshu sharma
@ 2024-10-07  8:33                                                           ` martin rudalics
  2024-10-07  9:42                                                             ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-07  8:33 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Eli Zaretskii, emacs-devel

 > I see, seems like this is low level, complex and will require lot of
 > work to do right.  I'm out.  Let just focus on getting the orignal
 > functions added.

No sweat.  In another mail I attached a new version of 'split-window'.
Please use that from now on.

 >>> It just makes them dissapear in gui, and in terminal it makes them blank.
 >>>
 >>> Try this:
 >>> C-x t n
 >>> C-x 2
 >>> C-x 3
 >>> M-x flip-windows
 >>> C-x z
 >>
 >> Works here (with 'flip-windows-horizontally') as expected.  At the end
 >> there are two tabs, one for a one window configuration and one for a
 >> three windows one.
 >
 > I'm on master branch if that changes anything.  Also you have to call
 > flip-windows-horizontally at least twice for it to work.
 >
 > I attached demo of this.

In the demo you use C-x 3 C-x 2 C-x 3 so you make one split more.
However, even with that additional split I see no problem here.  Unless
you mean that the final C-x 1 is part of the scenario.  But even with

C-x t n C-x 3 C-x 2 C-x 3 C-x w f C-x z C-x 1

I still see two tabs here.  I'm not familiar with 'tab-bar-mode' so you
will probably have to debug it.  Otherwise, we can ask Juri whether he
sees any problems with it.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-07  8:33                                                           ` martin rudalics
@ 2024-10-07  9:42                                                             ` pranshu sharma
  0 siblings, 0 replies; 111+ messages in thread
From: pranshu sharma @ 2024-10-07  9:42 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel


> No sweat.  In another mail I attached a new version of 'split-window'.
> Please use that from now on.

Thanks, compiling emacs with the patch applied fixed the tab bug I was
having.



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

* Re: Add function to rotate/transpose all windows
  2024-10-03  7:06                                     ` pranshu sharma
  2024-10-03  8:17                                       ` martin rudalics
@ 2024-10-08 18:35                                       ` Juri Linkov
  2024-10-09  6:59                                         ` pranshu sharma
  1 sibling, 1 reply; 111+ messages in thread
From: Juri Linkov @ 2024-10-08 18:35 UTC (permalink / raw)
  To: pranshu sharma; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

> btw, what are the plans for the keybindings? imo they should be added
> top level in C-x w map, as there are lots of spots avaiable, and there
> are only 5 total new functions.

I recommend to put all these related transform commands on the dedicated
prefix map 'C-x w t', e.g. 'C-x w t t' for transpose-windows, etc.
because for example recently we discussed addition of new maps
'C-x w d <arrow>' to delete a window in direction, and
'C-x w s <arrow>' to split a window in direction.
But all these keys are already taken:
'C-x w d' for 'toggle-window-dedicated' and
'C-x w s' for 'window-toggle-side-windows'.



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

* Re: Add function to rotate/transpose all windows
  2024-10-08 18:35                                       ` Juri Linkov
@ 2024-10-09  6:59                                         ` pranshu sharma
  2024-10-09 16:21                                           ` Juri Linkov
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-09  6:59 UTC (permalink / raw)
  To: Juri Linkov; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

Juri Linkov <juri@linkov.net> writes:

> I recommend to put all these related transform commands on the dedicated
> prefix map 'C-x w t', e.g. 'C-x w t t' for transpose-windows, etc.

I see, then imo 'C-x w r' would probably be better, nemonic for
rerarrange.  I don't see any connection between rotating and flipping
windows under transposing catagory.

> because for example recently we discussed addition of new maps
> 'C-x w d <arrow>' to delete a window in direction, and
> 'C-x w s <arrow>' to split a window in direction.
> But all these keys are already taken:
> 'C-x w d' for 'toggle-window-dedicated' and
> 'C-x w s' for 'window-toggle-side-windows'.

Is there really need to be so strict, I mean, having semi-nonsencial but
useful keybindings is better than not having them in the first place.

For example the split window in direction could be 'C-x w n <arrow>',
'n' for new window in direction.  I would rather this than not having
it.  (tbh having four arrows not very useful, as C-x 2 and C-x 3 already
deal with 2 of them).



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

* Re: Add function to rotate/transpose all windows
  2024-10-09  6:59                                         ` pranshu sharma
@ 2024-10-09 16:21                                           ` Juri Linkov
  2024-10-10 11:49                                             ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: Juri Linkov @ 2024-10-09 16:21 UTC (permalink / raw)
  To: pranshu sharma; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

>> I recommend to put all these related transform commands on the dedicated
>> prefix map 'C-x w t', e.g. 'C-x w t t' for transpose-windows, etc.
>
> I see, then imo 'C-x w r' would probably be better, nemonic for
> rerarrange.  I don't see any connection between rotating and flipping
> windows under transposing catagory.

Here is an existing example to get inspiration.
There are two keymaps on images:

image-map:
i r             image-rotate
i h             image-flip-horizontally
i v             image-flip-vertically

image-mode-map:
s 0             image-transform-reset-to-initial
s b             image-transform-fit-both
s f             image-mode-fit-frame
s h             image-transform-fit-to-height
s i             image-transform-fit-to-width
s m             image-transform-set-smoothing
s o             image-transform-reset-to-original
s p             image-transform-set-percent
s r             image-transform-set-rotation
s s             image-transform-set-scale
s w             image-transform-fit-to-window

>> because for example recently we discussed addition of new maps
>> 'C-x w d <arrow>' to delete a window in direction, and
>> 'C-x w s <arrow>' to split a window in direction.
>> But all these keys are already taken:
>> 'C-x w d' for 'toggle-window-dedicated' and
>> 'C-x w s' for 'window-toggle-side-windows'.
>
> Is there really need to be so strict, I mean, having semi-nonsencial but
> useful keybindings is better than not having them in the first place.
>
> For example the split window in direction could be 'C-x w n <arrow>',
> 'n' for new window in direction.  I would rather this than not having
> it.  (tbh having four arrows not very useful, as C-x 2 and C-x 3 already
> deal with 2 of them).

'n' for new is a nice mnemonics indeed.

Or maybe better to move the toggle commands to the prefix 'C-x w t':

'C-x w t d' for 'toggle-window-dedicated' and
'C-x w t s' for 'window-toggle-side-windows'.



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

* Re: Add function to rotate/transpose all windows
  2024-10-09 16:21                                           ` Juri Linkov
@ 2024-10-10 11:49                                             ` pranshu sharma
  2024-10-10 16:57                                               ` Juri Linkov
  0 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-10 11:49 UTC (permalink / raw)
  To: Juri Linkov; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

Juri Linkov <juri@linkov.net> writes:

>>> I recommend to put all these related transform commands on the dedicated
>>> prefix map 'C-x w t', e.g. 'C-x w t t' for transpose-windows, etc.
>>
>> I see, then imo 'C-x w r' would probably be better, nemonic for
>> rerarrange.  I don't see any connection between rotating and flipping
>> windows under transposing catagory.
>
> Here is an existing example to get inspiration.
> There are two keymaps on images:
>
> image-map:
> i r             image-rotate
> i h             image-flip-horizontally
> i v             image-flip-vertically
>

Whole i map:
i +		image-increase-size
i -		image-decrease-size
i c		image-crop
i h		image-flip-horizontally
i o		image-save
i r		image-rotate
i v		image-flip-vertically
i x		image-cut

seems like the pattern here is changing how image is displayed.

What do you think about rotate/transpose/flip keybindings based of this?

>>> because for example recently we discussed addition of new maps
>>> 'C-x w d <arrow>' to delete a window in direction, and
>>> 'C-x w s <arrow>' to split a window in direction.
>>> But all these keys are already taken:
>>> 'C-x w d' for 'toggle-window-dedicated' and
>>> 'C-x w s' for 'window-toggle-side-windows'.
>>
>> Is there really need to be so strict, I mean, having semi-nonsencial but
>> useful keybindings is better than not having them in the first place.
>>
>> For example the split window in direction could be 'C-x w n <arrow>',
>> 'n' for new window in direction.  I would rather this than not having
>> it.  (tbh having four arrows not very useful, as C-x 2 and C-x 3 already
>> deal with 2 of them).
>
> 'n' for new is a nice mnemonics indeed.

Now I think about it, n is better than s, as when you are splitting
something, with an argument, it implies splitting direction (eg angle)
more than new window.  'n' would somewhat imply that a new window is
beign spawned.

> Or maybe better to move the toggle commands to the prefix 'C-x w t':
>
> 'C-x w t d' for 'toggle-window-dedicated' and
> 'C-x w t s' for 'window-toggle-side-windows'.

maybe, but my rsi says no



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

* Re: Add function to rotate/transpose all windows
  2024-10-10 11:49                                             ` pranshu sharma
@ 2024-10-10 16:57                                               ` Juri Linkov
  2024-10-13  5:43                                                 ` pranshu sharma
  0 siblings, 1 reply; 111+ messages in thread
From: Juri Linkov @ 2024-10-10 16:57 UTC (permalink / raw)
  To: pranshu sharma; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

>>>> I recommend to put all these related transform commands on the dedicated
>>>> prefix map 'C-x w t', e.g. 'C-x w t t' for transpose-windows, etc.
>>>
>>> I see, then imo 'C-x w r' would probably be better, nemonic for
>>> rerarrange.  I don't see any connection between rotating and flipping
>>> windows under transposing catagory.
>>
>> Here is an existing example to get inspiration.
>> There are two keymaps on images:
>>
>> image-map:
>> i r          image-rotate
>> i h          image-flip-horizontally
>> i v          image-flip-vertically
>
> Whole i map:
> i +		image-increase-size
> i -		image-decrease-size
> i c		image-crop
> i h		image-flip-horizontally
> i o		image-save
> i r		image-rotate
> i v		image-flip-vertically
> i x		image-cut
>
> seems like the pattern here is changing how image is displayed.
>
> What do you think about rotate/transpose/flip keybindings based of this?

Then keybindings could be like these:

C-x w r a       rotate-windows-anticlockwise
C-x w r c       rotate-windows-clockwise
C-x w r h       flip-windows-horizontally
C-x w r v       flip-windows-vertically
C-x w r t       transpose-windows

>>>> because for example recently we discussed addition of new maps
>>>> 'C-x w d <arrow>' to delete a window in direction, and
>>>> 'C-x w s <arrow>' to split a window in direction.
>>>> But all these keys are already taken:
>>>> 'C-x w d' for 'toggle-window-dedicated' and
>>>> 'C-x w s' for 'window-toggle-side-windows'.
>>>
>>> Is there really need to be so strict, I mean, having semi-nonsencial but
>>> useful keybindings is better than not having them in the first place.
>>>
>>> For example the split window in direction could be 'C-x w n <arrow>',
>>> 'n' for new window in direction.  I would rather this than not having
>>> it.  (tbh having four arrows not very useful, as C-x 2 and C-x 3 already
>>> deal with 2 of them).
>>
>> 'n' for new is a nice mnemonics indeed.
>
> Now I think about it, n is better than s, as when you are splitting
> something, with an argument, it implies splitting direction (eg angle)
> more than new window.  'n' would somewhat imply that a new window is
> beign spawned.

Agreed.

Also there is 'C-x w d' bound to toggle-window-dedicated,
so the key [D] can't be used to [D]elete a window
neither to [D]isplay a window in the specified direction.

>> Or maybe better to move the toggle commands to the prefix 'C-x w t':
>>
>> 'C-x w t d' for 'toggle-window-dedicated' and
>> 'C-x w t s' for 'window-toggle-side-windows'.
>
> maybe, but my rsi says no

Depends on how often these key sequences are used.



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

* Re: Add function to rotate/transpose all windows
  2024-10-10 16:57                                               ` Juri Linkov
@ 2024-10-13  5:43                                                 ` pranshu sharma
  2024-10-13  8:17                                                   ` martin rudalics
  2024-10-14 17:32                                                   ` Juri Linkov
  0 siblings, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-10-13  5:43 UTC (permalink / raw)
  To: Juri Linkov; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

Juri Linkov <juri@linkov.net> writes:

> Then keybindings could be like these:
>
> C-x w r a       rotate-windows-anticlockwise
> C-x w r c       rotate-windows-clockwise
> C-x w r h       flip-windows-horizontally
> C-x w r v       flip-windows-vertically
> C-x w r t       transpose-windows

Yeah this seems good.

Btw Martin, did you have look at the code for these functions, and think
it is ready? I am still waiting on assign@gnu.org(I sent them signed
form) but then we can add it.

>> Now I think about it, n is better than s, as when you are splitting
>> something, with an argument, it implies splitting direction (eg angle)
>> more than new window.  'n' would somewhat imply that a new window is
>> beign spawned.
>
> Agreed.
>
> Also there is 'C-x w d' bound to toggle-window-dedicated,
> so the key [D] can't be used to [D]elete a window
> neither to [D]isplay a window in the specified direction.

toggle window dedicated seems like an extremely useless command tbh,
have you should about binding it to C-x w D or C-x w C-d.  I prefer the
former, but capital keys are not really used in C-x maps, but lots of
major modes(gnus,org) use them.

Also for the delete-window, you could maybe bind it to C-x w k, for kill
window, or C-x w <DEL>, not perfect but better than nothing.  Also is
windmove not responiseble for these bindings?

>>> Or maybe better to move the toggle commands to the prefix 'C-x w t':
>>>
>>> 'C-x w t d' for 'toggle-window-dedicated' and
>>> 'C-x w t s' for 'window-toggle-side-windows'.
>>
>> maybe, but my rsi says no
>
> Depends on how often these key sequences are used.

If we are also coutning that in, do you think C-x w ^ map should be
moved to C-x w t (for tear-off) instead? I mean those keys are
frequently used if you have multitab/frame workflow, and pressing 'C-x w
^' a lot is perfect RSI fuel.

Also C-x w o for open in new frame/tab could be used.



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

* Re: Add function to rotate/transpose all windows
  2024-10-13  5:43                                                 ` pranshu sharma
@ 2024-10-13  8:17                                                   ` martin rudalics
  2024-10-14 17:36                                                     ` Juri Linkov
                                                                       ` (2 more replies)
  2024-10-14 17:32                                                   ` Juri Linkov
  1 sibling, 3 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-13  8:17 UTC (permalink / raw)
  To: pranshu sharma, Juri Linkov; +Cc: Eli Zaretskii, emacs-devel

 >> Then keybindings could be like these:
 >>
 >> C-x w r a       rotate-windows-anticlockwise
 >> C-x w r c       rotate-windows-clockwise
 >> C-x w r h       flip-windows-horizontally
 >> C-x w r v       flip-windows-vertically
 >> C-x w r t       transpose-windows
 >
 > Yeah this seems good.

I'm not sure whether "horizontally" and "vertically" are good terms.  In
the past, people complained that for example 'split-window-horizontally'
could mean side-by-side or at some horizontal axis.  Maybe
'flip-windows-left-right' and 'flip-windows-above-below' would be better
and should be reflected in the key mnemonics somehow.

 > Btw Martin, did you have look at the code for these functions, and think
 > it is ready? I am still waiting on assign@gnu.org(I sent them signed
 > form) but then we can add it.

Yes.  Also people might still want to comment on my latest patch to
'split-window'.

 > toggle window dedicated seems like an extremely useless command tbh,
 > have you should about binding it to C-x w D or C-x w C-d.  I prefer the
 > former, but capital keys are not really used in C-x maps, but lots of
 > major modes(gnus,org) use them.

You should hurry here: IIUC 'toggle-window-dedicated' is new in the
release version and if you want to change its default binding, you'd
better do that right away there.

 > Also C-x w o for open in new frame/tab could be used.

"o" could be also used for deleting an "old" window in some direction
but I think that it has been already taken to mean "other" for too long.

But whatever you do my opinion should hardly count - I'm using my own
sets of key bindings ever since.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-13  5:43                                                 ` pranshu sharma
  2024-10-13  8:17                                                   ` martin rudalics
@ 2024-10-14 17:32                                                   ` Juri Linkov
  1 sibling, 0 replies; 111+ messages in thread
From: Juri Linkov @ 2024-10-14 17:32 UTC (permalink / raw)
  To: pranshu sharma; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

>> Also there is 'C-x w d' bound to toggle-window-dedicated,
>> so the key [D] can't be used to [D]elete a window
>> neither to [D]isplay a window in the specified direction.
>
> toggle window dedicated seems like an extremely useless command tbh,
> have you should about binding it to C-x w D or C-x w C-d.  I prefer the
> former, but capital keys are not really used in C-x maps, but lots of
> major modes(gnus,org) use them.

'C-x w D' is fine since "D" is displayed on the mode-line
for strongly dedicated windows.

> Also for the delete-window, you could maybe bind it to C-x w k, for kill
> window, or C-x w <DEL>, not perfect but better than nothing.

'C-x w k' would be more suitable for 'quit-window'
with its KILL argument set to t.

> Also is windmove not responiseble for these bindings?

windmove commands are exactly what should be bound to these keys
because there are no default short keybindings for
windmove-display-default-keybindings, windmove-delete-default-keybindings,
etc.

>>>> Or maybe better to move the toggle commands to the prefix 'C-x w t':
>>>>
>>>> 'C-x w t d' for 'toggle-window-dedicated' and
>>>> 'C-x w t s' for 'window-toggle-side-windows'.
>>>
>>> maybe, but my rsi says no
>>
>> Depends on how often these key sequences are used.
>
> If we are also coutning that in, do you think C-x w ^ map should be
> moved to C-x w t (for tear-off) instead? I mean those keys are
> frequently used if you have multitab/frame workflow, and pressing
> 'C-x w ^' a lot is perfect RSI fuel.

'^' is not easy to type indeed, so any suggestions are welcome.
Then with the prefix 'C-x w t' we could replace

  C-x w ^ f     tear-off-window

with

  C-x w t f     tear-off-window

with mnemonic 'w t f'.



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

* Re: Add function to rotate/transpose all windows
  2024-10-13  8:17                                                   ` martin rudalics
@ 2024-10-14 17:36                                                     ` Juri Linkov
  2024-10-15  8:34                                                     ` pranshu sharma
  2024-10-18 14:52                                                     ` pranshu sharma
  2 siblings, 0 replies; 111+ messages in thread
From: Juri Linkov @ 2024-10-14 17:36 UTC (permalink / raw)
  To: martin rudalics; +Cc: pranshu sharma, Eli Zaretskii, emacs-devel

>> Btw Martin, did you have look at the code for these functions, and think
>> it is ready? I am still waiting on assign@gnu.org(I sent them signed
>> form) but then we can add it.
>
> Yes.  Also people might still want to comment on my latest patch to
> 'split-window'.

I already tested your latest patch together with Pranshu's implementation
of new commands, and everything works nicely.

>> toggle window dedicated seems like an extremely useless command tbh,
>> have you should about binding it to C-x w D or C-x w C-d.  I prefer the
>> former, but capital keys are not really used in C-x maps, but lots of
>> major modes(gnus,org) use them.
>
> You should hurry here: IIUC 'toggle-window-dedicated' is new in the
> release version and if you want to change its default binding, you'd
> better do that right away there.

Agreed.

>> Also C-x w o for open in new frame/tab could be used.
>
> "o" could be also used for deleting an "old" window in some direction
> but I think that it has been already taken to mean "other" for too long.

Or 'C-x w C-o' like 'C-o' in 'open-line'.  Or 'C-x w c' ([c]reate).



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

* Re: Add function to rotate/transpose all windows
  2024-10-13  8:17                                                   ` martin rudalics
  2024-10-14 17:36                                                     ` Juri Linkov
@ 2024-10-15  8:34                                                     ` pranshu sharma
  2024-10-15 16:16                                                       ` Juri Linkov
  2024-10-18 14:52                                                     ` pranshu sharma
  2 siblings, 1 reply; 111+ messages in thread
From: pranshu sharma @ 2024-10-15  8:34 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>> Then keybindings could be like these:
>>>
>>> C-x w r a       rotate-windows-anticlockwise
>>> C-x w r c       rotate-windows-clockwise
>>> C-x w r h       flip-windows-horizontally
>>> C-x w r v       flip-windows-vertically
>>> C-x w r t       transpose-windows
>>
>> Yeah this seems good.
>
> I'm not sure whether "horizontally" and "vertically" are good terms.  In
> the past, people complained that for example 'split-window-horizontally'
> could mean side-by-side or at some horizontal axis.  Maybe
> 'flip-windows-left-right' and 'flip-windows-above-below' would be better
> and should be reflected in the key mnemonics somehow.

I think in terms of flipping the window, horizontal/vertical don't
really leave that much room for interpretatin as
split-window-horizontally.  flip-windows-left-right above-below seem
kind of clunky tbh, imo we should just go with horizontal/vertical, I
mean, worst case users will learn it by using it, or reading the
doctring. 99% of users should already know from
split-window-horizotnally and it's counter part what horizontal and
vertical means in the context of window.  But I'm also fine with
left-right, only problem is that it will make searching through C-h f
and 'C-h m' a little bit weird



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

* Re: Add function to rotate/transpose all windows
  2024-10-15  8:34                                                     ` pranshu sharma
@ 2024-10-15 16:16                                                       ` Juri Linkov
  0 siblings, 0 replies; 111+ messages in thread
From: Juri Linkov @ 2024-10-15 16:16 UTC (permalink / raw)
  To: pranshu sharma; +Cc: martin rudalics, Eli Zaretskii, emacs-devel

>>>> Then keybindings could be like these:
>>>>
>>>> C-x w r a       rotate-windows-anticlockwise
>>>> C-x w r c       rotate-windows-clockwise
>>>> C-x w r h       flip-windows-horizontally
>>>> C-x w r v       flip-windows-vertically
>>>> C-x w r t       transpose-windows
>>>
>>> Yeah this seems good.
>>
>> I'm not sure whether "horizontally" and "vertically" are good terms.  In
>> the past, people complained that for example 'split-window-horizontally'
>> could mean side-by-side or at some horizontal axis.  Maybe
>> 'flip-windows-left-right' and 'flip-windows-above-below' would be better
>> and should be reflected in the key mnemonics somehow.
>
> I think in terms of flipping the window, horizontal/vertical don't
> really leave that much room for interpretatin as
> split-window-horizontally.  flip-windows-left-right above-below seem
> kind of clunky tbh, imo we should just go with horizontal/vertical, I
> mean, worst case users will learn it by using it, or reading the
> doctring. 99% of users should already know from
> split-window-horizotnally and it's counter part what horizontal and
> vertical means in the context of window.  But I'm also fine with
> left-right, only problem is that it will make searching through C-h f
> and 'C-h m' a little bit weird

Agreed, let's stick to the established terminology.



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

* Re: Add function to rotate/transpose all windows
  2024-10-13  8:17                                                   ` martin rudalics
  2024-10-14 17:36                                                     ` Juri Linkov
  2024-10-15  8:34                                                     ` pranshu sharma
@ 2024-10-18 14:52                                                     ` pranshu sharma
  2024-10-18 17:48                                                       ` martin rudalics
  2024-10-20  8:19                                                       ` martin rudalics
  2 siblings, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-10-18 14:52 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

>> Btw Martin, did you have look at the code for these functions, and think
>> it is ready? I am still waiting on assign@gnu.org(I sent them signed
>> form) but then we can add it.
>
> Yes.  Also people might still want to comment on my latest patch to
> 'split-window'.
>

Ok, so today I made major changes to the interactive side of the
functions(new file attached), mostly because the function would error if
you call iti with prefix arg, and the press C-x z, as it would use the
last used argument of frame-or-window which is now dead.

The problem was that C-x z sends with same arguments, so using code in
the (interactive (HERE)) would be repeated, which means same argument
would be used in `repeat'.

I added subtree speicfic options to the docstring(before they were not
there), and made changes to window--transpose.  However it is weird if
you C-u M-x transpose-windows C-x z multiple times, you get weired
result since the parent window changes, nothing I can do about that,
just how windows work.

Also I'm finally done with the bureaucracy, I got the copyright
assignment done.

I'm ready to send final patch now, do you want it to be added to
window.el or new file?


[-- Attachment #2: new file --]
[-- Type: application/emacs-lisp, Size: 8509 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-10-18 14:52                                                     ` pranshu sharma
@ 2024-10-18 17:48                                                       ` martin rudalics
  2024-10-18 18:37                                                         ` Eli Zaretskii
  2024-10-19  1:45                                                         ` pranshu sharma
  2024-10-20  8:19                                                       ` martin rudalics
  1 sibling, 2 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-18 17:48 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > Ok, so today I made major changes to the interactive side of the
 > functions(new file attached), mostly because the function would error if
 > you call iti with prefix arg, and the press C-x z, as it would use the
 > last used argument of frame-or-window which is now dead.

If we did reuse parent windows too as I once suggested, it would be
still valid and in place.

Note that 'flip-windows-horizontally' binds "windows" instead of
"window".  And please don't cascade ifs - the appropriate Lisp idiom
here is 'cond' as in

   (let ((window (cond
		 (current-prefix-arg
		  (window-parent))
		 ((windowp frame-or-window)
		  frame-or-window)
		 (t
		  (window-main-window frame-or-window)))))

BTW does it work if FRAME-OR-WINDOW is an internal window?  In the doc
string you say that it must be a "live frame or window" but 'windowp'
returns t for internal windows too.

 > The problem was that C-x z sends with same arguments, so using code in
 > the (interactive (HERE)) would be repeated, which means same argument
 > would be used in `repeat'.

I never use C-x z so I have no opinion here.

 > I added subtree speicfic options to the docstring(before they were not
 > there), and made changes to window--transpose.  However it is weird if
 > you C-u M-x transpose-windows C-x z multiple times, you get weired
 > result since the parent window changes, nothing I can do about that,
 > just how windows work.

If you optionally can supply REFER as a cons where the car is the
previous (currently deleted) parent and the cdr the previous (currently
deleted) leaf window (you have to keep pointers to all of them just in
case the collector runs in between), we can easily fix that.  Just that
'window-tree-pixel-sizes' would have to report the old parent window too
(in front of the flag telling whether it constitutes a horizontal or
vertical split, probably).  It's easy to do, believe me.

 > Also I'm finally done with the bureaucracy, I got the copyright
 > assignment done.

Fine.

 > I'm ready to send final patch now, do you want it to be added to
 > window.el or new file?

What about adding it to windmove.el?  Eli or Juri will decide.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-18 17:48                                                       ` martin rudalics
@ 2024-10-18 18:37                                                         ` Eli Zaretskii
  2024-10-19  1:45                                                         ` pranshu sharma
  1 sibling, 0 replies; 111+ messages in thread
From: Eli Zaretskii @ 2024-10-18 18:37 UTC (permalink / raw)
  To: martin rudalics; +Cc: pranshusharma366, juri, emacs-devel

> Date: Fri, 18 Oct 2024 19:48:41 +0200
> Cc: Juri Linkov <juri@linkov.net>, Eli Zaretskii <eliz@gnu.org>,
>  emacs-devel@gnu.org
> From: martin rudalics <rudalics@gmx.at>
> 
>  > I'm ready to send final patch now, do you want it to be added to
>  > window.el or new file?
> 
> What about adding it to windmove.el?  Eli or Juri will decide.

I don't think we need it preloaded?  If so, windmove.el is indeed a
better place, IMO.  But I'd like to hear from Juri as well.



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

* Re: Add function to rotate/transpose all windows
  2024-10-18 17:48                                                       ` martin rudalics
  2024-10-18 18:37                                                         ` Eli Zaretskii
@ 2024-10-19  1:45                                                         ` pranshu sharma
  2024-10-19  6:45                                                           ` Eli Zaretskii
  2024-10-19  8:33                                                           ` martin rudalics
  1 sibling, 2 replies; 111+ messages in thread
From: pranshu sharma @ 2024-10-19  1:45 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> Note that 'flip-windows-horizontally' binds "windows" instead of
> "window".  And please don't cascade ifs - the appropriate Lisp idiom
> here is 'cond' as in
>

Ah, I see

>
> BTW does it work if FRAME-OR-WINDOW is an internal window?  In the doc
> string you say that it must be a "live frame or window" but 'windowp'
> returns t for internal windows too.
>

It works if frame-or-window is an internal window, I changed 'live' with
'valid'

>> I added subtree speicfic options to the docstring(before they were not
>> there), and made changes to window--transpose.  However it is weird if
>> you C-u M-x transpose-windows C-x z multiple times, you get weired
>> result since the parent window changes, nothing I can do about that,
>> just how windows work.
>
> If you optionally can supply REFER as a cons where the car is the
> previous (currently deleted) parent and the cdr the previous (currently
> deleted) leaf window (you have to keep pointers to all of them just in
> case the collector runs in between), we can easily fix that.  Just that
> 'window-tree-pixel-sizes' would have to report the old parent window too
> (in front of the flag telling whether it constitutes a horizontal or
> vertical split, probably).  It's easy to do, believe me.
>

I don't understand

>
> What about adding it to windmove.el?  Eli or Juri will decide.
>

I agree with Eli it should autoloaded, but every windmove function seems
to be focused on something to do with directional windows, these
functions would be a bit out of place.

How does a new autoloaded window-util.el sound? if you think its a good
idea I can also spend some time making an ace-window like window jump
function to make window-util a bit more complete. Which instead of
acewin puts a letter or number in the window's modeline instead, and if
you enter that letter/number you go to that window (the idea is to save
letter in a window-parameter, and use that in modeline)



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

* Re: Add function to rotate/transpose all windows
  2024-10-19  1:45                                                         ` pranshu sharma
@ 2024-10-19  6:45                                                           ` Eli Zaretskii
  2024-10-19 18:19                                                             ` Juri Linkov
  2024-10-19  8:33                                                           ` martin rudalics
  1 sibling, 1 reply; 111+ messages in thread
From: Eli Zaretskii @ 2024-10-19  6:45 UTC (permalink / raw)
  To: pranshu sharma; +Cc: rudalics, juri, emacs-devel

> From: pranshu sharma <pranshusharma366@gmail.com>
> Cc: Juri Linkov <juri@linkov.net>,  Eli Zaretskii <eliz@gnu.org>,
>   emacs-devel@gnu.org
> Date: Sat, 19 Oct 2024 11:45:02 +1000
> 
> > What about adding it to windmove.el?  Eli or Juri will decide.
> >
> 
> I agree with Eli it should autoloaded, but every windmove function seems
> to be focused on something to do with directional windows, these
> functions would be a bit out of place.
> 
> How does a new autoloaded window-util.el sound?

We could have a new file window-x.el, perhaps.  "-util" sounds like
it's for utility subroutines, which these functions aren't, AFAIU.



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

* Re: Add function to rotate/transpose all windows
  2024-10-19  1:45                                                         ` pranshu sharma
  2024-10-19  6:45                                                           ` Eli Zaretskii
@ 2024-10-19  8:33                                                           ` martin rudalics
  1 sibling, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-19  8:33 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 >> If you optionally can supply REFER as a cons where the car is the
 >> previous (currently deleted) parent and the cdr the previous (currently
 >> deleted) leaf window (you have to keep pointers to all of them just in
 >> case the collector runs in between), we can easily fix that.  Just that
 >> 'window-tree-pixel-sizes' would have to report the old parent window too
 >> (in front of the flag telling whether it constitutes a horizontal or
 >> vertical split, probably).  It's easy to do, believe me.
 >>
 >
 > I don't understand

Suppose I had defined 'window-tree-pixel-sizes' as

(defun window-tree-pixel-sizes (window &optional next)
   "Return pixel sizes of all windows rooted at WINDOW.
The return value is a list where each internal window is represented by
a list of four elements: The window object, an indicator that is t if
the window is a horizontal combination and nil otherwise, the size of
the window as a cons of its pixel height and pixel width and a recursive
list of the window's child windows using the same list structure.  A
live window is represented by a cons consisting of the window object and
a cons of its pixel height and pixel width."
   (let (list child)
     (while window
       (setq list
	    (cons
	     (if (window-live-p window)
		 (cons window (cons (window-pixel-height window)
				    (window-pixel-width window)))
	       (list window
		     (not (window-top-child window))
		     (cons (window-pixel-height window)
			   (window-pixel-width window))
		     (window-tree-pixel-sizes
		      (or (window-top-child window)
			  (window-left-child window))
		      t)))
	     list))
       (setq window (when next (window-next-sibling window))))
     (nreverse list)))

Then

(pp (window-tree-pixel-sizes (frame-root-window)))

gets me here with a three live windows frame instead of your

((nil (979 . 1680) (#<window 15 on window-transpose.el> (979 . 840))
       (t (979 . 840) (#<window 89 on *Messages*> (490 . 840))
	 (#<window 95 on *scratch*> (489 . 840)))))

something like

((#<window 56> t (979 . 1680)
	   ((#<window 15 on window-transpose.el> 979 . 840)
	    (#<window 60> nil (979 . 840)
		      ((#<window 57 on *Messages*> 490 . 840)
		       (#<window 61 on *scratch*> 489 . 840))))))

The first element for each window would be its object: So #<window 56>
is a horizontal combination with the live #<window 15> as left and the
internal window #<window 60> as its right child.  Now when you want to
recreate #<window #15> you would have to pass 'split-window' a cons cell
referencing #<window 56> and #<window #15> and 'split-window-internal'
would resurrect the first as the parent and the second as its child.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-19  6:45                                                           ` Eli Zaretskii
@ 2024-10-19 18:19                                                             ` Juri Linkov
  0 siblings, 0 replies; 111+ messages in thread
From: Juri Linkov @ 2024-10-19 18:19 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: pranshu sharma, rudalics, emacs-devel

>> > What about adding it to windmove.el?  Eli or Juri will decide.
>>
>> I agree with Eli it should autoloaded, but every windmove function seems
>> to be focused on something to do with directional windows, these
>> functions would be a bit out of place.
>>
>> How does a new autoloaded window-util.el sound?
>
> We could have a new file window-x.el, perhaps.  "-util" sounds like
> it's for utility subroutines, which these functions aren't, AFAIU.

window-x.el looks right following the established file naming scheme.
Then some new functions from the end of window.el could be moved to
window-x.el as well.



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

* Re: Add function to rotate/transpose all windows
  2024-10-18 14:52                                                     ` pranshu sharma
  2024-10-18 17:48                                                       ` martin rudalics
@ 2024-10-20  8:19                                                       ` martin rudalics
  2024-10-20 14:11                                                         ` Pranshu Sharma
  1 sibling, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-20  8:19 UTC (permalink / raw)
  To: pranshu sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

 > Ok, so today I made major changes to the interactive side of the
 > functions(new file attached), mostly because the function would error if
 > you call iti with prefix arg, and the press C-x z, as it would use the
 > last used argument of frame-or-window which is now dead.
 >
 > The problem was that C-x z sends with same arguments, so using code in
 > the (interactive (HERE)) would be repeated, which means same argument
 > would be used in `repeat'.
 >
 > I added subtree speicfic options to the docstring(before they were not
 > there), and made changes to window--transpose.  However it is weird if
 > you C-u M-x transpose-windows C-x z multiple times, you get weired
 > result since the parent window changes, nothing I can do about that,
 > just how windows work.

I now tried to make 'split-window' reuse a dead parent window as well so
you shouldn't have to care about this any more.  Patch attached.

martin

[-- Attachment #2: split-window.diff --]
[-- Type: text/x-patch, Size: 19302 bytes --]

git diff | tee
diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 0b8d7d3b76d..e34fc990734 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -1355,7 +1355,7 @@ Splitting Windows
 Examples of such windows are side windows (@pxref{Side Windows}) and
 atomic windows (@pxref{Atomic Windows}).
 
-@defun split-window &optional window size side pixelwise
+@defun split-window &optional window size side pixelwise refer
 This function creates a new live window next to the window
 @var{window}.  If @var{window} is omitted or @code{nil}, it defaults
 to the selected window.  That window is split, and reduced in
@@ -1403,10 +1403,24 @@ Splitting Windows
 to interpret @var{size} in units of pixels, instead of lines and
 columns.
 
-If @var{window} is a live window, the new window inherits various
-properties from it, including margins and scroll bars.  If
-@var{window} is an internal window, the new window inherits the
-properties of the window selected within @var{window}'s frame.
+If the optional fifth argument @var{refer} is non-@code{nil}, it has to
+denote either a deleted, former live window on the same frame as
+@var{window} or an arbitrary live window.  If @var{refer} is a deleted
+window, this function does not make a new window but rather resurrects
+@var{refer} and inserts it into the window tree at the position and with
+the sizes the new window would have been given.  Buffer, start and point
+positions of @var{refer} are restored to the values they had immediately
+before @var{refer} was deleted the last time.  Decorations and
+parameters remain unaltered from their values before @var{refer} was
+deleted.  An error is thrown if @var{refer}'s buffer has been deleted
+after @var{refer} itself was deleted.
+
+If @var{refer} is a live window, the new window will inherit properties
+like buffer, start and point and some decorations from @var{refer}.  If
+@var{refer} is @code{nil} or omitted, then if @var{window} is live, any
+such properties are inherited from @var{window}.  If, however,
+@var{window} is an internal window, the new window will inherit these
+properties from the window selected on @var{window}'s frame.
 
 The behavior of this function may be altered by the window parameters
 of @var{window}, so long as the variable
@@ -7126,13 +7140,16 @@ Window Hooks
 window.
 
 @defun window-old-buffer &optional window
-This function returns the buffer shown in @var{window} at the last
-time window change functions were run for @var{window}'s frame.  If it
-returns @code{nil}, @var{window} has been created after that.  If it
-returns @code{t}, @var{window} was not shown at that time but has been
-restored from a previously saved window configuration afterwards.
-Otherwise, the return value is the buffer shown by @var{window} at
-that time.
+This function returns the buffer shown in @var{window} at the last time
+window change functions were run for @var{window}'s frame.  If it
+returns @code{nil}, @var{window} is either an internal window or has
+been created after that.  If it returns @code{t}, @var{window} was not
+shown at that time but has been restored from a previously saved window
+configuration afterwards.  Otherwise, the return value is the buffer
+shown by @var{window} at that time.  As a special case, if @var{window}
+has been deleted, this function returns the last buffer @var{window} had
+shown at that time.  @var{window} can be any window and defaults to the
+selected one.
 @end defun
 
 @defun window-old-pixel-width &optional window
diff --git a/lisp/window.el b/lisp/window.el
index b50770cbd7e..2cf2c288452 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -5511,7 +5511,7 @@ window--combination-resizable
       (setq sibling (window-next-sibling sibling)))
     (/ size (1+ number))))
 
-(defun split-window (&optional window size side pixelwise)
+(defun split-window (&optional window size side pixelwise refer)
   "Make a new window adjacent to WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 Return the new window which is always a live window.
@@ -5554,11 +5554,30 @@ split-window
 root of that atomic window.  The new window does not become a
 member of that atomic window.
 
-If WINDOW is live, properties of the new window like margins and
-scrollbars are inherited from WINDOW.  If WINDOW is an internal
-window, these properties as well as the buffer displayed in the
-new window are inherited from the window selected on WINDOW's
-frame.  The selected window is not changed by this function."
+If the optional fifth argument REFER is non-nil, it has to denote either
+a deleted, former live window on the same frame as WINDOW or an
+arbitrary live window.  If REFER is a deleted window, do not make a new
+window but rather make REFER live again and insert it into the window
+tree at the position and with the sizes the new window would have been
+given.  Buffer, start and point positions of REFER are set to the values
+they had immediately before REFER was deleted the last time.
+Decorations and parameters remain unaltered from their values before
+REFER was deleted.  Throw an error if REFER's buffer has been deleted
+after REFER itself was deleted.
+
+If and only if REFER is a deleted window and its previous parent window
+is now a deleted window too, do not make a new parent window but reuse
+the old aprent window, making it a valid window again.
+
+If REFER is a live window, the new window will inherit properties like
+buffer, start and point and some decorations from REFER.  If REFER is
+nil or omitted, then if WINDOW is live, any such properties are
+inherited from WINDOW.  If, however, WINDOW is an internal window, the
+new window will inherit these properties from the window selected on
+WINDOW's frame.
+
+The selected window and the selected window on WINDOW's frame are
+not changed by this function."
   (setq window (window-normalize-window window))
   (let* ((side (cond
 		((not side) 'below)
@@ -5598,14 +5617,14 @@ split-window
        ((and (window-parameter window 'window-atom)
 	     (setq atom-root (window-atom-root window))
 	     (not (eq atom-root window)))
-	(throw 'done (split-window atom-root size side pixelwise)))
+	(throw 'done (split-window atom-root size side pixelwise refer)))
        ;; If WINDOW's frame has a side window and WINDOW specifies the
        ;; frame's root window, split the frame's main window instead
        ;; (Bug#73627).
        ((and (eq window (frame-root-window frame))
 	     (window-with-parameter 'window-side nil frame))
 	(throw 'done (split-window (window-main-window frame)
-				   size side pixelwise)))
+				   size side pixelwise refer)))
        ;; If WINDOW is a side window or its first or last child is a
        ;; side window, throw an error unless `window-combination-resize'
        ;; equals 'side.
@@ -5644,8 +5663,8 @@ split-window
 		   (window-combined-p window horizontal)))
 	     ;; 'old-pixel-size' is the current pixel size of WINDOW.
 	     (old-pixel-size (window-size window horizontal t))
-	     ;; 'new-size' is the specified or calculated size of the
-	     ;; new window.
+	     ;; 'new-pixel-size' is the specified or calculated size
+	     ;; of the new window.
 	     new-pixel-size new-parent new-normal)
 	(cond
 	 ((not pixel-size)
@@ -5766,8 +5785,9 @@ split-window
 	   window (- (if new-parent 1.0 (window-normal-size window horizontal))
 		     new-normal)))
 
-	(let* ((new (split-window-internal window new-pixel-size side new-normal)))
-	  (window--pixel-to-total frame horizontal)
+	(let ((new (split-window-internal
+		    window new-pixel-size side new-normal refer)))
+          (window--pixel-to-total frame horizontal)
 	  ;; Assign window-side parameters, if any.
 	  (cond
 	   ((eq window-combination-resize 'side)
diff --git a/src/window.c b/src/window.c
index 34968ac824f..ca5d8dea8aa 100644
--- a/src/window.c
+++ b/src/window.c
@@ -652,15 +652,16 @@ DEFUN ("window-buffer", Fwindow_buffer, Swindow_buffer, 0, 1, 0,
 
 DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
        doc: /* Return the old buffer displayed by WINDOW.
-WINDOW must be a live window and defaults to the selected one.
+WINDOW can be any window and defaults to the selected one.
 
 The return value is the buffer shown in WINDOW at the last time window
-change functions were run.  It is nil if WINDOW was created after
-that.  It is t if WINDOW has been restored from a window configuration
-after that.  */)
+change functions were run or WINDOW is a former live window that was
+deleted.  It is nil if WINDOW was created after that.  It is t if WINDOW
+has been restored from a window configuration after that.  It is always
+nil if WINDOW is an internal window.  */)
   (Lisp_Object window)
 {
-  struct window *w = decode_live_window (window);
+  struct window *w = decode_any_window (window);
 
   return (NILP (w->old_buffer)
 	  /* A new window.  */
@@ -668,8 +669,8 @@ DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
 	  : (w->change_stamp != WINDOW_XFRAME (w)->change_stamp)
 	  /* A window restored from a configuration.  */
 	  ? Qt
-	  /* A window that was live the last time seen by window
-	     change functions.  */
+	  /* A window that was live the last time seen by window change
+	     functions or was deleted.  */
 	  : w->old_buffer);
 }
 
@@ -4521,6 +4522,7 @@ make_parent_window (Lisp_Object window, bool horflag)
   wset_pointm (p, Qnil);
   wset_old_pointm (p, Qnil);
   wset_buffer (p, Qnil);
+  wset_old_buffer (p, Qnil);
   wset_combination (p, horflag, window);
   wset_combination_limit (p, Qnil);
   /* Reset any previous and next buffers of p which have been installed
@@ -5073,7 +5075,7 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
 }
 
 
-DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 4, 0,
+DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 5, 0,
        doc: /* Split window OLD.
 Second argument PIXEL-SIZE specifies the number of pixels of the
 new window.  It must be a positive integer.
@@ -5088,32 +5090,33 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 the right side of WINDOW.  SIDE `left' means the new window shall be
 located on the left of WINDOW.  In both cases PIXEL-SIZE specifies the
 width of the new window including space reserved for fringes and the
-scrollbar or a divider column.
+scroll bar or a divider column.
 
 Fourth argument NORMAL-SIZE specifies the normal size of the new window
-according to the SIDE argument.
+according to the SIDE argument.  Optional fifth argument REFER is as for
+'split-window'.
 
 The new pixel and normal sizes of all involved windows must have been
 set correctly.  See the code of `split-window' for how this is done.  */)
-  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size)
-{
-  /* OLD (*o) is the window we have to split.  (*p) is either OLD's
-     parent window or an internal window we have to install as OLD's new
-     parent.  REFERENCE (*r) must denote a live window, or is set to OLD
-     provided OLD is a leaf window, or to the frame's selected window.
-     NEW (*n) is the new window created with some parameters taken from
-     REFERENCE (*r).  */
-  Lisp_Object new, frame, reference;
-  struct window *o, *p, *n, *r, *c;
-  struct frame *f;
+  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side,
+   Lisp_Object normal_size, Lisp_Object refer)
+{
+  /* OLD (*o) is the window to split.  REFER (*r) is a reference window,
+     either an arbitrary live window or a former live, now deleted
+     window on the same frame as OLD.  NEW (*n) is the new window
+     created anew or resurrected from REFER (*r), if specified.  *p
+     refers either to OLD's parent window that will become NEW's parent
+     window too or to a new internal window that becomes OLD's and NEW's
+     new parent.  */
+  struct window *o = decode_valid_window (old);
+  Lisp_Object frame = WINDOW_FRAME (o);
+  struct frame *f = XFRAME (frame);
+  struct window *p, *n, *r, *c;
   bool horflag
     /* HORFLAG is true when we split side-by-side, false otherwise.  */
     = EQ (side, Qt) || EQ (side, Qleft) || EQ (side, Qright);
-
-  CHECK_WINDOW (old);
-  o = XWINDOW (old);
-  frame = WINDOW_FRAME (o);
-  f = XFRAME (frame);
+  Lisp_Object new;
+  bool dead = false;
 
   CHECK_FIXNUM (pixel_size);
   EMACS_INT total_size
@@ -5131,14 +5134,38 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	   ? WINDOW_VERTICAL_COMBINATION_P (XWINDOW (o->parent))
 	   : WINDOW_HORIZONTAL_COMBINATION_P (XWINDOW (o->parent))));
 
-  /* We need a live reference window to initialize some parameters.  */
-  if (WINDOW_LIVE_P (old))
-    /* OLD is live, use it as reference window.  */
-    reference = old;
+  /* Set up reference window.  */
+  if (NILP (refer))
+    {
+      if (WINDOW_LIVE_P (old))
+	/* OLD is live, use it as reference window.  */
+	refer = old;
+      else
+	/* Use the frame's selected window as reference window.  */
+	refer = FRAME_SELECTED_WINDOW (f);
+
+      r = XWINDOW (refer);
+    }
   else
-    /* Use the frame's selected window as reference window.  */
-    reference = FRAME_SELECTED_WINDOW (f);
-  r = XWINDOW (reference);
+    {
+      r = decode_any_window (refer);
+
+      if (NILP (r->contents))
+	/* Presumably a deleted, former live window.  Check whether its
+	   contents can be used.  */
+	{
+	  if (!BUFFERP (r->old_buffer))
+	    error ("Deleted window REFER was not a live window (did not show a buffer)");
+	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	    error ("The buffer formerly shown by deleted window REFER has been deleted");
+	  else if (!EQ (r->frame, frame))
+	    error ("Deleted window REFER was not on the same frame as the window to split");
+
+	  dead = true;
+	}
+      else if (!WINDOW_LIVE_P (refer))
+	error ("REFER is not a live window (does not show a buffer)");
+    }
 
   /* The following bugs are caught by `split-window'.  */
   if (MINI_WINDOW_P (o))
@@ -5178,8 +5205,23 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
       Lisp_Object new_normal
 	= horflag ? o->normal_cols : o->normal_lines;
 
-      make_parent_window (old, horflag);
-      p = XWINDOW (o->parent);
+      if (dead && WINDOWP (r->parent) && !WINDOW_VALID_P (r->parent))
+	/* Reuse REFER's old parent too.  */
+	{
+	  p = XWINDOW (r->parent);
+	  replace_window (old, r->parent, true);
+	  wset_next (o, Qnil);
+	  wset_prev (o, Qnil);
+	  wset_parent (o, r->parent);
+	  wset_combination (p, horflag, old);
+	}
+      else
+	/* Make a new parent window.  */
+	{
+	  make_parent_window (old, horflag);
+	  p = XWINDOW (o->parent);
+	}
+
       if (EQ (Vwindow_combination_limit, Qt))
 	/* Store t in the new parent's combination_limit slot to avoid
 	   that its children get merged into another window.  */
@@ -5195,7 +5237,12 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     p = XWINDOW (o->parent);
 
   fset_redisplay (f);
-  new = make_window ();
+
+  if (dead)
+    new = refer;
+  else
+    new = make_window ();
+
   n = XWINDOW (new);
   wset_frame (n, frame);
   wset_parent (n, o->parent);
@@ -5222,16 +5269,19 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   n->window_end_valid = false;
   n->last_cursor_vpos = 0;
 
-  /* Get special geometry settings from reference window.  */
-  n->left_margin_cols = r->left_margin_cols;
-  n->right_margin_cols = r->right_margin_cols;
-  n->left_fringe_width = r->left_fringe_width;
-  n->right_fringe_width = r->right_fringe_width;
-  n->fringes_outside_margins = r->fringes_outside_margins;
-  n->scroll_bar_width = r->scroll_bar_width;
-  n->scroll_bar_height = r->scroll_bar_height;
-  wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
-  wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+  if (!dead)
+    {
+      /* Get special geometry settings from reference window.  */
+      n->left_margin_cols = r->left_margin_cols;
+      n->right_margin_cols = r->right_margin_cols;
+      n->left_fringe_width = r->left_fringe_width;
+      n->right_fringe_width = r->right_fringe_width;
+      n->fringes_outside_margins = r->fringes_outside_margins;
+      n->scroll_bar_width = r->scroll_bar_width;
+      n->scroll_bar_height = r->scroll_bar_height;
+      wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
+      wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+    }
 
   /* Directly assign orthogonal coordinates and sizes.  */
   if (horflag)
@@ -5260,6 +5310,7 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	sum = sum + XFIXNUM (c->new_total);
       c = NILP (c->next) ? 0 : XWINDOW (c->next);
     }
+
   wset_new_total (n, make_fixnum ((horflag
 				   ? p->total_cols
 				   : p->total_lines)
@@ -5267,10 +5318,30 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   wset_new_normal (n, normal_size);
 
   block_input ();
+
+  if (dead)
+    {
+      /* Get dead window back its old buffer and markers.  */
+      wset_buffer (n, n->old_buffer);
+      set_marker_restricted
+	(n->start, make_fixnum (XMARKER (n->start)->charpos), n->contents);
+      set_marker_restricted
+	(n->pointm, make_fixnum (XMARKER (n->pointm)->charpos), n->contents);
+      set_marker_restricted
+	(n->old_pointm, make_fixnum (XMARKER (n->old_pointm)->charpos),
+	 n->contents);
+
+      Vwindow_list = Qnil;
+      /* Remove window from the table of dead windows.  */
+      Fremhash (make_fixnum (n->sequence_number),
+		window_dead_windows_table);
+    }
+
   window_resize_apply (p, horflag);
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  */
+
   set_window_buffer (new, r->contents, true, true);
+
   FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();
 
@@ -5368,6 +5439,8 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  /* Store WINDOW's buffer in old_buffer.  */
+	  wset_old_buffer (w, w->contents);
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
@@ -7712,6 +7785,8 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      /* Store WINDOW's buffer in old_buffer.  */
+      wset_old_buffer (w, w->contents);
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -9064,12 +9139,9 @@ syms_of_window (void)
     doc: /* Hash table of dead windows.
 Each entry in this table maps a window number to a window object.
 Entries are added by `delete-window-internal' and are removed by the
-garbage collector.
-
-This table is maintained by code in window.c and is made visible in
-Elisp for testing purposes only.  */);
+garbage collector.  */);
   window_dead_windows_table
-    = CALLN (Fmake_hash_table, QCweakness, Qt);
+    = CALLN (Fmake_hash_table, QCweakness, Qvalue);
 
   defsubr (&Sselected_window);
   defsubr (&Sold_selected_window);

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

* Re: Add function to rotate/transpose all windows
  2024-10-20  8:19                                                       ` martin rudalics
@ 2024-10-20 14:11                                                         ` Pranshu Sharma
  2024-10-20 17:37                                                           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-10-20 14:11 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:
>
> I now tried to make 'split-window' reuse a dead parent window as well so
> you shouldn't have to care about this any more.  Patch attached.


Thanks!
I can't compile emacs rn(will do tommorow), but does this
respwawn the child windows or only keep the parameters.

Either way, looks like rewrite of window--transpose-1 is coming.

Or do you mean it automaticlly makes the window-parameters the same?, as
in I won't have to modify anything?



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

* Re: Add function to rotate/transpose all windows
  2024-10-20 14:11                                                         ` Pranshu Sharma
@ 2024-10-20 17:37                                                           ` martin rudalics
  2024-10-21  5:54                                                             ` Pranshu Sharma
  2024-10-21 13:37                                                             ` Pranshu Sharma
  0 siblings, 2 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-20 17:37 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > Or do you mean it automaticlly makes the window-parameters the same?, as
 > in I won't have to modify anything?

It should work automatically in the sense that the parent windows get
reused too.  But it doesn't work reliably for a couple of reasons:

With emacs -Q do

(let ((window (split-window)))
   (set-window-buffer window (get-buffer-create "*foo*"))
   (setq window (split-window nil nil t))
   (set-window-buffer window "*Messages*"))

so you have an arrangement with *scratch* and *Messages* at the top and
*foo* at the bottom.  Now rotate windows clockwise with the *Messages*
window selected.  Here this first deletes the remaining windows and
starts with *scratch* as the root window's buffer.  Now the very first
you ask is to split the *scratch* window with REFER set to the *foo*
window.

This is a bad idea because *foo* is _not_ *scratch*'s sibling in the
initial configuration - *Messages* is.  It can break my approach which
reuses the old parent of REFER which, however, is _not_ the old parent
of the *scratch* window.  In the next call to 'split-window' you again
ask to split the *scratch* window but set REFER to the *Messages* window.
All goes miraculously well.

Repeat the call.  Now *foo* with REFER *scratch* reuses the parent of
*scratch*.  Next *scratch* with REFER *Messages* reuses the parent of
*Messages*.  Probably due to the fact in which order you earlier deleted
windows, this changes the parent windows from (the car of each cons is
the buffer window the cdr the parent window)

((#<window 9 on *Messages*> . #<window 8>)
  (#<window 7 on *foo*> . #<window 6>)
  (#<window 3 on *scratch*> . #<window 8>))

before the call to

((#<window 9 on *Messages*> . #<window 6>)
  (#<window 7 on *foo*> . #<window 8>)
  (#<window 3 on *scratch*> . #<window 6>))

after the call. So the parent windows were interchanged.  Looks like a
minor annoyance.  But with a more complex layout things fail.  Try with

(let ((window (split-window)))
   (set-window-buffer window (get-buffer-create "*foo1*"))
   (setq window (split-window nil nil t))
   (set-window-buffer window (get-buffer-create "*foo2*"))
   (setq window (split-window window))
   (set-window-buffer window (get-buffer-create "*foo3*")))

and repeat the scenario.  Here, in the second call, when it tries to
split the window on *foo3* with REFER set to the window of *foo2*,
'split-window' finds out that the parent of *foo2* has been already
reused and allocates a new parent window.

I think you have to change two things:

(1) Instead of

       ;; All child windows need to be recursively deleted.
       (mapc (lambda (win)
	      (when (and (windowp win)
			 (not (eq win fwin)))
		(delete-window win)))
	    ;; We know for sure that first 2 in the list are not
	    ;; windows.
	    (cddr (flatten-list win-tree)))

use

       (delete-other-windows fwin)

Your strategy of deleting windows piecemeal destroys the original window
structure in a quite chaotic way.

(2) Whenever you split a leaf window (like *scratch* in the first
scenario), make sure that REFER gets always set to the window's
'window-next-sibling' (*Messages* in the first scenario) and never to
some window further up or down in the hierarchy (like *foo* in the first
scenario).  Which means that in the first scenario you have two possible
strategies:

- Split *scratch* first with REFER *Messages* and then its parent with
   REFER *foo*.

- Split *foo* first with REFER *scratch* and then *scratch* with REFER
   *Messages*.

You should try both in case we later find out that the one we did choose
fails.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-20 17:37                                                           ` martin rudalics
@ 2024-10-21  5:54                                                             ` Pranshu Sharma
  2024-10-21  8:14                                                               ` martin rudalics
  2024-10-21  9:23                                                               ` martin rudalics
  2024-10-21 13:37                                                             ` Pranshu Sharma
  1 sibling, 2 replies; 111+ messages in thread
From: Pranshu Sharma @ 2024-10-21  5:54 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> Or do you mean it automaticlly makes the window-parameters the same?, as
>> in I won't have to modify anything?
>
> It should work automatically in the sense that the parent windows get
> reused too.  But it doesn't work reliably for a couple of reasons:

I compiled an it works

> With emacs -Q do
>
> (let ((window (split-window)))
>   (set-window-buffer window (get-buffer-create "*foo*"))
>   (setq window (split-window nil nil t))
>   (set-window-buffer window "*Messages*"))
>
> so you have an arrangement with *scratch* and *Messages* at the top and
> *foo* at the bottom.  Now rotate windows clockwise with the *Messages*
> window selected.  Here this first deletes the remaining windows and
> starts with *scratch* as the root window's buffer.  Now the very first
> you ask is to split the *scratch* window with REFER set to the *foo*
> window.
>
> This is a bad idea because *foo* is _not_ *scratch*'s sibling in the
> initial configuration - *Messages* is.  It can break my approach which
> reuses the old parent of REFER which, however, is _not_ the old parent
> of the *scratch* window.  In the next call to 'split-window' you again
> ask to split the *scratch* window but set REFER to the *Messages* window.
> All goes miraculously well.

Ohh, I finally get what you mean.

I thought that for each layout, there is only one possible window tree
that will match it.

> Repeat the call.  Now *foo* with REFER *scratch* reuses the parent of
> *scratch*.  Next *scratch* with REFER *Messages* reuses the parent of
> *Messages*.  Probably due to the fact in which order you earlier deleted
> windows, this changes the parent windows from (the car of each cons is
> the buffer window the cdr the parent window)
>
> ((#<window 9 on *Messages*> . #<window 8>)
>  (#<window 7 on *foo*> . #<window 6>)
>  (#<window 3 on *scratch*> . #<window 8>))
>
> before the call to
>
> ((#<window 9 on *Messages*> . #<window 6>)
>  (#<window 7 on *foo*> . #<window 8>)
>  (#<window 3 on *scratch*> . #<window 6>))
>
> after the call. So the parent windows were interchanged.  Looks like a
> minor annoyance.  But with a more complex layout things fail.  Try with
>
> (let ((window (split-window)))
>   (set-window-buffer window (get-buffer-create "*foo1*"))
>   (setq window (split-window nil nil t))
>   (set-window-buffer window (get-buffer-create "*foo2*"))
>   (setq window (split-window window))
>   (set-window-buffer window (get-buffer-create "*foo3*")))
>
> and repeat the scenario.  Here, in the second call, when it tries to
> split the window on *foo3* with REFER set to the window of *foo2*,
> 'split-window' finds out that the parent of *foo2* has been already
> reused and allocates a new parent window.
>
> I think you have to change two things:
>
> (1) Instead of
>
>       ;; All child windows need to be recursively deleted.
>       (mapc (lambda (win)
> 	      (when (and (windowp win)
> 			 (not (eq win fwin)))
> 		(delete-window win)))
> 	    ;; We know for sure that first 2 in the list are not
> 	    ;; windows.
> 	    (cddr (flatten-list win-tree)))
>
> use
>
>       (delete-other-windows fwin)
>
> Your strategy of deleting windows piecemeal destroys the original window
> structure in a quite chaotic way.

The problem is that it will fail when only acting upon a partial subtree.

> (2) Whenever you split a leaf window (like *scratch* in the first
> scenario), make sure that REFER gets always set to the window's
> 'window-next-sibling' (*Messages* in the first scenario) and never to
> some window further up or down in the hierarchy (like *foo* in the first
> scenario).  Which means that in the first scenario you have two possible
> strategies:
>
> - Split *scratch* first with REFER *Messages* and then its parent with
>   REFER *foo*.
>
> - Split *foo* first with REFER *scratch* and then *scratch* with REFER
>   *Messages*.

I see what you mean here, I knew I should done something like this from
the start when I had to flatten the window subtree.

Seems like this will require a rewrite of the windows--transpose-1
(Yay!), but it should be doable, I have an idea of what I could do.

btw, is there a major perfomance/memory footprint of the patches you are
adding



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

* Re: Add function to rotate/transpose all windows
  2024-10-21  5:54                                                             ` Pranshu Sharma
@ 2024-10-21  8:14                                                               ` martin rudalics
  2024-10-21  9:23                                                               ` martin rudalics
  1 sibling, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-21  8:14 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > I thought that for each layout, there is only one possible window tree
 > that will match it.

I think so too.  But there are infinite many ways to obtain that layout.

 >> Your strategy of deleting windows piecemeal destroys the original window
 >> structure in a quite chaotic way.
 >
 > The problem is that it will fail when only acting upon a partial subtree.

I see.  I think I have to give 'delete-other-windows' an additional
argument say MAIN so it checks whether WINDOW is in MAIN's subtree and
puts WINDOW in the place of MAIN in that subtree.  You would call it
with MAIN set to the root of the subtree and WINDOW to fwin.

Note again: When you delete windows one by one you may change the
parents of the windows that remain but you want to delete afterwards.
But 'split-window' should work with the parent windows that existed
_before_ you started deleting.  So maybe you should first try to
experiment with 'delete-other-windows' as it is now and then we can try
to fix 'delete-other-windows' the way I sketched above.

 > I see what you mean here, I knew I should done something like this from
 > the start when I had to flatten the window subtree.
 >
 > Seems like this will require a rewrite of the windows--transpose-1
 > (Yay!), but it should be doable, I have an idea of what I could do.

I cannot exclude that one can also get good results by using your
current approach.  As I said above, there are many ways to obtain a
specific layout.

 > btw, is there a major perfomance/memory footprint of the patches you are
 > adding

In which sense?  Reusing windows should reduce GC overhead since you
create less new windows and produce less obsolete ones.  Everything else
is negligible.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-21  5:54                                                             ` Pranshu Sharma
  2024-10-21  8:14                                                               ` martin rudalics
@ 2024-10-21  9:23                                                               ` martin rudalics
  1 sibling, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-10-21  9:23 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 >> Your strategy of deleting windows piecemeal destroys the original window
 >> structure in a quite chaotic way.
 >
 > The problem is that it will fail when only acting upon a partial subtree.

I completely forgot that 'delete-other-windows-internal' should already
do everything you need: Call it with WINDOW set to fwin and ROOT set to
the root window of the "partial subtree".

martin



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

* Re: Add function to rotate/transpose all windows
  2024-10-20 17:37                                                           ` martin rudalics
  2024-10-21  5:54                                                             ` Pranshu Sharma
@ 2024-10-21 13:37                                                             ` Pranshu Sharma
  2024-10-22 18:12                                                               ` martin rudalics
  1 sibling, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-10-21 13:37 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:
>
> - Split *scratch* first with REFER *Messages* and then its parent with
>   REFER *foo*.
>
> - Split *foo* first with REFER *scratch* and then *scratch* with REFER
>   *Messages*.
>

Here, the second option is non feesible as it relies on #<window 222 on
*foo*> being an actual window, not a window split.  Althouht it is
possible with workarounds and I'll end up implimenting the first option
trying to get the second to work.

(t (0 1 148 31)
    (nil (0 1 148 16) #<window 182 on *scratch*> #<window 224 on
	 *Messages*>)
    #<window 222 on *foo*>)

When I orignally wrote the transpose-windows-1, I didn't know about
internal windows.
    
Also I looked at your other message about window split internal, that
should help a lot.



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

* Re: Add function to rotate/transpose all windows
  2024-10-21 13:37                                                             ` Pranshu Sharma
@ 2024-10-22 18:12                                                               ` martin rudalics
  2024-10-24 14:38                                                                 ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-22 18:12 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

 >> - Split *scratch* first with REFER *Messages* and then its parent with
 >>    REFER *foo*.
 >>
 >> - Split *foo* first with REFER *scratch* and then *scratch* with REFER
 >>    *Messages*.
 >>
 >
 > Here, the second option is non feesible as it relies on #<window 222 on
 > *foo*> being an actual window, not a window split.  Althouht it is
 > possible with workarounds and I'll end up implimenting the first option
 > trying to get the second to work.
 >
 > (t (0 1 148 31)
 >      (nil (0 1 148 16) #<window 182 on *scratch*> #<window 224 on
 > 	 *Messages*>)
 >      #<window 222 on *foo*>)
 >
 > When I orignally wrote the transpose-windows-1, I didn't know about
 > internal windows.
 >
 > Also I looked at your other message about window split internal, that
 > should help a lot.

OK.  I looked into this again and maybe I can help you.  Attached find a
new version of 'split-window' that accepts also a (WINDOW . PARENT)
cons as REFER where WINDOW is the buffer window to use and PARENT its
parent before WINDOW was deleted.  How to use that is documented in the
version of window-transpose.el I also attach.  It's full of debugging
code so you have to figure out yourself how to make use of it.

The three crucial aspects are:

(1) It has a function 'window-alist' that establishes before transposing
anything an association list of all live windows with their parents and
stores it in the variable 'window-alist'.

(2) It deletes windows via 'delete-other-windows-internal'.

(3) When it calls 'split-window' then in the (listp win) case I set the
cdr of the cons to the old parent of the _window to split_ (found via
'window-alist').  In the win is a window case, I set the cdr of the cons
to the old parent of the _window to make_ (again found via
'window-alist').

This survives quite a number of 'rotate-windows-clockwise' on the three
windows structure I create on the bottom of window-transpose.el.

Try it and then maybe try it with your remaining functions in an
analogous manner.  The tricky part is certainly (3) which might just
work with my simple rotation scenario.

martin

[-- Attachment #2: split-window-diff --]
[-- Type: text/plain, Size: 21794 bytes --]

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 0b8d7d3b76d..8bf6ebc828b 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -1355,7 +1355,7 @@ Splitting Windows
 Examples of such windows are side windows (@pxref{Side Windows}) and
 atomic windows (@pxref{Atomic Windows}).

-@defun split-window &optional window size side pixelwise
+@defun split-window &optional window size side pixelwise refer
 This function creates a new live window next to the window
 @var{window}.  If @var{window} is omitted or @code{nil}, it defaults
 to the selected window.  That window is split, and reduced in
@@ -1403,10 +1403,35 @@ Splitting Windows
 to interpret @var{size} in units of pixels, instead of lines and
 columns.

-If @var{window} is a live window, the new window inherits various
-properties from it, including margins and scroll bars.  If
-@var{window} is an internal window, the new window inherits the
-properties of the window selected within @var{window}'s frame.
+If the optional fifth argument @var{refer} is non-@code{nil}, it has to
+denote either a deleted, former live window on the same frame as
+@var{window} or an arbitrary live window.  If @var{refer} is a deleted
+window, this function does not make a new window but rather resurrects
+@var{refer} and inserts it into the window tree at the position and with
+the sizes the new window would have been given.  Buffer, start and point
+positions of @var{refer} are restored to the values they had immediately
+before @var{refer} was deleted the last time.  Decorations and
+parameters remain unaltered from their values before @var{refer} was
+deleted.  An error is thrown if @var{refer}'s buffer has been deleted
+after @var{refer} itself was deleted.
+
+If and only if @var{refer} is a deleted window and its former parent
+window (@pxref{Windows and Frames}) is a deleted window too, this
+function will also resurrect the old parent window and insert it into
+the appropriate place in the window tree.
+
+Note that in order to resurrect a deleted window, that window must not
+have been recycled by the garbage collector (@pxref{Garbage Collection})
+yet.  Hence to make sure that a deleted window can get resurrected, you
+should keep a reference to any such window in a variable until this
+function returns.
+
+If @var{refer} is a live window, the new window will inherit properties
+like buffer, start and point and some decorations from @var{refer}.  If
+@var{refer} is @code{nil} or omitted, then if @var{window} is live, any
+such properties are inherited from @var{window}.  If, however,
+@var{window} is an internal window, the new window will inherit these
+properties from the window selected on @var{window}'s frame.

 The behavior of this function may be altered by the window parameters
 of @var{window}, so long as the variable
@@ -7126,13 +7151,16 @@ Window Hooks
 window.

 @defun window-old-buffer &optional window
-This function returns the buffer shown in @var{window} at the last
-time window change functions were run for @var{window}'s frame.  If it
-returns @code{nil}, @var{window} has been created after that.  If it
-returns @code{t}, @var{window} was not shown at that time but has been
-restored from a previously saved window configuration afterwards.
-Otherwise, the return value is the buffer shown by @var{window} at
-that time.
+This function returns the buffer shown in @var{window} at the last time
+window change functions were run for @var{window}'s frame.  If it
+returns @code{nil}, @var{window} is either an internal window or has
+been created after that.  If it returns @code{t}, @var{window} was not
+shown at that time but has been restored from a previously saved window
+configuration afterwards.  Otherwise, the return value is the buffer
+shown by @var{window} at that time.  As a special case, if @var{window}
+has been deleted, this function returns the last buffer @var{window} had
+shown at that time.  @var{window} can be any window and defaults to the
+selected one.
 @end defun

 @defun window-old-pixel-width &optional window
diff --git a/lisp/window.el b/lisp/window.el
index b50770cbd7e..2cf2c288452 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -5511,7 +5511,7 @@ window--combination-resizable
       (setq sibling (window-next-sibling sibling)))
     (/ size (1+ number))))

-(defun split-window (&optional window size side pixelwise)
+(defun split-window (&optional window size side pixelwise refer)
   "Make a new window adjacent to WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 Return the new window which is always a live window.
@@ -5554,11 +5554,30 @@ split-window
 root of that atomic window.  The new window does not become a
 member of that atomic window.

-If WINDOW is live, properties of the new window like margins and
-scrollbars are inherited from WINDOW.  If WINDOW is an internal
-window, these properties as well as the buffer displayed in the
-new window are inherited from the window selected on WINDOW's
-frame.  The selected window is not changed by this function."
+If the optional fifth argument REFER is non-nil, it has to denote either
+a deleted, former live window on the same frame as WINDOW or an
+arbitrary live window.  If REFER is a deleted window, do not make a new
+window but rather make REFER live again and insert it into the window
+tree at the position and with the sizes the new window would have been
+given.  Buffer, start and point positions of REFER are set to the values
+they had immediately before REFER was deleted the last time.
+Decorations and parameters remain unaltered from their values before
+REFER was deleted.  Throw an error if REFER's buffer has been deleted
+after REFER itself was deleted.
+
+If and only if REFER is a deleted window and its previous parent window
+is now a deleted window too, do not make a new parent window but reuse
+the old aprent window, making it a valid window again.
+
+If REFER is a live window, the new window will inherit properties like
+buffer, start and point and some decorations from REFER.  If REFER is
+nil or omitted, then if WINDOW is live, any such properties are
+inherited from WINDOW.  If, however, WINDOW is an internal window, the
+new window will inherit these properties from the window selected on
+WINDOW's frame.
+
+The selected window and the selected window on WINDOW's frame are
+not changed by this function."
   (setq window (window-normalize-window window))
   (let* ((side (cond
 		((not side) 'below)
@@ -5598,14 +5617,14 @@ split-window
        ((and (window-parameter window 'window-atom)
 	     (setq atom-root (window-atom-root window))
 	     (not (eq atom-root window)))
-	(throw 'done (split-window atom-root size side pixelwise)))
+	(throw 'done (split-window atom-root size side pixelwise refer)))
        ;; If WINDOW's frame has a side window and WINDOW specifies the
        ;; frame's root window, split the frame's main window instead
        ;; (Bug#73627).
        ((and (eq window (frame-root-window frame))
 	     (window-with-parameter 'window-side nil frame))
 	(throw 'done (split-window (window-main-window frame)
-				   size side pixelwise)))
+				   size side pixelwise refer)))
        ;; If WINDOW is a side window or its first or last child is a
        ;; side window, throw an error unless `window-combination-resize'
        ;; equals 'side.
@@ -5644,8 +5663,8 @@ split-window
 		   (window-combined-p window horizontal)))
 	     ;; 'old-pixel-size' is the current pixel size of WINDOW.
 	     (old-pixel-size (window-size window horizontal t))
-	     ;; 'new-size' is the specified or calculated size of the
-	     ;; new window.
+	     ;; 'new-pixel-size' is the specified or calculated size
+	     ;; of the new window.
 	     new-pixel-size new-parent new-normal)
 	(cond
 	 ((not pixel-size)
@@ -5766,8 +5785,9 @@ split-window
 	   window (- (if new-parent 1.0 (window-normal-size window horizontal))
 		     new-normal)))

-	(let* ((new (split-window-internal window new-pixel-size side new-normal)))
-	  (window--pixel-to-total frame horizontal)
+	(let ((new (split-window-internal
+		    window new-pixel-size side new-normal refer)))
+          (window--pixel-to-total frame horizontal)
 	  ;; Assign window-side parameters, if any.
 	  (cond
 	   ((eq window-combination-resize 'side)
diff --git a/src/window.c b/src/window.c
index 34968ac824f..5254f96ba89 100644
--- a/src/window.c
+++ b/src/window.c
@@ -652,15 +652,16 @@ DEFUN ("window-buffer", Fwindow_buffer, Swindow_buffer, 0, 1, 0,

 DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
        doc: /* Return the old buffer displayed by WINDOW.
-WINDOW must be a live window and defaults to the selected one.
+WINDOW can be any window and defaults to the selected one.

 The return value is the buffer shown in WINDOW at the last time window
-change functions were run.  It is nil if WINDOW was created after
-that.  It is t if WINDOW has been restored from a window configuration
-after that.  */)
+change functions were run or WINDOW is a former live window that was
+deleted.  It is nil if WINDOW was created after that.  It is t if WINDOW
+has been restored from a window configuration after that.  It is always
+nil if WINDOW is an internal window.  */)
   (Lisp_Object window)
 {
-  struct window *w = decode_live_window (window);
+  struct window *w = decode_any_window (window);

   return (NILP (w->old_buffer)
 	  /* A new window.  */
@@ -668,8 +669,8 @@ DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
 	  : (w->change_stamp != WINDOW_XFRAME (w)->change_stamp)
 	  /* A window restored from a configuration.  */
 	  ? Qt
-	  /* A window that was live the last time seen by window
-	     change functions.  */
+	  /* A window that was live the last time seen by window change
+	     functions or was deleted.  */
 	  : w->old_buffer);
 }

@@ -4491,45 +4492,6 @@ allocate_window (void)
 				       PVEC_WINDOW);
 }

-/* Make new window, have it replace WINDOW in window-tree, and make
-   WINDOW its only vertical child (HORFLAG means make WINDOW its only
-   horizontal child).   */
-static void
-make_parent_window (Lisp_Object window, bool horflag)
-{
-  Lisp_Object parent;
-  register struct window *o, *p;
-
-  o = XWINDOW (window);
-  p = allocate_window ();
-  memcpy ((char *) p + sizeof (union vectorlike_header),
-	  (char *) o + sizeof (union vectorlike_header),
-	  word_size * VECSIZE (struct window));
-  /* P's buffer slot may change from nil to a buffer...  */
-  adjust_window_count (p, 1);
-  XSETWINDOW (parent, p);
-
-  p->sequence_number = ++sequence_number;
-
-  replace_window (window, parent, true);
-
-  wset_next (o, Qnil);
-  wset_prev (o, Qnil);
-  wset_parent (o, parent);
-  /* ...but now P becomes an internal window.  */
-  wset_start (p, Qnil);
-  wset_pointm (p, Qnil);
-  wset_old_pointm (p, Qnil);
-  wset_buffer (p, Qnil);
-  wset_combination (p, horflag, window);
-  wset_combination_limit (p, Qnil);
-  /* Reset any previous and next buffers of p which have been installed
-     by the memcpy above.  */
-  wset_prev_buffers (p, Qnil);
-  wset_next_buffers (p, Qnil);
-  wset_window_parameters (p, Qnil);
-}
-
 /* Make new window from scratch.  */
 Lisp_Object
 make_window (void)
@@ -5073,7 +5035,7 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
 }


-DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 4, 0,
+DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 5, 0,
        doc: /* Split window OLD.
 Second argument PIXEL-SIZE specifies the number of pixels of the
 new window.  It must be a positive integer.
@@ -5088,32 +5050,33 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 the right side of WINDOW.  SIDE `left' means the new window shall be
 located on the left of WINDOW.  In both cases PIXEL-SIZE specifies the
 width of the new window including space reserved for fringes and the
-scrollbar or a divider column.
+scroll bar or a divider column.

 Fourth argument NORMAL-SIZE specifies the normal size of the new window
-according to the SIDE argument.
+according to the SIDE argument.  Optional fifth argument REFER is as for
+'split-window'.

 The new pixel and normal sizes of all involved windows must have been
 set correctly.  See the code of `split-window' for how this is done.  */)
-  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size)
-{
-  /* OLD (*o) is the window we have to split.  (*p) is either OLD's
-     parent window or an internal window we have to install as OLD's new
-     parent.  REFERENCE (*r) must denote a live window, or is set to OLD
-     provided OLD is a leaf window, or to the frame's selected window.
-     NEW (*n) is the new window created with some parameters taken from
-     REFERENCE (*r).  */
-  Lisp_Object new, frame, reference;
-  struct window *o, *p, *n, *r, *c;
-  struct frame *f;
+  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side,
+   Lisp_Object normal_size, Lisp_Object refer)
+{
+  /* OLD (*o) is the window to split.  REFER (*r) is a reference window,
+     either an arbitrary live window or a former live, now deleted
+     window on the same frame as OLD.  NEW (*n) is the new window
+     created anew or resurrected from REFER (*r), if specified.  *p
+     refers either to OLD's parent window that will become NEW's parent
+     window too or to a new internal window that becomes OLD's and NEW's
+     new parent.  */
+  struct window *o = decode_valid_window (old);
+  Lisp_Object frame = WINDOW_FRAME (o);
+  struct frame *f = XFRAME (frame);
+  struct window *p, *n, *r, *c;
   bool horflag
     /* HORFLAG is true when we split side-by-side, false otherwise.  */
     = EQ (side, Qt) || EQ (side, Qleft) || EQ (side, Qright);
-
-  CHECK_WINDOW (old);
-  o = XWINDOW (old);
-  frame = WINDOW_FRAME (o);
-  f = XFRAME (frame);
+  Lisp_Object new, parent = Qnil;
+  bool dead = false;

   CHECK_FIXNUM (pixel_size);
   EMACS_INT total_size
@@ -5131,14 +5094,54 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	   ? WINDOW_VERTICAL_COMBINATION_P (XWINDOW (o->parent))
 	   : WINDOW_HORIZONTAL_COMBINATION_P (XWINDOW (o->parent))));

-  /* We need a live reference window to initialize some parameters.  */
-  if (WINDOW_LIVE_P (old))
-    /* OLD is live, use it as reference window.  */
-    reference = old;
+  /* Set up reference window.  */
+  if (NILP (refer))
+    {
+      if (WINDOW_LIVE_P (old))
+	/* OLD is live, use it as reference window.  */
+	refer = old;
+      else
+	/* Use the frame's selected window as reference window.  */
+	refer = FRAME_SELECTED_WINDOW (f);
+
+      r = XWINDOW (refer);
+    }
   else
-    /* Use the frame's selected window as reference window.  */
-    reference = FRAME_SELECTED_WINDOW (f);
-  r = XWINDOW (reference);
+    {
+      if (CONSP (refer))
+	{
+	  parent = Fcdr (refer);
+	  p = decode_any_window (parent);
+	  refer = Fcar (refer);
+
+	  /* Note: PARENT is not necessarily REFER's parent at the time
+	     REFER was deleted but maybe some earlier parent of REFER.  */
+	  if (BUFFERP (p->old_buffer))
+	    error ("Deleted window in REFER was a live window (did show a buffer)");
+	  else if (!EQ (p->frame, frame))
+	    error ("Deleted window in REFER was not on same frame as the window to split");
+	}
+
+      r = decode_any_window (refer);
+
+      if (NILP (r->contents))
+	/* Presumably a deleted, former live window.  Check whether its
+	   contents can be used.  */
+	{
+	  if (!BUFFERP (r->old_buffer))
+	    error ("Deleted window in REFER was not a live window (did not show a buffer)");
+	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	    error ("The buffer formerly shown by deleted window in REFER has been deleted");
+	  else if (!EQ (r->frame, frame))
+	    error ("Deleted window in REFER was not on same frame as the window to split");
+
+	  dead = true;
+	}
+      else if (!NILP (parent))
+	error ("REFER must not contain live and dead windows");
+      else if (!WINDOW_LIVE_P (refer))
+	error ("REFER is not a live window (does not show a buffer)");
+    }

   /* The following bugs are caught by `split-window'.  */
   if (MINI_WINDOW_P (o))
@@ -5178,8 +5181,31 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
       Lisp_Object new_normal
 	= horflag ? o->normal_cols : o->normal_lines;

-      make_parent_window (old, horflag);
-      p = XWINDOW (o->parent);
+      /* This is the crux of the old make_parent_window.  */
+      if (dead && WINDOWP (parent) && !WINDOW_VALID_P (parent))
+	/* Reuse REFER's cdr (provided by caller).  */
+	p = XWINDOW (parent);
+      else if (dead && WINDOWP (r->parent) && !WINDOW_VALID_P (r->parent))
+	/* Reuse REFER's old parent (potentially unsafe).  */
+	{
+	  parent = r->parent;
+	  p = XWINDOW (parent);
+	}
+      else
+	/* Allocate new parent (classic style).  */
+	{
+	  p = allocate_window ();
+	  XSETWINDOW (parent, p);
+	  p->sequence_number = ++sequence_number;
+	  wset_frame (p, frame);
+	}
+
+      replace_window (old, parent, true);
+      wset_next (o, Qnil);
+      wset_prev (o, Qnil);
+      wset_parent (o, parent);
+      wset_combination (p, horflag, old);
+
       if (EQ (Vwindow_combination_limit, Qt))
 	/* Store t in the new parent's combination_limit slot to avoid
 	   that its children get merged into another window.  */
@@ -5195,7 +5221,12 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     p = XWINDOW (o->parent);

   fset_redisplay (f);
-  new = make_window ();
+
+  if (dead)
+    new = refer;
+  else
+    new = make_window ();
+
   n = XWINDOW (new);
   wset_frame (n, frame);
   wset_parent (n, o->parent);
@@ -5222,16 +5253,19 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   n->window_end_valid = false;
   n->last_cursor_vpos = 0;

-  /* Get special geometry settings from reference window.  */
-  n->left_margin_cols = r->left_margin_cols;
-  n->right_margin_cols = r->right_margin_cols;
-  n->left_fringe_width = r->left_fringe_width;
-  n->right_fringe_width = r->right_fringe_width;
-  n->fringes_outside_margins = r->fringes_outside_margins;
-  n->scroll_bar_width = r->scroll_bar_width;
-  n->scroll_bar_height = r->scroll_bar_height;
-  wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
-  wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+  if (!dead)
+    {
+      /* Get special geometry settings from reference window.  */
+      n->left_margin_cols = r->left_margin_cols;
+      n->right_margin_cols = r->right_margin_cols;
+      n->left_fringe_width = r->left_fringe_width;
+      n->right_fringe_width = r->right_fringe_width;
+      n->fringes_outside_margins = r->fringes_outside_margins;
+      n->scroll_bar_width = r->scroll_bar_width;
+      n->scroll_bar_height = r->scroll_bar_height;
+      wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
+      wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+    }

   /* Directly assign orthogonal coordinates and sizes.  */
   if (horflag)
@@ -5260,6 +5294,7 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	sum = sum + XFIXNUM (c->new_total);
       c = NILP (c->next) ? 0 : XWINDOW (c->next);
     }
+
   wset_new_total (n, make_fixnum ((horflag
 				   ? p->total_cols
 				   : p->total_lines)
@@ -5267,10 +5302,30 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   wset_new_normal (n, normal_size);

   block_input ();
+
+  if (dead)
+    {
+      /* Get dead window back its old buffer and markers.  */
+      wset_buffer (n, n->old_buffer);
+      set_marker_restricted
+	(n->start, make_fixnum (XMARKER (n->start)->charpos), n->contents);
+      set_marker_restricted
+	(n->pointm, make_fixnum (XMARKER (n->pointm)->charpos), n->contents);
+      set_marker_restricted
+	(n->old_pointm, make_fixnum (XMARKER (n->old_pointm)->charpos),
+	 n->contents);
+
+      Vwindow_list = Qnil;
+      /* Remove window from the table of dead windows.  */
+      Fremhash (make_fixnum (n->sequence_number),
+		window_dead_windows_table);
+    }
+
   window_resize_apply (p, horflag);
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  */
+
   set_window_buffer (new, r->contents, true, true);
+
   FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();

@@ -5368,6 +5423,8 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  /* Store WINDOW's buffer in old_buffer.  */
+	  wset_old_buffer (w, w->contents);
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
@@ -7712,6 +7769,8 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      /* Store WINDOW's buffer in old_buffer.  */
+      wset_old_buffer (w, w->contents);
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -9064,12 +9123,9 @@ syms_of_window (void)
     doc: /* Hash table of dead windows.
 Each entry in this table maps a window number to a window object.
 Entries are added by `delete-window-internal' and are removed by the
-garbage collector.
-
-This table is maintained by code in window.c and is made visible in
-Elisp for testing purposes only.  */);
+garbage collector.  */);
   window_dead_windows_table
-    = CALLN (Fmake_hash_table, QCweakness, Qt);
+    = CALLN (Fmake_hash_table, QCweakness, Qvalue);

   defsubr (&Sselected_window);
   defsubr (&Sold_selected_window);

[-- Attachment #3: window-transpose.el --]
[-- Type: text/x-emacs-lisp, Size: 9966 bytes --]

;;; -*- lexical-binding:t -*-
(setq debug-on-error t)

(defvar old-window-alist nil)
(defvar old-window-list nil)
(defvar new-window-alist nil)
(defvar window-alist nil)

(defun window-alist (&optional window)
  (setq window-alist nil)
  (walk-window-subtree
   (lambda (w)
     (setq window-alist
	   (cons (cons w (window-parent w))
		 window-alist)))
   (or window (frame-root-window))))

(defun old-window-alist ()
  (setq old-window-list (window-list))
  (setq old-window-alist nil)
  (dolist (window old-window-list)
    (setq old-window-alist
	  (cons
	   (cons window (window-parent window))
	   old-window-alist)))

  (setq old-window-alist (nreverse old-window-alist)))

(defun new-window-alist ()
  (setq new-window-alist nil)
  (dolist (window old-window-list)
    (setq new-window-alist
	  (cons
	   (cons window (window-parent window))
	   new-window-alist)))

  (setq new-window-alist (nreverse new-window-alist)))

(defun window-tree-pixel-sizes (window &optional next)
  "Return pixel sizes of all windows rooted at WINDOW.
The return value is a list where each window is represented either by a
triple whose first element is either t for an internal window that is a
horizontal combination, nil for an internal window that is a vertical
combination, or the window itself for a live window.  The second element
is a cons of the pixel height and pixel width of the window.  The third
element is specified for internal windows only and recursively lists
that window's child windows using the same triple structure."
  (let (list)
    (while window
      (setq list
	    (cons
	     (cond
	      ((window-top-child window)
	       (cons t (cons (cons (window-pixel-height window)
				   (window-pixel-width window))
			     (window-tree-pixel-sizes
			      (window-top-child window) t))))
	      ((window-left-child window)
	       (cons nil (cons (cons (window-pixel-height window)
				     (window-pixel-width window))
			       (window-tree-pixel-sizes
				(window-left-child window) t))))
	      (t (list window (cons (window-pixel-height window)
				    (window-pixel-width window)))))
	     list))
      (setq window (when next (window-next-sibling window))))
    (nreverse list)))

(defun rotate-windows-anticlockwise (&optional subtree frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW anticlockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
of the frame, otherwise rotate FRAME-OR-WINDOW.  See
`rotate-windows-clockwise' for how to rotate windows in the opposite
direction.  If SUBTREE is non-nil, the function will act as if
FRAME-OR-WINDOW is the parent window of the selected window."
  (interactive "P")
  (let ((window (if subtree
		    (window-parent)
		  (if (windowp frame-or-window)
		      frame-or-window
		    (window-main-window frame-or-window)))))
    (window--transpose window '(right . above) nil)))

(defun rotate-windows-clockwise (&optional subtree frame-or-window)
  "Rotate windows of FRAME-OR-WINDOW clockwise by 90 degrees.
FRAME-OR-WINDOW must be a live frame or window and defaults to the
selected frame.  If FRAME-OR-WINDOW is a frame, rotate the main window
of the frame, otherwise rotate FRAME-OR-WINDOW.  See
`rotate-windows-anticlockwise' for how to rotate windows in the opposite
direction.  If SUBTREE is non-nil, the function will act as if
FRAME-OR-WINDOW is the parent window of the selected window."
  (interactive "P")
  (let ((window (cond
		 (subtree
		  (window-parent))
		 ((windowp frame-or-window)
		  frame-or-window)
		 (t
		  (window-main-window frame-or-window)))))
    (old-window-alist)
    (window-alist window)
;;     (message "%s" window-alist) (sit-for 3)
    (window--transpose window '(left . below) nil)
    (new-window-alist)
    (with-current-buffer (get-buffer-create "*foo*")
      (erase-buffer)
      (pp old-window-alist (current-buffer))
      (insert "\n")
      (pp new-window-alist (current-buffer)))))

(defun flip-windows-horizontally (&optional subtree frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.
When the windows are flipped horzontally, the window layout is made to
it's reflection from the side edge.  FRAME-OR-WINDOW must be a live
frame or window and defaults to the selected frame.  If FRAME-OR-WINDOW
is a frame, flip from the main window of the frame, otherwise flip from
FRAME-OR-WINDOW.  See `flip-windows-vertically' for how to flip windows
vertically.  If SUBTREE is non-nil, the function will act as if
FRAME-OR-WINDOW is the parent window of the selected window."
  (interactive "P")
  (let ((window (cond
		 (subtree
		  (window-parent))
		 ((windowp frame-or-window)
		  frame-or-window)
		 (t
		  (window-main-window frame-or-window)))))
    (window--transpose window '(below . left) t)))

(defun flip-windows-vertically (&optional subtree frame-or-window)
  "Horizontally flip windows of FRAME-OR-WINDOW.
When the windows are flipped vertically, the window layout is made to
it's reflection from the top edge.  FRAME-OR-WINDOW must be a live frame
or window and defaults to the selected frame.  If FRAME-OR-WINDOW is a
frame, flip from the main window of the frame, otherwise flip from
FRAME-OR-WINDOW.  See `flip-windows-horizontally' for how to flip
windows horizontally."
  (interactive "P")
  (let ((window (if subtree
		    (window-parent)
		  (if (windowp frame-or-window)
		      frame-or-window
		    (window-main-window frame-or-window)))))
    (window--transpose window '(above . right) t)))

(defun transpose-windows (&optional subtree frame-or-window)
  "Transpose windows of FRAME-OR-WINDOW.
Rearrange windows such that where a horizontal split was used a vertical
one is used instead, and vice versa.  FRAME-OR-WINDOW must be a live
frame or window and defaults to the selected frame.  If FRAME-OR-WINDOW
is a frame, transpose the main window of the frame, otherwise transpose
FRAME-OR-WINDOW.  If SUBTREE is non-nil, the function will act as if
FRAME-OR-WINDOW is the parent window of the selected window."
  (interactive "P")
  (let ((window (if subtree
		    (window-parent)
		  (if (windowp frame-or-window)
		      frame-or-window
		    (window-main-window frame-or-window)))))
    (window--transpose window '(right . below) nil)))

(defun window--transpose (window conf do-not-convert-size)
  "Rearrange windows of WINDOW recursively.
CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
HORIZONTAL-SPLIT will be used as the third argument of `split-window'
when splitting a window that was previously horizontally split, and
VERTICAL-SPLIT as third argument of `split-window' for a window that was
previously vertically split.  If DO-NOT-CONVERT-SIZE non-nil, the size
argument of the window-split is converted from vertical to horizontal or
vice versa, with the same proportion of the total split."
  (if (or (not window)
	  (window-live-p window))
      (message "No windows to transpose")
    (let* ((frame (window-frame window))
	   (fwin window)
	   (selwin (frame-selected-window window))
	   (win-tree (car (window-tree-pixel-sizes window))))
      (while (not (window-live-p fwin))
	(setq fwin (window-child fwin)))
      (delete-other-windows-internal
       fwin (and (window-valid-p (car win-tree)) (car win-tree)))
      (window--transpose-1 win-tree fwin conf do-not-convert-size)
      ;; Go back to previously selected window.
      (set-frame-selected-window frame selwin))))

(defun window--transpose-1 (subtree cwin conf do-not-convert-size)
  "Subroutine of `window--transpose'.
SUBTREE must be in the format of the result of
`window-tree-pixel-sizes'.  CWIN is the current window through which the
window splits are made.  The CONF and DO-NOT-CONVERT-SIZE arguments are
the same as the ones in `window--transpose'."
  ;; `ilen' is the max size a window could be of given the split type.
  ;; `flen' is max size the window could be converted to the opposite
  ;; of the given split type.
  (pcase-let ((`(,ilen . ,flen) (if (car subtree)
				    (cons (float (car (cadr subtree)))
					  (float (window-pixel-width cwin)))
				  (cons (float (cdr (cadr subtree)))
					(float (window-pixel-height cwin))))))
    (mapc
     (pcase-lambda (`(,win . ,size))
       (let ((split-size (- (if do-not-convert-size
				size
			      (round (* flen  (/ size ilen))))))
	     (split-type
	      (funcall (if (car subtree) 'car 'cdr) conf)))
	 (if (listp win)
	     (let* ((refer-car (seq-some
				(lambda (x)
				  (and (windowp x) x))
				(flatten-list win)))
		    (refer (cons refer-car
				 (cdr (assq cwin window-alist)))))
;; 	       (message "1 .. %s" (cons refer (cdr (assq refer-car window-alist)))) (sit-for 3)
	       ;; `win' is a window subtree.
	       (window--transpose-1 win (split-window cwin
						      split-size
						      split-type
						      t
						      refer)
				    conf do-not-convert-size))
	   ;; `win' is a window.
;; 	   (message "2 .. %s" (cons win (cdr (assq win window-alist)))) (sit-for 3)
	   (split-window cwin split-size
			 split-type t
			 (cons win (cdr (assq win window-alist)))))))
     (mapcar
      (lambda (e)
	(let ((window? (if (windowp (car e)) (car e) e)))
	  (cons window?
		;; The respective size of the window.
		(if (car subtree)
		    (car (cadr e))
		  (cdr (cadr e))))))
      ;; By using cdddr, we ignore window split type, sizes and the
      ;; first window (it's implicitly created).
      (nreverse (cdddr subtree))))
    ;; (caaddr subtree) is the first window.
    (unless (windowp (caaddr subtree))
      (window--transpose-1 (caddr subtree) cwin conf do-not-convert-size))))

(global-set-key [(super p)] 'transpose-windows)
(global-set-key [(super q)] 'rotate-windows-clockwise)

(let ((window (split-window)))
  (set-window-buffer window (get-buffer-create "*foo*"))
  (setq window (split-window nil nil t))
  (set-window-buffer window (get-buffer-create "*Messages*"))
  (select-window window))

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

* Re: Add function to rotate/transpose all windows
  2024-10-22 18:12                                                               ` martin rudalics
@ 2024-10-24 14:38                                                                 ` Pranshu Sharma
  2024-10-24 18:39                                                                   ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-10-24 14:38 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

> OK.  I looked into this again and maybe I can help you.  Attached find a
> new version of 'split-window' that accepts also a (WINDOW . PARENT)
> cons as REFER where WINDOW is the buffer window to use and PARENT its
> parent before WINDOW was deleted.  How to use that is documented in the
> version of window-transpose.el I also attach.  It's full of debugging
> code so you have to figure out yourself how to make use of it.
>
> The three crucial aspects are:
>
> (1) It has a function 'window-alist' that establishes before transposing
> anything an association list of all live windows with their parents and
> stores it in the variable 'window-alist'.
>
> (2) It deletes windows via 'delete-other-windows-internal'.
>
> (3) When it calls 'split-window' then in the (listp win) case I set the
> cdr of the cons to the old parent of the _window to split_ (found via
> 'window-alist').  In the win is a window case, I set the cdr of the cons
> to the old parent of the _window to make_ (again found via
> 'window-alist').
>
> This survives quite a number of 'rotate-windows-clockwise' on the three
> windows structure I create on the bottom of window-transpose.el.
>
> Try it and then maybe try it with your remaining functions in an
> analogous manner.  The tricky part is certainly (3) which might just
> work with my simple rotation scenario.

Sorry for late reply, been caught up with rl shit.

I had a skim look at this, will have a proper one tommorow or saturday,
and I think the problem of using flatten-list still exists.

I've made a new protoptype (buggy) of window--transpose-1 that does not
call flatten.

Right now, it works with this kind of split:

(let ((win (split-window (split-window nil nil t))))
  (dotimes (_ 4)
    (setq win (split-window win 10 t))
    ))
    
and:

(let ((win (split-window (split-window nil nil t))))
  (dotimes (_ 4)
    (setq win (split-window win 10 t))
    ))

However, not on the one achived by:
C-x 3
C-x o
C-x 2
C-x 3

I did make some progress on this, but the problem was there was no way I know
to achive this, which makes it a bit harder:

|-------------|      |-------------|
|      A      |      |      A      |
|-------------|      |-------------|
|       B     | ->   |  B   |      |
|-------------|      |------|   D  |
|      C      |      |   C  |      |
|-------------|      |-------------|

I'm sure there is way to make it work without this, I'll probably come
up with way in shower.


[-- Attachment #2: isolate.el --]
[-- Type: application/emacs-lisp, Size: 2205 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-10-24 14:38                                                                 ` Pranshu Sharma
@ 2024-10-24 18:39                                                                   ` martin rudalics
  2024-10-25 14:24                                                                     ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-24 18:39 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

 > However, not on the one achived by:
 > C-x 3
 > C-x o
 > C-x 2
 > C-x 3

Yes, this one is a bit nasty.

 > I did make some progress on this, but the problem was there was no way I know
 > to achive this, which makes it a bit harder:
 >
 > |-------------|      |-------------|
 > |      A      |      |      A      |
 > |-------------|      |-------------|
 > |       B     | ->   |  B   |      |
 > |-------------|      |------|   D  |
 > |      C      |      |   C  |      |
 > |-------------|      |-------------|
 >
 > I'm sure there is way to make it work without this, I'll probably come
 > up with way in shower.

Maybe you should have done

|-------------|      |-------------|
|      A      |      |      A      |
|-------------|      |-------------|
|      |      | ->   |   B  |      |
|  B   |   D  |      |------|   D  |
|      |      |      |   C  |      |
|-------------|      |-------------|

since you don't want to split parent windows.

I attach a file called window-rotate.el which handles the above
scenario.  In contrast with a window tree it simply operates on the old
window structure of the frame directly which I store as an association
list.  Note two aspects:

(1) It always tries to find a leftmost live window to start operating
on.  I think you do the same but I've never been able to understand how.

(2) It binds 'window-combination-limit' to make a parent window within
something like the first combination you sketched above so this becomes

|-------------|
|      A      |
|-------------|
| ----------- |
||     B     ||
||-----------||
||     C     ||
| ----------- |
|-------------|

Half of the code is debugging code that is not commented.  Note that the
code handles rotations only, rotates only the entire frame, does not set
sizes and probably misses other things.  But it seems to work so please
test it and maybe try to use as much of it as possible.

martin

[-- Attachment #2: window-rotate.el --]
[-- Type: text/x-emacs-lisp, Size: 8543 bytes --]

;;; -*- lexical-binding:t -*-

;; Debugging code.
(defvar window-number 0)
(defvar old-window-alist nil)
(defvar old-window-list nil)
(defvar new-window-alist nil)

(defun old-window-alist ()
  (setq old-window-list (window-list))
  (setq old-window-alist nil)
  (dolist (window old-window-list)
    (setq old-window-alist
	  (cons
	   (cons window (window-parent window))
	   old-window-alist)))

  (setq old-window-alist (nreverse old-window-alist)))

(defun new-window-alist ()
  (setq new-window-alist nil)
  (dolist (window old-window-list)
    (setq new-window-alist
	  (cons
	   (cons window (window-parent window))
	   new-window-alist)))

  (setq new-window-alist (nreverse new-window-alist)))

;; Two variables the code below binds lexically.
(defvar window-alist nil)
(defvar window-clockwise)

(defun window-first-window (window)
  "Return topmost leftmost live descendant of WINDOW."
  (let ((child (or (window-top-child window)
		   (window-left-child window))))
    (if child
	(window-first-window child)
      window)))

(defun window-alist ()
  "Return alist of all windows on selected frame."
  (let (alist)
    (walk-window-tree
     (lambda (window)
       (setq alist
	     (cons
	      (if (window-buffer window)
		  (list window
			(cons 'buffer (window-buffer window))
			(cons 'parent (window-parent window))
			(cons 'next (window-next-sibling window))
			(cons 'prev (window-prev-sibling window))
			(cons 'width (window-pixel-width window))
			(cons 'height (window-pixel-height window)))
		(list window
		      (cons 'left (window-left-child window))
		      (cons 'top (window-top-child window))
		      (cons 'parent (window-parent window))
		      (cons 'next (window-next-sibling window))
		      (cons 'prev (window-prev-sibling window))
		      (cons 'first (window-first-window window))
		      (cons 'width (window-pixel-width window))
		      (cons 'height (window-pixel-height window))))
	      alist)))
     nil t)

    alist))

;; Accessor functions.
(defun window-alist-parent (window)
  (cdr (assq 'parent (cdr (assq window window-alist)))))

(defun window-alist-buffer (window)
  (cdr (assq 'buffer (cdr (assq window window-alist)))))

(defun window-alist-next (window)
  (cdr (assq 'next (cdr (assq window window-alist)))))

(defun window-alist-prev (window)
  (cdr (assq 'prev (cdr (assq window window-alist)))))

(defun window-alist-left (window)
  (cdr (assq 'left (cdr (assq window window-alist)))))

(defun window-alist-top (window)
  (cdr (assq 'top (cdr (assq window window-alist)))))

(defun window-alist-first (window)
  (cdr (assq 'first (cdr (assq window window-alist)))))

(defun window-rotate-clockwise ()
  "Rotate root window of selected frame clockwise."
  (interactive)
  (let ((window-clockwise t)
	;; Construct 'window-alist' which also makes sure that no window
	;; gets recycled by GC.
	(window-alist (window-alist))
	(window (frame-first-window)))
    (setq window-number 0)
    (old-window-alist)
    ;; Delete all windows but the frame's first window.
    (delete-other-windows-internal window)
    (with-current-buffer (get-buffer-create "*foo*")
      (erase-buffer)
      (pp window-alist (current-buffer))
      (insert "\n"))
    ;; Now rotate starting with the frame's first window.
    (window-rotate window nil)
    (new-window-alist)
    (with-current-buffer (get-buffer-create "*foo*")
      (pp old-window-alist (current-buffer))
      (insert "\n")
      (pp new-window-alist (current-buffer)))))

(defun window-rotate-anti-clockwise ()
  "Rotate root window of selected frame anti-clockwise."
  (interactive)
  (let (window-clockwise
	;; Construct 'window-alist' which also makes sure that no window
	;; gets recycled by GC.
	(window-alist (window-alist))
	(window (frame-first-window)))
    (setq window-number 0)
    (old-window-alist)
    ;; Delete all windows but the frame's first window.
    (delete-other-windows-internal window)
    (with-current-buffer (get-buffer-create "*foo*")
      (erase-buffer)
      (pp window-alist (current-buffer))
      (insert "\n"))
    ;; Now rotate starting with the frame's first window.
    (window-rotate window nil)
    (new-window-alist)
    (with-current-buffer (get-buffer-create "*foo*")
      (pp old-window-alist (current-buffer))
      (insert "\n")
      (pp new-window-alist (current-buffer)))))

(defun window-rotate (window root)
  "Rotate window subroutine.
WINDOW must be a leftmost bottommost child window such as produced by
`frame-first-window' or `window-first-window'.  More precisely, WINDOW
must show a buffer and its previous sibling must be nil.

ROOT is either the root window to rotate or a parent window such that
WINDOW is its `frame-first-window'.

Traverse the window structure saved in `window-alist' starting with
WINDOW, continuing with its next siblings.  If a sibling is a parent
window, recurse with WINDOW set to the window returned by
`window-first-window' for that sibling and ROOT set to sibling's parent.
After having traced all siblings continue with WINDOW's parent unless
that parent equals ROOT."
  (let ((parent (window-alist-parent window))
	next first side)
    (when parent
      ;; Traverse WINDOW (which is always the first sibling) and its
      ;; next siblings.
      (while (setq next (window-alist-next window))
	;; Calculate SIDE argument for 'split-window' taking into
	;; account 'window-clockwise' and whether WINDOW is in a
	;; horizontal of vertical combination.
	(setq side (if window-clockwise
		       (if (window-alist-left parent) 'below 'left)
		     (if (window-alist-left parent) 'above 'right)))
	;; Save current window configuration in case of an error.
	(let ((config (current-window-configuration)))
	  (if (window-alist-buffer next)
	      ;; If the next sibling shows a buffer, split it with
	      ;; WINDOW's old parent as new parent.
	      (condition-case var
		  (let* ((prev (window-alist-prev window))
			 ;; Set 'window-combination-limit' to make sure
			 ;; that a new parent is created when WINDOW's
			 ;; previous sibling and WINDOW do not have the
			 ;; same parent.
			 (window-combination-limit
			  (or (not prev)
			      (and (not (eq (window-alist-parent prev)
					    (window-alist-parent window)))
				   t))))
		    (with-current-buffer (get-buffer-create "*foo*")
		      (insert (format "1 .. w %s .. n %s .. p %s\n" window next parent)))
		    ;; Split WINDOW with REFER set to a cons of WINDOW's
		    ;; saved next sibling and WINDOW's saved parent.
		    (split-window window nil side nil (cons next parent)))
		(error
		 (with-current-buffer (get-buffer-create "*foo*")
		   (pp window-alist (current-buffer))
		   (insert "\n")
		   (insert (format "1 .. w %s .. s %s .. r %s\n" window side (cons next parent)))
		   (insert (format "%s\n" var)))
		 (set-window-configuration config)))
	    ;; If the next sibling is a parent window set FIRST to its
	    ;; topmost leftmost live descendant.
	    (setq first (window-alist-first next))
	    (condition-case var
		(let* ((prev (window-alist-prev window))
		       ;; Set 'window-combination-limit' to make sure
		       ;; that a new parent is created when WINDOW's
		       ;; previous sibling and WINDOW do not have the
		       ;; same parent.
		       (window-combination-limit
			(or (not prev)
			    (and (not (eq (window-alist-parent prev)
					  (window-alist-parent window)))
				 t))))
		  (with-current-buffer (get-buffer-create "*foo*")
		    (insert (format "2 .. w %s .. f %s .. p %s\n" window first parent)))
		  ;; Split WINDOW with REFER set to a cons of WINDOW's
		  ;; first live descendant and WINDOW's saved parent.
		  (split-window window nil side nil (cons first parent)))
	      (error
	       (with-current-buffer (get-buffer-create "*foo*")
		 (pp window-alist (current-buffer))
		 (insert "\n")
		 (insert (format "2 .. w %s .. s %s .. r %s\n" window side (cons next parent)))
		 (insert (format "%s\n" var)))
	       (set-window-configuration config)))
	    ;; Recurse starting with WINDOW's first live descendant.
	    ;; The second argument PARENT should terminate the recursion
	    ;; when going up again.
	    (window-rotate first parent)))
	;; Continue with WINDOW's next sibling.
	(setq window next))
      ;; Continue with WINDOW's parent unless we are at the ROOT of the
      ;; current recursion.
      (unless (eq (window-alist-parent window) root)
	(window-rotate (window-alist-parent window) root)))))

(global-set-key [(super p)] 'window-rotate-clockwise)
(global-set-key [(super q)] 'window-rotate-anti-clockwise)

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

* Re: Add function to rotate/transpose all windows
  2024-10-24 18:39                                                                   ` martin rudalics
@ 2024-10-25 14:24                                                                     ` Pranshu Sharma
  2024-10-25 17:09                                                                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-10-25 14:24 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

>
> since you don't want to split parent windows.

I decided to do this, since I also don't want my brain to start bleeding

> I attach a file called window-rotate.el which handles the above
> scenario.  In contrast with a window tree it simply operates on the old
> window structure of the frame directly which I store as an association
> list.  Note two aspects:
>
> (1) It always tries to find a leftmost live window to start operating
> on.  I think you do the same but I've never been able to understand how.

From looking at the code, same reason as you. The leftmost top window
is first window in a flattened window tree, then the recurisve descent
started from there.

The new function also does ame, but perfoms recursive ascent asc

> (2) It binds 'window-combination-limit' to make a parent window within
> something like the first combination you sketched above so this becomes
>
> |-------------|
> |      A      |
> |-------------|
> | ----------- |
> ||     B     ||
> ||-----------||
> ||     C     ||
> | ----------- |
> |-------------|

Wow! this is best thing since flush toilets, I was able to make the
function work with this.

Also, I plan on working on a cycle-windows function which will basiclly
change window-tree so that that the layout isn't changed, but the
windows are thing is.  This will be able to be properly done using
window--transpose-1, instead of window swap state which you said didn't
work.

> Half of the code is debugging code that is not commented.  Note that the
> code handles rotations only, rotates only the entire frame, does not set
> sizes and probably misses other things.  But it seems to work so please
> test it and maybe try to use as much of it as possible.

I looked at it, and could not really understnad it fully due to some low
level stuff, but I got the idea and incoroperated some logic of
windows-rotate.

It seems like window-configurations are kind of being reinvented except
in elisp, which is good.

The function I made, I feel like my approach is higher level then yours
cause I can't quite fully wrap my head around window management in
emacs.  So a lot of stuff, I don't know you can do.  But on other hand,
I feel the logic is also quite simpler, you can judge that.


[-- Attachment #2: window-transpose.el --]
[-- Type: application/emacs-lisp, Size: 9149 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-10-25 14:24                                                                     ` Pranshu Sharma
@ 2024-10-25 17:09                                                                       ` martin rudalics
  2024-10-26  9:14                                                                         ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-25 17:09 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 >> (2) It binds 'window-combination-limit' to make a parent window within
 >> something like the first combination you sketched above so this becomes
[...]
 > Wow! this is best thing since flush toilets, I was able to make the
 > function work with this.

I don't see it in the code you sent, though.  Also that code has one bug
and produces a couple of warnings:

In rotate-windows-anticlockwise:
window-transpose.el:59:48: Warning: Unused lexical argument ‘subtree’

In rotate-windows-clockwise:
window-transpose.el:84:44: Warning: Unused lexical argument ‘subtree’

In flip-windows-horizontally:
window-transpose.el:109:45: Warning: Unused lexical argument ‘subtree’

In flip-windows-vertically:
window-transpose.el:128:43: Warning: Unused lexical argument ‘subtree’

In transpose-windows:
window-transpose.el:146:37: Warning: Unused lexical argument ‘subtree’

In window--transpose-1:
window-transpose.el:209:28: Warning: reference to free variable ‘fwin’

The last one keeps it from running.

martin

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

* Re: Add function to rotate/transpose all windows
  2024-10-25 17:09                                                                       ` martin rudalics
@ 2024-10-26  9:14                                                                         ` Pranshu Sharma
  2024-10-27  8:23                                                                           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-10-26  9:14 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

> I don't see it in the code you sent, though.

Look in window--transpose

> In rotate-windows-anticlockwise:
> window-transpose.el:59:48: Warning: Unused lexical argument ‘subtree’

Oh, I just realised these ones also existed in before code

> In window--transpose-1:
> window-transpose.el:209:28: Warning: reference to free variable ‘fwin’
>
> The last one keeps it from running.

This one happend becuase you turned on lexical scoping.

I changed the code so it works with lexical

Also the
C-x 3
C-x 2
C-x 3
split has some size messups (still works tho), that will need fixing


[-- Attachment #2: window-transpose.el --]
[-- Type: application/emacs-lisp, Size: 9066 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-10-26  9:14                                                                         ` Pranshu Sharma
@ 2024-10-27  8:23                                                                           ` martin rudalics
  2024-11-02 14:06                                                                             ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-10-27  8:23 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > Look in window--transpose

I see it now.  You should not bind 'window-combination-limit' there.
Always bind it around a single 'split-window' call only.  If you bind it
for all 'split-window' operations run by transposing, you get a binary
window tree which is certainly not what a user wants.

When with emacs -Q I set 'debug-on-error' t, do C-x 2 C-x 2 and then
call 'rotate-windows-clockwise' twice I get

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p nil)
   -(916.0 nil)
   (if (eq o-split split-type) (- fflen o-size) fflen)
   (let* ((split-type (funcall (if (car subtree) 'car 'cdr) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'(lambda (parent-win arg0) (progn (ignore ...) (let* ... ...))) (let ((ls (mapcar ... ...))) (seq-mapn 'cons (mapcar 'car ls) (cons nil (mapcar ... ls)))) cwin))) (progn (let ((upper-bound (- (length subtree) 3)) (counter 0)) (while (< counter upper-bound) (let ((_ counter)) (setq deepest-window (window-parent deepest-window))) (setq counter (1+ counter)))) deepest-window))
   (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall (if (car subtree) 'car 'cdr) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'(lambda (parent-win arg0) (progn ... ...)) (let ((ls ...)) (seq-mapn 'cons (mapcar ... ls) (cons nil ...))) cwin))) (progn (let ((upper-bound (- (length subtree) 3)) (counter 0)) (while (< counter upper-bound) (let ((_ counter)) (setq deepest-window (window-parent deepest-window))) (setq counter (1+ counter)))) deepest-window)))
   (let* ((x0 (car-safe val)) (x1 (cdr-safe val))) (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall (if (car subtree) 'car 'cdr) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'(lambda ... ...) (let (...) (seq-mapn ... ... ...)) cwin))) (progn (let ((upper-bound (- ... 3)) (counter 0)) (while (< counter upper-bound) (let (...) (setq deepest-window ...)) (setq counter (1+ counter)))) deepest-window))))
   (progn (ignore (consp val)) (let* ((x0 (car-safe val)) (x1 (cdr-safe val))) (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall (if ... ... ...) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'... (let ... ...) cwin))) (progn (let ((upper-bound ...) (counter 0)) (while (< counter upper-bound) (let ... ...) (setq counter ...))) deepest-window)))))
   (let* ((val (if (car subtree) (cons (float (car (car ...))) (float (window-pixel-width cwin))) (cons (float (cdr (car ...))) (float (window-pixel-height cwin)))))) (progn (ignore (consp val)) (let* ((x0 (car-safe val)) (x1 (cdr-safe val))) (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall ... conf)) (flen (if ... ... fflen)) (deepest-window (seq-reduce ... ... cwin))) (progn (let (... ...) (while ... ... ...)) deepest-window))))))
   window--transpose-1((nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405))) #<window 7 on *scratch*> (left . below) nil #<window 7 on *scratch*> nil below)
   (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type))
   (if (listp win) (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and (not size) o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win)))
   (let ((split-size (and size (if do-not-convert-size size (round (* flen (/ size ilen))))))) (if (listp win) (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and (not size) o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win))))
   (if (eq fwin win) parent-win (let ((split-size (and size (if do-not-convert-size size (round (* flen ...)))))) (if (listp win) (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and (not size) o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win)))))
   (let ((win x2) (size x3)) (if (eq fwin win) parent-win (let ((split-size (and size (if do-not-convert-size size (round ...))))) (if (listp win) (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and (not size) o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win))))))
   (let* ((x2 (car-safe arg0)) (x3 (cdr-safe arg0))) (let ((win x2) (size x3)) (if (eq fwin win) parent-win (let ((split-size (and size (if do-not-convert-size size ...)))) (if (listp win) (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and (not size) o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win)))))))
   (progn (ignore (consp arg0)) (let* ((x2 (car-safe arg0)) (x3 (cdr-safe arg0))) (let ((win x2) (size x3)) (if (eq fwin win) parent-win (let ((split-size (and size ...))) (if (listp win) (if (and ... o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and ... o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win))))))))
   #f(lambda (parent-win arg0) [(flen 916.0) (split-type below) (ilen 1680.0) (o-split nil) (o-size nil) (fwin #<window 7 on *scratch*>) (do-not-convert-size nil) (conf (left . below))] (progn (ignore (consp arg0)) (let* ((x2 (car-safe arg0)) (x3 (cdr-safe arg0))) (let ((win x2) (size x3)) (if (eq fwin win) parent-win (let ((split-size (and size (if do-not-convert-size size (round (* flen (/ size ilen))))))) (if (listp win) (if (and (not size) o-size) (window--transpose-1 win parent-win conf do-not-convert-size fwin o-size o-split) (window--transpose-1 win parent-win conf do-not-convert-size fwin split-size split-type)) (if (and (not size) o-size) (split-window parent-win o-size o-split t win) (split-window parent-win split-size split-type t win)))))))))(#<window 7 on *scratch*> ((nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405)))))
   #f(compiled-function (elt) #<bytecode 0x1f3b072de5930c3f>)(((nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405)))))
   mapc(#f(compiled-function (elt) #<bytecode 0x1f3b072de5930c3f>) (((nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405)))) (#<window 3> . 1248)))
   seq-do(#f(compiled-function (elt) #<bytecode 0x1f3b072de5930c3f>) (((nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405)))) (#<window 3> . 1248)))
   seq-reduce(#f(lambda (parent-win arg0) [(flen 916.0) (split-type below) (ilen 1680.0) (o-split nil) (o-size nil) (fwin #<window 7 on *scratch*>) (do-not-convert-size nil) (conf (left . below))] (progn (ignore (consp arg0)) (let* ((x2 (car-safe arg0)) (x3 (cdr-safe arg0))) (let ((win x2) (size x3)) (if (eq fwin win) parent-win (let (...) (if ... ... ...))))))) (((nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405)))) (#<window 3> . 1248)) #<window 7 on *scratch*>)
   (let* ((split-type (funcall (if (car subtree) 'car 'cdr) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'(lambda (parent-win arg0) (progn (ignore ...) (let* ... ...))) (let ((ls (mapcar ... ...))) (seq-mapn 'cons (mapcar 'car ls) (cons nil (mapcar ... ls)))) cwin))) (progn (let ((upper-bound (- (length subtree) 3)) (counter 0)) (while (< counter upper-bound) (let ((_ counter)) (setq deepest-window (window-parent deepest-window))) (setq counter (1+ counter)))) deepest-window))
   (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall (if (car subtree) 'car 'cdr) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'(lambda (parent-win arg0) (progn ... ...)) (let ((ls ...)) (seq-mapn 'cons (mapcar ... ls) (cons nil ...))) cwin))) (progn (let ((upper-bound (- (length subtree) 3)) (counter 0)) (while (< counter upper-bound) (let ((_ counter)) (setq deepest-window (window-parent deepest-window))) (setq counter (1+ counter)))) deepest-window)))
   (let* ((x0 (car-safe val)) (x1 (cdr-safe val))) (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall (if (car subtree) 'car 'cdr) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'(lambda ... ...) (let (...) (seq-mapn ... ... ...)) cwin))) (progn (let ((upper-bound (- ... 3)) (counter 0)) (while (< counter upper-bound) (let (...) (setq deepest-window ...)) (setq counter (1+ counter)))) deepest-window))))
   (progn (ignore (consp val)) (let* ((x0 (car-safe val)) (x1 (cdr-safe val))) (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall (if ... ... ...) conf)) (flen (if (eq o-split split-type) (- fflen o-size) fflen)) (deepest-window (seq-reduce #'... (let ... ...) cwin))) (progn (let ((upper-bound ...) (counter 0)) (while (< counter upper-bound) (let ... ...) (setq counter ...))) deepest-window)))))
   (let* ((val (if (car subtree) (cons (float (car (car ...))) (float (window-pixel-width cwin))) (cons (float (cdr (car ...))) (float (window-pixel-height cwin)))))) (progn (ignore (consp val)) (let* ((x0 (car-safe val)) (x1 (cdr-safe val))) (let ((ilen x0) (fflen x1)) (let* ((split-type (funcall ... conf)) (flen (if ... ... fflen)) (deepest-window (seq-reduce ... ... cwin))) (progn (let (... ...) (while ... ... ...)) deepest-window))))))
   window--transpose-1((nil (916 . 1680) (nil (916 . 1248) (#<window 7 on *scratch*> (916 . 843)) (#<window 8> (916 . 405))) (#<window 3> (916 . 432))) #<window 7 on *scratch*> (left . below) nil #<window 7 on *scratch*> nil)
   (let* ((frame (window-frame window)) (fwin window) (selwin (frame-selected-window window)) (window-combination-limit t) (win-tree (car (window-tree-pixel-sizes window)))) (while (not (window-live-p fwin)) (setq fwin (window-child fwin))) (delete-other-windows-internal fwin) (window--transpose-1 win-tree fwin conf do-not-convert-size fwin nil) (set-frame-selected-window frame selwin))
   (if (or (not window) (window-live-p window)) (message "No windows to transpose") (let* ((frame (window-frame window)) (fwin window) (selwin (frame-selected-window window)) (window-combination-limit t) (win-tree (car (window-tree-pixel-sizes window)))) (while (not (window-live-p fwin)) (setq fwin (window-child fwin))) (delete-other-windows-internal fwin) (window--transpose-1 win-tree fwin conf do-not-convert-size fwin nil) (set-frame-selected-window frame selwin)))
   window--transpose(#<window 6> (left . below) nil)
   (let ((window (cond (subtree (window-parent)) ((windowp frame-or-window) frame-or-window) (t (window-main-window frame-or-window))))) (old-window-alist) (window--transpose window '(left . below) nil) (new-window-alist) (save-current-buffer (set-buffer (get-buffer-create "*foo*")) (pp old-window-alist (current-buffer)) (insert "\n") (pp new-window-alist (current-buffer))))
   rotate-windows-clockwise(nil)
   funcall-interactively(rotate-windows-clockwise nil)
   call-interactively(rotate-windows-clockwise nil nil)
   command-execute(rotate-windows-clockwise)

I have no idea whether it's related.  For me that backtrace is
impossible to trace.  Please have a look.  Once it works, I'll try to
come up with an example why what you do is wrong.

Note the following paragraph in the Elisp manual on
'window-combination-limit'.

      If, as a consequence of this variable’s setting, ‘split-window’
      makes a new parent window, it also calls
      ‘set-window-combination-limit’ (see below) on the newly-created
      internal window.  This affects how the window tree is rearranged
      when the child windows are deleted (see below).

So when you bind 'window-combination-limit' globally, deleting windows
after transposing may behave differently from before transposing them.

The correct approach is to save the value of 'window-combination-limit'
for a parent window before splitting, split the window, and then restore
the previous value of the parent.

The problem, however, is also with finding the right parent window and
I'm afraid your algorithm doesn't get that right yet.  IIUC when finding
a next sibling as the new window in a split and that sibling is an
internal window, you take that window's first live descendant - let's
call it FIRST - and pass it to 'split-window' as REFER argument.  Right?
But 'split-window' has no idea of FIRST's former parent and, when it now
needs a parent because of 'window-combination-limit' being non-nil, it
will not reuse the previous parent of FIRST (some internal window down
the old window tree) but simply make a new one.

Note that if you had not set 'window-combination-limit', 'split-window'
would have set the parent to the parent of the window to split, which is
equally wrong as you probably noticed earlier.  So what you have to do
here is to pass a cons as REFER to 'split-window' with the car set to
FIRST and the cdr set to the parent of FIRST it had in the window tree
_before_ you deleted the other windows.  This will instruct
'split-window' to reinstall that old parent in the window tree and
things should work out as intended.

Note in this context that preserving the identities of live windows
across transpositions via the REFER argument is important to keep all
those things like window positions, margins, scroll bars, fringes,
overlays with a window property, dedicated status and parameters like
'no-other-window' or 'quit-restore' in place.  Without the REFER
argument, you would have to painstakingly restore all these things
manually for each new live window from the old one it substitutes.

Preserving the identity of internal windows on the other hand is
important to keep their combination limits and any parameters stored
for them.  Even here, it's much easier to reuse the old parent windows
instead of saving all these properties before deleting windows and
trying to find the identity of the parent windows that substituted the
old ones and store the appropriate properties in the new windows.

martin

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

* Re: Add function to rotate/transpose all windows
  2024-10-27  8:23                                                                           ` martin rudalics
@ 2024-11-02 14:06                                                                             ` Pranshu Sharma
  2024-11-05 18:01                                                                               ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-02 14:06 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> I have no idea whether it's related.  For me that backtrace is
> impossible to trace.  Please have a look.  Once it works, I'll try to
> come up with an example why what you do is wrong.

I totally understood it, then I saw it

> Note the following paragraph in the Elisp manual on
> 'window-combination-limit'.
>
>      If, as a consequence of this variable’s setting, ‘split-window’
>      makes a new parent window, it also calls
>      ‘set-window-combination-limit’ (see below) on the newly-created
>      internal window.  This affects how the window tree is rearranged
>      when the child windows are deleted (see below).
>
> So when you bind 'window-combination-limit' globally, deleting windows
> after transposing may behave differently from before transposing them.
>
> The correct approach is to save the value of 'window-combination-limit'
> for a parent window before splitting, split the window, and then restore
> the previous value of the parent.

I get what you meant, I made a new version of the program that fixes
this problem, but it doesn't work for too deep splits yet

> The problem, however, is also with finding the right parent window and
> I'm afraid your algorithm doesn't get that right yet.  IIUC when finding
> a next sibling as the new window in a split and that sibling is an
> internal window, you take that window's first live descendant - let's
> call it FIRST - and pass it to 'split-window' as REFER argument.
> Right?

This is the top-down way, where say you have the tree
(a (b (c d) e))
You go like
(a) -> (a (b)) -> (a (b c)) -> (a (b (c d)))...
the way this one works is bottom up, kind of like:
(a) -> (a (c d)) -> (a (b (c d)))...

But this is not the best representation for windows at all

> But 'split-window' has no idea of FIRST's former parent and, when it now
> needs a parent because of 'window-combination-limit' being non-nil, it
> will not reuse the previous parent of FIRST (some internal window down
> the old window tree) but simply make a new one.
>
> Note that if you had not set 'window-combination-limit', 'split-window'
> would have set the parent to the parent of the window to split, which is
> equally wrong as you probably noticed earlier.  So what you have to do
> here is to pass a cons as REFER to 'split-window' with the car set to
> FIRST and the cdr set to the parent of FIRST it had in the window tree
> _before_ you deleted the other windows.  This will instruct
> 'split-window' to reinstall that old parent in the window tree and
> things should work out as intended.
>
> Note in this context that preserving the identities of live windows
> across transpositions via the REFER argument is important to keep all
> those things like window positions, margins, scroll bars, fringes,
> overlays with a window property, dedicated status and parameters like
> 'no-other-window' or 'quit-restore' in place.  Without the REFER
> argument, you would have to painstakingly restore all these things
> manually for each new live window from the old one it substitutes.

I think I might have sent you code without refer argument, if that was
the case I was just working on code in a non patch applied emacs, so the
refer argument would work if you just ad `win'

The problem with my curent solution is that it does not account for
emacs split's properly, for example if you split the parent of a and b:

|--------|     |---------------|
| A | B  | ->  | A | B  |  C   |
|--------|     |---------------|

this means it squashes the proportions to match C, which eff's up the
inital split of A and B, which was designed to match the transpsoed from
the inital tree, messing up the current model where I don't pass down
the size information directly, rather pass the full width/height of the
current split(kind of like ilen), so it can be converted

If you didn't understand any of that, don't worry, I fully don't
understand also

Also sorry for late replies, I am kind of in weekend warrior mode on
this until wednesday, been focusing on trying to get my rap career
started

------------------------

(defun window--transpose-1 (subtree cwin conf do-not-convert-size fwin root
									&optional fflen root-split-type)
  "Subroutine of `window--transpose'.
SUBTREE must be in the format of the result of
`window-tree-pixel-sizes'.  CWIN is the current window through which the
window splits are made.  The CONF and DO-NOT-CONVERT-SIZE arguments are
the same as the ones in `window--transpose'."
  ;; `ilen' is the max size a window could be of given the split type.
  ;; `flen' is max size the window could be converted to the opposite
  ;; of the given split type.
  (pcase-let* ((`(,ilen ,oflen)
				(funcall (if (car subtree) 'identity 'reverse)
						 (mapcar 'float
								 (flatten-list (cadr subtree)))))
			   (split-type (if (car subtree)
							   (car conf)
							 (cdr conf)))
			   (flen (or fflen oflen))
			   (nfsize ;; (and fflen (* oflen (/ ilen fflen)))
				(if (eq split-type root-split-type)
					flen
				  (* oflen (/ ilen flen)))))
	(cons
	 (window-parent
	  (cadr
	   (seq-reduce
		(pcase-lambda (`(,count ,mwin ,lsize) `(,win . ,size))
		  ;; Better than (/ (* flen size) ilen) as (* flen size) can make
		  ;; really big numbers
		  (cons
		   (+ 1 count)
		   (let ((new-size (and size (if do-not-convert-size
										 size
									   (round (* flen (/ size ilen)))))))
			 (if (eq fwin win)
				 (list mwin (- lsize new-size))
			   (if (listp win)
				   (let ((win-n-size
						  (window--transpose-1
						   win
						   mwin
						   conf
						   do-not-convert-size
						   fwin
						   nil
						   ;; nfsize
						   (if (eq split-type root-split-type)
							   oflen
							   nfsize)
						   (if (= count 0)
							   root-split-type
							 split-type))))
					 (list (car win-n-size)
						   (- lsize new-size)))
				 (if (eq count 0)
					 (list (split-window mwin (- nfsize)
										 root-split-type t win)
						   (- lsize new-size))
				   (let ((window-combination-limit
						  (and (= count 1) (eq split-type root-split-type))))
					 (list (split-window mwin (- lsize) split-type t win)
						   (- lsize new-size)))))))))
		(mapcar
		 (lambda (e)
		   (let ((window? (if (windowp (car e)) (car e) e)))
			 (cons window?
				   ;; The respective size of the window.
				   (if (car subtree)
					   (car (cadr e))
					 (cdr (cadr e))))))
		 (cddr subtree))
		;; This is the starting point.
		(list 0 cwin ;; (if do-not-convert-size ilen flen)
			  (or flen nfsize)
			  ))))
	 nfsize))
  )



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

* Re: Add function to rotate/transpose all windows
  2024-11-02 14:06                                                                             ` Pranshu Sharma
@ 2024-11-05 18:01                                                                               ` martin rudalics
  2024-11-08  9:23                                                                                 ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-05 18:01 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

 > This is the top-down way, where say you have the tree
 > (a (b (c d) e))
 > You go like
 > (a) -> (a (b)) -> (a (b c)) -> (a (b (c d)))...
 > the way this one works is bottom up, kind of like:
 > (a) -> (a (c d)) -> (a (b (c d)))...

I don't see that here - I assume that you made the configuration via
something like

C-x 3 C-x o C-x 2 C-x 2 C-x o C-x 3

or its orthogonal variant.  With your approach I do not see (a (c d))
after the second split but (a (b c)) just like with mine.  AFAICT your
algorithm performs exactly the same sequence of splits as mine.  You can
verify my claim by putting a 'y-or-n-p' after each 'split-window' call
(in the version I attached they are commented out in the code), making
two frames on your screen and stepping through your version in one and
mine in the other window.  You will see that they split windows in
exactly the same order.

I also noted that with

C-x 3 C-x o C-x 2 C-x 3 C-x o C-x 2

my and your last 'split-window' call split a parent window - something
you earlier told me that you didn't want to do.

The following is the central idea of my approach.  Suppose F is a first
live window (initially the 'frame-first-window') - a live window which
has no previous sibling in the window tree.  And suppose P is the parent
of F in the window tree that has already been deleted, all relationships
are from the tree before it has been deleted.  'delete-other-windows'
now deletes all windows but F.

   P
  /
F   P1    P2 ... Pn
    /     /      /
   /     /      /
  F1    F2     Fn

Now I take the next sibling P1 of F - I assume it's a parent window
because that's the hard case.  I find the first live descendant of P1
which is F1 (any parent window invariably has such a descendant).  My
first split is now

(split-window F .. (F1 . P))

because I see that F has not yet its valid parent P.  This resurrects F1
and makes F and F1 the children of the resurrected P.  If F already had
its valid parent P, I would have called

(split-window F .. F1)

and P would have got the additional child F1.  Now I recurse with F1
taking over the part of F.  After I'm done with the entire subtree of
P1, I do

(split-window P1 .. F2)

so P gets a new child window F2 and continue with F2 taking over the
part of F1 and F.  After I'm done with the subtree of Pn, I continue
with P's next sibling and go up until I arrive at the root of the window
tree.

Note here: Initially F's parent is nil.  When I split F to resurrect F1
both get the new parent P.  P is already the final parent of F but not
of F1.  When I later split F1 it will get its final parent as well.  In
neither of these splits, 'split-window' has any idea which parent window
to use.  It could try to find one among the dead windows the one whose
first child is F (or F1) but I haven't implemented such a mechanism and
I'm not sure whether it's worth the hassle.  That's why I have the
rotation routine initially remember the old parents of all windows and
pass them to 'split-window' as cdr of the REFER argument.

Note also that I always apply the new size to the window I split.  This
means that when I split F then F1 initially gets the entire remainder of
the size of P.  It also means that when I later split P1, 'split-window'
has to recursively resize the entire tree rooted at P1 and F2 gets the
rest of the size of P - that of P minus that of F and P1.

It might be interesting to try the following variant: Instead of
recursing immediately into F1, first split all siblings of F so that one
has a layout of

F  F1  F2  ... Fn

and only now recursively process F1, F2 ... Fn.  This would have the
advantage that when splitting F1 to fit in the entire subtree rooted at
P1, its size is already the final size.  And we might not have to split
parent windows either.  But such a solution will be less trivial to
code.

Attached find my latest diffs (the documentation has not been completed
yet) and window-rotate.el.

martin

[-- Attachment #2: split-window.diff --]
[-- Type: text/x-patch, Size: 37781 bytes --]

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 0b8d7d3b76d..e67fc256b3d 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -22,6 +22,7 @@ Windows
 * Deleting Windows::        Removing a window from its frame.
 * Recombining Windows::     Preserving the frame layout when splitting and
                               deleting windows.
+* Resurrecting deleted windows:: Restoring indivdual windows.
 * Cyclic Window Ordering::  Moving around the existing windows.
 * Buffers and Windows::     Each window displays the contents of a buffer.
 * Switching Buffers::       Higher-level functions for switching to a buffer.
@@ -1355,7 +1356,7 @@ Splitting Windows
 Examples of such windows are side windows (@pxref{Side Windows}) and
 atomic windows (@pxref{Atomic Windows}).
 
-@defun split-window &optional window size side pixelwise
+@defun split-window &optional window size side pixelwise refer
 This function creates a new live window next to the window
 @var{window}.  If @var{window} is omitted or @code{nil}, it defaults
 to the selected window.  That window is split, and reduced in
@@ -1364,7 +1365,7 @@ Splitting Windows
 The optional second argument @var{size} determines the sizes of
 @var{window} and/or the new window.  If it is omitted or @code{nil},
 both windows are given equal sizes; if there is an odd line, it is
-allocated to the new window.  If @var{size} is a positive number,
+allotted to the new window.  If @var{size} is a positive number,
 @var{window} is given @var{size} lines (or columns, depending on the
 value of @var{side}).  If @var{size} is a negative number, the new
 window is given @minus{}@var{size} lines (or columns).
@@ -1374,13 +1375,13 @@ Splitting Windows
 Sizes}).  Thus, it signals an error if splitting would result in making
 a window smaller than those variables specify.  However, a
 non-@code{nil} value for @var{size} causes those variables to be
-ignored; in that case, the smallest allowable window is considered to be
-one that has space for a text that is one line tall and/or two columns
-wide.
+ignored; in that case, the smallest allowable sizes are determined by
+the values of @code{window-safe-min-height} and
+@code{window-safe-min-width}.
 
 Hence, if @var{size} is specified, it's the caller's responsibility to
-check whether the emanating windows are large enough to encompass all of
-their decorations like a mode line or a scroll bar.  The function
+check whether the emanating windows are large enough to encompass all
+areas like a mode line or a scroll bar.  The function
 @code{window-min-size} (@pxref{Window Sizes}) can be used to determine
 the minimum requirements of @var{window} in this regard.  Since the new
 window usually inherits areas like the mode line or the scroll bar from
@@ -1399,14 +1400,62 @@ Splitting Windows
 window is placed on the left of @var{window}.  In both these cases,
 @var{size} specifies a total window width, in columns.
 
+As a rule, if @var{window} already forms a combination (@pxref{Windows
+and Frames}) that matches @var{side} (a horizontal combination matches
+@var{side} if it is @code{left} or @code{right}, a vertical combination
+matches @var{side} if it is @code{above} or @code{below}) and
+@code{window-combination-limit} (@pxref{Recombining Windows}) is
+@code{nil}, this function reuses @var{window}'s parent in the window
+tree as parent of the new window.
+
+However, if @var{window} is in a combination that does not match
+@var{side} or if @code{window-combination-limit} is non-@code{nil}, this
+function makes a new parent window that replaces @var{window} in the
+window tree and makes @var{window} and the new window its sole child
+windows.  This standard behavior can be overridden via the @var{refer}
+argument.
+
 The optional fourth argument @var{pixelwise}, if non-@code{nil}, means
 to interpret @var{size} in units of pixels, instead of lines and
 columns.
 
-If @var{window} is a live window, the new window inherits various
-properties from it, including margins and scroll bars.  If
-@var{window} is an internal window, the new window inherits the
-properties of the window selected within @var{window}'s frame.
+If the optional fifth argument @var{refer} is non-@code{nil}, it
+specifies a reference window used for setting up properties of the new
+window.  If non-@code{nil}, @var{refer} can be either a window or a cons
+cell of two windows.
+
+If @var{refer} is a cons cell, its @sc{car} has to specify a deleted,
+former live window - a window that has shown a buffer before - on the
+same frame as @var{window}.  That buffer must be still live.  The
+@sc{cdr} has to specify a deleted window that was, before its deletion,
+a parent window on the same frame as @var{window}.  In this case, rather
+then making new windows, this function replaces @var{window} with the
+@sc{cdr} of @var{refer} in the window tree and makes @var{window} and
+@var{refer}'s @sc{car} its new child windows.  Buffer, start and point
+positions of @var{refer}'s @sc{car} are set to the values they had
+immediately before @var{refer}'s @sc{car} was deleted the last time.
+Decorations and parameters remain unaltered from their values before
+@var{refer}'s @sc{car} and @sc{cdr} were deleted.
+
+Alternatively, @var{refer} may specify a deleted, former live window - a
+window that has shown a buffer before - on the same frame as
+@var{window}.  That buffer must be still live.  In this case, this
+function do not make a new window but rather makes @var{refer} live
+again and inserts it into the window tree at the position and with the
+sizes the new window would have been given.  Buffer, start and point
+positions of @var{refer} are set to the values they had immediately
+before @var{refer} was deleted the last time.  Decorations and
+parameters remain unaltered from their values before @var{refer} was
+deleted.  The parent of @var{refer} is then determined as if it were a
+window created anew.
+
+Otherwise, @var{refer} must specify a live window.  In this case, the
+new window will inherit properties like buffer, start and point
+positions and some decorations from @var{refer}.  If @var{refer} is
+@code{nil} or omitted, then if @var{window} is live, any such properties
+are inherited from @var{window}.  If, however, @var{window} is an
+internal window, the new window will inherit these properties from the
+window selected on @var{window}'s frame.
 
 The behavior of this function may be altered by the window parameters
 of @var{window}, so long as the variable
@@ -2048,6 +2097,157 @@ Recombining Windows
 windows.
 
 
+@node Resurrecting deleted windows
+@section Resurrecting deleted windows
+@cindex resurrecting deleted windows
+
+After a window has been deleted (@pxref{Deleting Windows}) it cannot be
+used any more by functions that require a valid window as their argument
+even if some Lisp variable still references that window.  When the last
+reference to a window has ceased to exist, the window's Lisp object will
+be eventually recycled by the garbage collector.
+
+There are two ways to resurrect a deleted window whose object has not
+been yet recycled by the collector: The first is to keep a reference to
+that window in a saved window configuration (@pxref{Window
+Configurations}) and then call @code{set-window-configuration} with that
+configuration as argument.  The second one is to keep a reference to
+that window in a variable or let-bind it and then use that reference as
+@var{refer} argument in @code{split-window} (@pxref{Splitting Windows}).
+
+The major difference between these two is that
+@code{set-window-configuration} restores the frame layout that existed
+before deleting the window.  The @code{split-window} approach, on the
+other hand, allows for arbitrary variations of the layout.
+
+Consider the following example starting with a frame containing a single
+window showing the buffer @file{*scratch*}:
+
+@example
+@group
+(let* ((old (selected-window))
+       (new (split-window old nil 'right))
+       overlay)
+  (with-current-buffer (get-buffer-create "*Messages*")
+    (set-window-buffer new (current-buffer))
+    (setq overlay (make-overlay (point-min) (point-max)))
+    (overlay-put overlay 'face 'highlight)
+    (overlay-put overlay 'window new)
+    (message "new %s parent %s" new (window-parent new))
+    (sit-for 3))
+  (delete-window new)
+  (setq new (split-window old nil 'left))
+  (set-window-buffer new (get-buffer-create "*Messages*"))
+  (format "new %s parent %s" new (window-parent new)))
+@end group
+@end example
+
+When you run that code in @file{*scratch*} it will first split the
+window showing @file{*scratch*} to display @file{*Messages*} in a new
+window on the right.  It also sets up an overlay with a window property
+to highlight the text of @file{*Messages*} in the new window and
+displays a message showing the new window and its parent in the window
+tree.  It then deletes the new window and resurrects it on the left of
+the @file{*scratch*} window again displaying a message showing the new
+window and its parent in the window tree.
+
+Note that both, new window and its parent have changed after moving the
+@file{*Messages*} window to the left.  Also, the highlighting disappears
+because any properties set up for the new window on the right are lost
+when the new window is re-created on the left.
+
+The following code uses the @var{refer} argument of @code{split-window}
+instead.
+
+@example
+@group
+(let* ((old (selected-window))
+       (new (split-window old nil 'right))
+       overlay)
+  (with-current-buffer (get-buffer-create "*Messages*")
+    (set-window-buffer new (current-buffer))
+    (setq overlay (make-overlay (point-min) (point-max)))
+    (overlay-put overlay 'face 'highlight)
+    (overlay-put overlay 'window new)
+    (message "new %s parent %s" new (window-parent new))
+    (sit-for 3))
+  (delete-window new)
+  (split-window old nil 'left nil new)
+  (format "new %s parent %s" new (window-parent new)))
+@end group
+@end example
+
+Note that all properties of the resurrected window like its decorations,
+parameters as well as any overlays with a window property are preserved
+as if that window had never been deleted.  The only things that changed
+are its position in the window tree and consequently the values returned
+by @code{window-left-child} of its parent window as well as the values
+returned by @code{window-prev-sibling} and @code{window-next-sibling} of
+the window and its sibling.
+
+The following code passes both, the new window on the right and its
+parent, via the @var{refer} argument to @code{split-window}: instead.
+
+@example
+@group
+(let* ((old (selected-window))
+       (new (split-window old nil 'right))
+       (parent (window-parent new))
+       overlay)
+  (with-current-buffer (get-buffer-create "*Messages*")
+    (set-window-buffer new (current-buffer))
+    (setq overlay (make-overlay (point-min) (point-max)))
+    (overlay-put overlay 'face 'highlight)
+    (overlay-put overlay 'window new)
+    (message "new %s parent %s" new (window-parent new))
+    (sit-for 3))
+  (delete-window new)
+  (split-window old nil 'left nil (cons new parent))
+  (format "new %s parent %s" new (window-parent new)))
+@end group
+@end example
+
+Note that the parent window has been resurrected along with the new
+window.
+
+Resurrecting dead windows is useful to preserve the identity of windows
+in actions that are supposed to do that like moving windows around on a
+frame or hiding them temporarily.  Any properties of such a window like
+its decorations, the buffer it has shown previously, that buffer's start
+and point position in the window, the window's dedicated status, its
+cursor type are left untouched and there's no need to recreate them from
+scratch.  For internal windows, the value of that window's combination
+limit is preerved which means that the window can be recombined
+(@pxref{Recombining Windows}) as before.
+
+Due to certain limitations in the way windows can be split, making a
+sequence of changes to the window structure can be more tricky.  It's
+still fairly simple to rotate three windows as follows:
+
+@example
+@group
+(let* ((old (selected-window))
+       (new-1 (split-window old nil 'right))
+       (parent-1 (window-parent old))
+       (new-2 (split-window new-1 nil 'below))
+       (parent-2 (window-parent new-2))
+       new)
+  (message "old %s (%s) .. new-1 %s (%s) .. new-2 %s (%s)"
+	   old (window-parent old)
+	   new-1 (window-parent new-1)
+	   new-2 (window-parent new-2))
+  (sit-for 3)
+  (delete-other-windows old)
+  (setq new (split-window old nil 'below nil (cons new-1 parent-1)))
+  (split-window new nil 'right nil (cons new-2 parent-2))
+  (format "old %s (%s) .. new-1 %s (%s) .. new-2 %s (%s)"
+	  old (window-parent old)
+	  new-1 (window-parent new-1)
+	  new-2 (window-parent new-2)))
+@end group
+@end example
+
+
 @node Cyclic Window Ordering
 @section Cyclic Ordering of Windows
 @cindex cyclic ordering of windows
@@ -7126,13 +7326,16 @@ Window Hooks
 window.
 
 @defun window-old-buffer &optional window
-This function returns the buffer shown in @var{window} at the last
-time window change functions were run for @var{window}'s frame.  If it
-returns @code{nil}, @var{window} has been created after that.  If it
-returns @code{t}, @var{window} was not shown at that time but has been
-restored from a previously saved window configuration afterwards.
-Otherwise, the return value is the buffer shown by @var{window} at
-that time.
+This function returns the buffer shown in @var{window} at the last time
+window change functions were run for @var{window}'s frame.  If it
+returns @code{nil}, @var{window} is either an internal window or has
+been created after that.  If it returns @code{t}, @var{window} was not
+shown at that time but has been restored from a previously saved window
+configuration afterwards.  Otherwise, the return value is the buffer
+shown by @var{window} at that time.  As a special case, if @var{window}
+has been deleted, this function returns the last buffer @var{window} had
+shown at that time.  @var{window} can be any window and defaults to the
+selected one.
 @end defun
 
 @defun window-old-pixel-width &optional window
diff --git a/lisp/window.el b/lisp/window.el
index b50770cbd7e..060716df936 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -5511,54 +5511,95 @@ window--combination-resizable
       (setq sibling (window-next-sibling sibling)))
     (/ size (1+ number))))
 
-(defun split-window (&optional window size side pixelwise)
+(defun split-window (&optional window size side pixelwise refer)
   "Make a new window adjacent to WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 Return the new window which is always a live window.
 
-Optional argument SIZE a positive number means make WINDOW SIZE
-lines or columns tall.  If SIZE is negative, make the new window
--SIZE lines or columns tall.  If and only if SIZE is non-nil, its
-absolute value can be less than `window-min-height' or
-`window-min-width'; so this command can make a new window as
-small as one line or two columns.  SIZE defaults to half of
-WINDOW's size.
+If the optional argument SIZE is a positive number, shrink WINDOW
+to SIZE lines or columns in order to accommodate the new window.
+If SIZE is a negative number, make the new window -SIZE lines or
+columns tall.  In both cases, the absolute value of SIZE can be
+less than `window-min-height' or `window-min-width'; so this
+function can make a new window as small as one line or two
+columns.  If SIZE is not a number, make the new window occupy
+half of WINDOW's size.
 
 Optional third argument SIDE nil (or `below') specifies that the
-new window shall be located below WINDOW.  SIDE `above' means the
-new window shall be located above WINDOW.  In both cases SIZE
+new window shall be made below WINDOW.  SIDE `above' means the
+new window shall be made above WINDOW.  In both cases SIZE
 specifies the new number of lines for WINDOW (or the new window
 if SIZE is negative) including space reserved for the mode and/or
-header line.
+header line, scroll bars and window dividers.
 
-SIDE t (or `right') specifies that the new window shall be
-located on the right side of WINDOW.  SIDE `left' means the new
-window shall be located on the left of WINDOW.  In both cases
-SIZE specifies the new number of columns for WINDOW (or the new
-window provided SIZE is negative) including space reserved for
-fringes and the scrollbar or a divider column.
+SIDE t (or `right') specifies that the new window shall be made
+on the right side of WINDOW.  SIDE `left' means the new window
+shall be made on the left of WINDOW.  In both cases, SIZE
+specifies the new number of columns for WINDOW (or the new window
+provided SIZE is negative) including any space reserved for
+fringes, scroll bar and window dividers.
 
 For compatibility reasons, SIDE `up' and `down' are interpreted
 as `above' and `below'.  Any other non-nil value for SIDE is
 currently handled like t (or `right').
 
+As a rule, if WINDOW already forms a combination that matches the SIDE
+parameter and `window-combination-limit' is nil, reuse WINDOW's parent
+in the window tree as parent of the new window.  If WINDOW is in a
+combination that is orthogonal to the SIDE parameter or if
+`window-combination-limit' is non-nil, make a new parent window that
+replaces WINDOW in the window tree and make WINDOW and the new window
+its sole child windows.  This standard behavior can be overridden via
+the REFER argument.
+
 PIXELWISE, if non-nil, means to interpret SIZE pixelwise.
 
+If the optional fifth argument REFER is non-nil, it specifies a
+reference window used for setting up properties of the new window.
+REFER can be either a window or a cons cell of two windows.
+
+If REFER is a cons cell, its car has to specify a deleted, former live
+window - a window that has shown a buffer before - on the same frame as
+WINDOW.  That buffer must be still live.  The cdr has to specify a
+deleted window that was a parent window on the same frame as WINDOW
+before it was deleted.  In this case, rather then making new windows,
+replace WINDOW with the cdr of REFER in the window tree and make WINDOW
+and REFER's car its new child windows.  Buffer, start and point
+positions of REFER's car are set to the values they had immediately
+before REFER's car was deleted the last time.  Decorations and
+parameters remain unaltered from their values before REFER's car and cdr
+were deleted.
+
+Alternatively REFER may specify a deleted, former live window - a window
+that has shown a buffer before - on the same frame as WINDOW.  In this
+case do not make a new window but rather make REFER live again and
+insert it into the window tree at the position and with the sizes the
+new window would have been given.  Buffer, start and point positions of
+REFER are set to the values they had immediately before REFER was
+deleted the last time.  Decorations and parameters remain unaltered from
+their values before REFER was deleted.  Throw an error if REFER's buffer
+has been deleted after REFER itself was deleted.
+
+Otherwise REFER must specify a live window.  In this case, the new
+window will inherit properties like buffer, start and point position and
+some decorations from REFER.  If REFER is nil or omitted, then if WINDOW
+is live, any such properties are inherited from WINDOW.  If, however,
+WINDOW is an internal window, the new window will inherit these
+properties from the window selected on WINDOW's frame.
+
 If the variable `ignore-window-parameters' is non-nil or the
 `split-window' parameter of WINDOW equals t, do not process any
-parameters of WINDOW.  Otherwise, if the `split-window' parameter
-of WINDOW specifies a function, call that function with all three
-arguments and return the value returned by that function.
-
-Otherwise, if WINDOW is part of an atomic window, \"split\" the
-root of that atomic window.  The new window does not become a
-member of that atomic window.
-
-If WINDOW is live, properties of the new window like margins and
-scrollbars are inherited from WINDOW.  If WINDOW is an internal
-window, these properties as well as the buffer displayed in the
-new window are inherited from the window selected on WINDOW's
-frame.  The selected window is not changed by this function."
+parameters of WINDOW.  Otherwise, if the `split-window' parameter of
+WINDOW specifies a function, call that function with the three first
+arguments WINDOW, SIZE and SIDE and return the value returned by that
+function.
+
+Otherwise, if WINDOW is part of an atomic window, \"split\" the root of
+that atomic window.  The new window does not become a member of that
+atomic window.
+
+The selected window and the selected window on WINDOW's frame are not
+changed by this function."
   (setq window (window-normalize-window window))
   (let* ((side (cond
 		((not side) 'below)
@@ -5598,14 +5639,14 @@ split-window
        ((and (window-parameter window 'window-atom)
 	     (setq atom-root (window-atom-root window))
 	     (not (eq atom-root window)))
-	(throw 'done (split-window atom-root size side pixelwise)))
+	(throw 'done (split-window atom-root size side pixelwise refer)))
        ;; If WINDOW's frame has a side window and WINDOW specifies the
        ;; frame's root window, split the frame's main window instead
        ;; (Bug#73627).
        ((and (eq window (frame-root-window frame))
 	     (window-with-parameter 'window-side nil frame))
 	(throw 'done (split-window (window-main-window frame)
-				   size side pixelwise)))
+				   size side pixelwise refer)))
        ;; If WINDOW is a side window or its first or last child is a
        ;; side window, throw an error unless `window-combination-resize'
        ;; equals 'side.
@@ -5644,8 +5685,8 @@ split-window
 		   (window-combined-p window horizontal)))
 	     ;; 'old-pixel-size' is the current pixel size of WINDOW.
 	     (old-pixel-size (window-size window horizontal t))
-	     ;; 'new-size' is the specified or calculated size of the
-	     ;; new window.
+	     ;; 'new-pixel-size' is the specified or calculated size
+	     ;; of the new window.
 	     new-pixel-size new-parent new-normal)
 	(cond
 	 ((not pixel-size)
@@ -5766,8 +5807,9 @@ split-window
 	   window (- (if new-parent 1.0 (window-normal-size window horizontal))
 		     new-normal)))
 
-	(let* ((new (split-window-internal window new-pixel-size side new-normal)))
-	  (window--pixel-to-total frame horizontal)
+	(let ((new (split-window-internal
+		    window new-pixel-size side new-normal refer)))
+          (window--pixel-to-total frame horizontal)
 	  ;; Assign window-side parameters, if any.
 	  (cond
 	   ((eq window-combination-resize 'side)
diff --git a/src/window.c b/src/window.c
index 34968ac824f..6c7749ddae1 100644
--- a/src/window.c
+++ b/src/window.c
@@ -652,15 +652,16 @@ DEFUN ("window-buffer", Fwindow_buffer, Swindow_buffer, 0, 1, 0,
 
 DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
        doc: /* Return the old buffer displayed by WINDOW.
-WINDOW must be a live window and defaults to the selected one.
+WINDOW can be any window and defaults to the selected one.
 
 The return value is the buffer shown in WINDOW at the last time window
-change functions were run.  It is nil if WINDOW was created after
-that.  It is t if WINDOW has been restored from a window configuration
-after that.  */)
+change functions were run or WINDOW is a former live window that was
+deleted.  It is nil if WINDOW was created after that.  It is t if WINDOW
+has been restored from a window configuration after that.  It is always
+nil if WINDOW is an internal window.  */)
   (Lisp_Object window)
 {
-  struct window *w = decode_live_window (window);
+  struct window *w = decode_any_window (window);
 
   return (NILP (w->old_buffer)
 	  /* A new window.  */
@@ -668,8 +669,8 @@ DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
 	  : (w->change_stamp != WINDOW_XFRAME (w)->change_stamp)
 	  /* A window restored from a configuration.  */
 	  ? Qt
-	  /* A window that was live the last time seen by window
-	     change functions.  */
+	  /* A window that was live the last time seen by window change
+	     functions or was deleted.  */
 	  : w->old_buffer);
 }
 
@@ -4491,45 +4492,6 @@ allocate_window (void)
 				       PVEC_WINDOW);
 }
 
-/* Make new window, have it replace WINDOW in window-tree, and make
-   WINDOW its only vertical child (HORFLAG means make WINDOW its only
-   horizontal child).   */
-static void
-make_parent_window (Lisp_Object window, bool horflag)
-{
-  Lisp_Object parent;
-  register struct window *o, *p;
-
-  o = XWINDOW (window);
-  p = allocate_window ();
-  memcpy ((char *) p + sizeof (union vectorlike_header),
-	  (char *) o + sizeof (union vectorlike_header),
-	  word_size * VECSIZE (struct window));
-  /* P's buffer slot may change from nil to a buffer...  */
-  adjust_window_count (p, 1);
-  XSETWINDOW (parent, p);
-
-  p->sequence_number = ++sequence_number;
-
-  replace_window (window, parent, true);
-
-  wset_next (o, Qnil);
-  wset_prev (o, Qnil);
-  wset_parent (o, parent);
-  /* ...but now P becomes an internal window.  */
-  wset_start (p, Qnil);
-  wset_pointm (p, Qnil);
-  wset_old_pointm (p, Qnil);
-  wset_buffer (p, Qnil);
-  wset_combination (p, horflag, window);
-  wset_combination_limit (p, Qnil);
-  /* Reset any previous and next buffers of p which have been installed
-     by the memcpy above.  */
-  wset_prev_buffers (p, Qnil);
-  wset_next_buffers (p, Qnil);
-  wset_window_parameters (p, Qnil);
-}
-
 /* Make new window from scratch.  */
 Lisp_Object
 make_window (void)
@@ -5073,7 +5035,7 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
 }
 
 
-DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 4, 0,
+DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 5, 0,
        doc: /* Split window OLD.
 Second argument PIXEL-SIZE specifies the number of pixels of the
 new window.  It must be a positive integer.
@@ -5088,32 +5050,33 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 the right side of WINDOW.  SIDE `left' means the new window shall be
 located on the left of WINDOW.  In both cases PIXEL-SIZE specifies the
 width of the new window including space reserved for fringes and the
-scrollbar or a divider column.
+scroll bar or a divider column.
 
 Fourth argument NORMAL-SIZE specifies the normal size of the new window
-according to the SIDE argument.
+according to the SIDE argument.  Optional fifth argument REFER is as for
+'split-window'.
 
 The new pixel and normal sizes of all involved windows must have been
 set correctly.  See the code of `split-window' for how this is done.  */)
-  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size)
-{
-  /* OLD (*o) is the window we have to split.  (*p) is either OLD's
-     parent window or an internal window we have to install as OLD's new
-     parent.  REFERENCE (*r) must denote a live window, or is set to OLD
-     provided OLD is a leaf window, or to the frame's selected window.
-     NEW (*n) is the new window created with some parameters taken from
-     REFERENCE (*r).  */
-  Lisp_Object new, frame, reference;
-  struct window *o, *p, *n, *r, *c;
-  struct frame *f;
+  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side,
+   Lisp_Object normal_size, Lisp_Object refer)
+{
+  /* OLD (*o) is the window to split.  REFER (*r) is a reference window,
+     either an arbitrary live window or a former live, now deleted
+     window on the same frame as OLD.  NEW (*n) is the new window
+     created anew or resurrected from REFER (*r), if specified.  *p
+     refers either to OLD's parent window that will become NEW's parent
+     window too or to a new internal window that becomes OLD's and NEW's
+     new parent.  */
+  struct window *o = decode_valid_window (old);
+  Lisp_Object frame = WINDOW_FRAME (o);
+  struct frame *f = XFRAME (frame);
+  struct window *p, *n, *r, *c;
   bool horflag
     /* HORFLAG is true when we split side-by-side, false otherwise.  */
     = EQ (side, Qt) || EQ (side, Qleft) || EQ (side, Qright);
-
-  CHECK_WINDOW (old);
-  o = XWINDOW (old);
-  frame = WINDOW_FRAME (o);
-  f = XFRAME (frame);
+  Lisp_Object new, parent = Qnil;
+  bool dead = false;
 
   CHECK_FIXNUM (pixel_size);
   EMACS_INT total_size
@@ -5131,14 +5094,74 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	   ? WINDOW_VERTICAL_COMBINATION_P (XWINDOW (o->parent))
 	   : WINDOW_HORIZONTAL_COMBINATION_P (XWINDOW (o->parent))));
 
-  /* We need a live reference window to initialize some parameters.  */
-  if (WINDOW_LIVE_P (old))
-    /* OLD is live, use it as reference window.  */
-    reference = old;
+  /* Set up reference window.  */
+  if (NILP (refer))
+    {
+      if (WINDOW_LIVE_P (old))
+	/* OLD is live, use it as reference window.  */
+	refer = old;
+      else
+	/* Use the frame's selected window as reference window.  */
+	refer = FRAME_SELECTED_WINDOW (f);
+
+      r = XWINDOW (refer);
+    }
+  else if (CONSP (refer))
+    {
+      /* If REFER is a cons, then its car must be a deleted, former live
+	 window and its cdr must be a deleted former parent window.  Set
+	 PARENT to the cdr of REFER and REFER to its car.  WINDOW and
+	 REFER end up as the sole children of PARENT which replaces
+	 WINDOW in the window tree.  As a special case, if REFER's cdr
+	 is t, reuse REFER's car's old parent as new parent provided it
+	 is a deleted fromer parent window.  */
+      parent = Fcdr (refer);
+      refer = Fcar (refer);
+      r = decode_any_window (refer);
+
+      if (!NILP (r->contents) || !BUFFERP (r->old_buffer))
+	error ("REFER's car must specify a deleted, former live window");
+      else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	error ("The buffer formerly shown by REFER's car has been killed");
+      else if (!EQ (r->frame, frame))
+	error ("REFER's car must specify a window on same frame as WINDOW");
+
+      if (EQ (parent, Qt))
+	/* If REFER's cdr is t, use the old parent of REFER's car as new
+	   parent.  */
+	parent = r->parent;
+
+      p = decode_any_window (parent);
+
+      if (!NILP (p->contents) || BUFFERP (p->old_buffer))
+	error ("REFER's cdr must specify a deleted, former parent window");
+      else if (!EQ (p->frame, frame))
+	error ("REFER's cdr must specify window on same frame as WINDOW");
+
+      dead = true;
+    }
   else
-    /* Use the frame's selected window as reference window.  */
-    reference = FRAME_SELECTED_WINDOW (f);
-  r = XWINDOW (reference);
+    {
+      r = decode_any_window (refer);
+
+      if (NILP (r->contents))
+	/* Presumably a deleted, former live window.  Check whether its
+	   contents can be used.  */
+	{
+	  if (!BUFFERP (r->old_buffer))
+	    error ("REFER must specify a former live window (must have shown a buffer)");
+	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	    error ("The buffer formerly shown by REFER has been killed");
+	  else if (!EQ (r->frame, frame))
+	    error ("REFER must specify a window on same frame as WINDOW");
+
+	  dead = true;
+	}
+      else if (!NILP (parent))
+	error ("If REFER is a cons, its car must not specify a live window");
+      else if (!WINDOW_LIVE_P (refer))
+	error ("REFER is not a live window (does not show a buffer)");
+    }
 
   /* The following bugs are caught by `split-window'.  */
   if (MINI_WINDOW_P (o))
@@ -5149,16 +5172,18 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     /* `window-combination-resize' non-nil means try to resize OLD's siblings
        proportionally.  */
     {
-      p = XWINDOW (o->parent);
+      struct window *op = XWINDOW (o->parent);
+
       /* Temporarily pretend we split the parent window.  */
       wset_new_pixel
-	(p, make_fixnum ((horflag ? p->pixel_width : p->pixel_height)
+	(op, make_fixnum ((horflag ? op->pixel_width : op->pixel_height)
 			 - XFIXNUM (pixel_size)));
-      if (!window_resize_check (p, horflag))
+      if (!window_resize_check (op, horflag))
 	error ("Window sizes don't fit");
       else
 	/* Undo the temporary pretension.  */
-	wset_new_pixel (p, make_fixnum (horflag ? p->pixel_width : p->pixel_height));
+	wset_new_pixel
+	  (op, make_fixnum (horflag ? op->pixel_width : op->pixel_height));
     }
   else
     {
@@ -5178,8 +5203,24 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
       Lisp_Object new_normal
 	= horflag ? o->normal_cols : o->normal_lines;
 
-      make_parent_window (old, horflag);
-      p = XWINDOW (o->parent);
+      if (NILP (parent))
+	/* This is the crux of the old make_parent_window.  */
+	{
+	  p = allocate_window ();
+	  XSETWINDOW (parent, p);
+	  p->sequence_number = ++sequence_number;
+	  wset_frame (p, frame);
+	}
+      else
+	/* Pacify GCC.  */
+	p = XWINDOW (parent);
+
+      replace_window (old, parent, true);
+      wset_next (o, Qnil);
+      wset_prev (o, Qnil);
+      wset_parent (o, parent);
+      wset_combination (p, horflag, old);
+
       if (EQ (Vwindow_combination_limit, Qt))
 	/* Store t in the new parent's combination_limit slot to avoid
 	   that its children get merged into another window.  */
@@ -5195,7 +5236,12 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     p = XWINDOW (o->parent);
 
   fset_redisplay (f);
-  new = make_window ();
+
+  if (dead)
+    new = refer;
+  else
+    new = make_window ();
+
   n = XWINDOW (new);
   wset_frame (n, frame);
   wset_parent (n, o->parent);
@@ -5222,16 +5268,19 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   n->window_end_valid = false;
   n->last_cursor_vpos = 0;
 
-  /* Get special geometry settings from reference window.  */
-  n->left_margin_cols = r->left_margin_cols;
-  n->right_margin_cols = r->right_margin_cols;
-  n->left_fringe_width = r->left_fringe_width;
-  n->right_fringe_width = r->right_fringe_width;
-  n->fringes_outside_margins = r->fringes_outside_margins;
-  n->scroll_bar_width = r->scroll_bar_width;
-  n->scroll_bar_height = r->scroll_bar_height;
-  wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
-  wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+  if (!dead)
+    {
+      /* Get special geometry settings from reference window.  */
+      n->left_margin_cols = r->left_margin_cols;
+      n->right_margin_cols = r->right_margin_cols;
+      n->left_fringe_width = r->left_fringe_width;
+      n->right_fringe_width = r->right_fringe_width;
+      n->fringes_outside_margins = r->fringes_outside_margins;
+      n->scroll_bar_width = r->scroll_bar_width;
+      n->scroll_bar_height = r->scroll_bar_height;
+      wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
+      wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+    }
 
   /* Directly assign orthogonal coordinates and sizes.  */
   if (horflag)
@@ -5260,6 +5309,7 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	sum = sum + XFIXNUM (c->new_total);
       c = NILP (c->next) ? 0 : XWINDOW (c->next);
     }
+
   wset_new_total (n, make_fixnum ((horflag
 				   ? p->total_cols
 				   : p->total_lines)
@@ -5267,10 +5317,30 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   wset_new_normal (n, normal_size);
 
   block_input ();
+
+  if (dead)
+    {
+      /* Get dead window back its old buffer and markers.  */
+      wset_buffer (n, n->old_buffer);
+      set_marker_restricted
+	(n->start, make_fixnum (XMARKER (n->start)->charpos), n->contents);
+      set_marker_restricted
+	(n->pointm, make_fixnum (XMARKER (n->pointm)->charpos), n->contents);
+      set_marker_restricted
+	(n->old_pointm, make_fixnum (XMARKER (n->old_pointm)->charpos),
+	 n->contents);
+
+      Vwindow_list = Qnil;
+      /* Remove window from the table of dead windows.  */
+      Fremhash (make_fixnum (n->sequence_number),
+		window_dead_windows_table);
+    }
+
   window_resize_apply (p, horflag);
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  */
+
   set_window_buffer (new, r->contents, true, true);
+
   FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();
 
@@ -5368,6 +5438,8 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  /* Store WINDOW's buffer in old_buffer.  */
+	  wset_old_buffer (w, w->contents);
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
@@ -7712,6 +7784,8 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      /* Store WINDOW's buffer in old_buffer.  */
+      wset_old_buffer (w, w->contents);
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -9064,12 +9138,9 @@ syms_of_window (void)
     doc: /* Hash table of dead windows.
 Each entry in this table maps a window number to a window object.
 Entries are added by `delete-window-internal' and are removed by the
-garbage collector.
-
-This table is maintained by code in window.c and is made visible in
-Elisp for testing purposes only.  */);
+garbage collector.  */);
   window_dead_windows_table
-    = CALLN (Fmake_hash_table, QCweakness, Qt);
+    = CALLN (Fmake_hash_table, QCweakness, Qvalue);
 
   defsubr (&Sselected_window);
   defsubr (&Sold_selected_window);

[-- Attachment #3: window-rotate.el --]
[-- Type: text/x-emacs-lisp, Size: 12182 bytes --]

;;; -*- lexical-binding:t -*-

;; Debugging code.
(defvar window-rotate-debug t)

(defun window-rotate-debug (&rest rest)
  "Append REST to debugging buffer *foo*."
  (when window-rotate-debug
    (with-current-buffer (get-buffer-create "*foo*")
      (goto-char (point-max))
      (when rest
	(insert (format "%s" (car rest)))
	(setq rest (cdr rest))
	(while rest
	  (insert (format " .. %s" (car rest)))
	  (setq rest (cdr rest)))
	(insert "\n")))))

(defun window-rotate-debug-side (side)
  (pcase side
    ('left "<")
    ('right ">")
    ('above "^")
    ('below "v")
    (_ "?")))

;; Two variables the code below binds lexically.
(defvar window-alist nil)
(defvar window-rotate-clockwise nil)

(defun window-first-window (window)
  "Return leftmost live descendant of specified WINDOW.
Return nil if WINDOW is not valid.  Otherwise, return the live window in
the window tree rooted at WINDOW such that that window and all ancestors
of that window in that window tree have no previous sibling.  Return
WINDOW if it does not have a child window."
  (when (window-valid-p window)
    (let ((child (or (window-top-child window)
		     (window-left-child window))))
      (if child
	  (window-first-window child)
	window))))

(defun window-alist (&optional frame)
  "Return alist of all windows on FRAME.
FRAME must be a live frame and defaults to the selected one.  Each
element of the return value associates a window on FRAME with its
properties like, for example, its parent in the window tree of FRAME.
Accessor functions like, for example, `window-alist-parent' are provided
to conveniently retrieve the values stored by this function."
  (let (alist)
    (walk-window-tree
     (lambda (window)
       (setq alist
	     (cons
	      (if (window-buffer window)
		  (list window
			(cons 'buffer (window-buffer window))
			(cons 'parent (window-parent window))
			(cons 'prev (window-prev-sibling window))
			(cons 'next (window-next-sibling window))
			(cons 'width (window-normal-size window t))
			(cons 'height (window-normal-size window)))
		(list window
		      (cons 'left (window-left-child window))
		      (cons 'top (window-top-child window))
		      (cons 'parent (window-parent window))
		      (cons 'prev (window-prev-sibling window))
		      (cons 'next (window-next-sibling window))
		      (cons 'first (window-first-window window))
		      (cons 'width (window-normal-size window t))
		      (cons 'height (window-normal-size window))
		      (cons 'combination-limit (window-combination-limit window))
		      (cons 'no-rotate (window-parameter window 'no-rotate))))
	      alist)))
     frame t)

    (when window-rotate-debug
      (with-current-buffer (get-buffer-create "*foo*")
	(erase-buffer)
	(pp alist (current-buffer))
	(insert "\n")))

    alist))

;; Accessor functions.
(defun window-alist-parent (window)
  (cdr (assq 'parent (cdr (assq window window-alist)))))

(defun window-alist-buffer (window)
  (cdr (assq 'buffer (cdr (assq window window-alist)))))

(defun window-alist-next (window)
  (cdr (assq 'next (cdr (assq window window-alist)))))

(defun window-alist-prev (window)
  (cdr (assq 'prev (cdr (assq window window-alist)))))

(defun window-alist-left (window)
  (cdr (assq 'left (cdr (assq window window-alist)))))

(defun window-alist-top (window)
  (cdr (assq 'top (cdr (assq window window-alist)))))

(defun window-alist-first (window)
  (cdr (assq 'first (cdr (assq window window-alist)))))

(defun window-alist-width (window)
  (cdr (assq 'width (cdr (assq window window-alist)))))

(defun window-alist-height (window)
  (cdr (assq 'height (cdr (assq window window-alist)))))

(defun window-alist-no-rotate (window)
  (let ((no-rotate
	 (catch 'no-rotate
	   (while window
	     (when (cdr (assq 'no-rotate (cdr (assq window window-alist))))
	       (throw 'no-rotate t))
	     (setq window (window-alist-parent window))))))
    no-rotate))

(defun window-alist-combination-limit (window)
  (cdr (assq 'combination-limit (cdr (assq window window-alist)))))

(defun window-rotate-clockwise (&optional frame)
  "Rotate root window of specified FRAME clockwise.
FRAME must be a live frame and defaults to the selected one."
  (interactive)
  (let ((window-rotate-clockwise t))
    (window-rotate frame)))

(defun window-rotate-anticlockwise (&optional frame)
  "Rotate root window of specified FRAME anticlockwise.
FRAME must be a live frame and defaults to the selected one."
  (interactive)
  (let (window-rotate-clockwise)
    (window-rotate frame)))

(defun window-alist-check ()
  "Check whether `window-alist' has been preserved. "
  ;; For obvious reasons, never check previous and next siblings here.
  ;; The whole purpose of rotating is to change these.
  (catch 'failed
    (dolist (entry window-alist)
      (let ((window (car entry)))
	(cond
	 ((not (window-valid-p window))
	  (window-rotate-debug
	   (format
	    "Window %s has not been restored" window))
	  (throw 'failed nil))
	 ((not (eq (window-parent window)
		   (window-alist-parent window)))
	  (window-rotate-debug
	   (format
	    "New parent %s of window %s does not match old one %s"
	    (window-parent window) window (window-alist-parent window)))
	  (throw 'failed nil))
	 ((not (eq (window-buffer window)
		   (window-alist-buffer window)))
	  (window-rotate-debug
	   (format
	    "New buffer %s of window %s does not match old one %s"
	    (window-buffer window) window (window-alist-buffer window)))
	  (throw 'failed nil)))))

    t))

(defun window-rotate (&optional frame)
  "Rotate root window of specified FRAME."
  (setq frame (window-normalize-frame frame))
  ;; Construct 'window-alist' which also makes sure that no window
  ;; gets recycled by GC.
  (let ((window-alist (window-alist frame))
	(window (frame-first-window frame))
	(window-min-height 0)
	(window-min-width 0)
	(configuration (current-window-configuration frame)))
    (condition-case var
	(progn
	  ;; Delete all windows but FRAME's first window.
	  (delete-other-windows-internal window)
	  ;; Now rotate starting with the frame's first window as WINDOW
	  ;; argument and nil as ROOT.
	  (window-rotate-debug (format "First %s"window))
	  (window-rotate-1 window nil)
	  (unless (window-alist-check)
	    (user-error "Rotating failed")
	    (set-window-configuration configuration)))
      (error
       (message "Rotating failed as %s" var)
       (set-window-configuration configuration)))))

(defun window-rotate-1 (window root)
  "Rotate window subroutine.
WINDOW must be a leftmost bottommost child window such as produced by
`frame-first-window' or `window-first-window'.  More precisely, WINDOW
must show a buffer and its previous sibling must be nil.

ROOT is either the root window to rotate or a parent window such that
WINDOW is its `frame-first-window'.

Traverse the window structure saved in `window-alist' starting with
WINDOW, continuing with its next siblings.  If a sibling is a parent
window, recurse with WINDOW set to the window returned by
`window-first-window' for that sibling and ROOT set to sibling's parent.
After having traced all siblings continue with WINDOW's parent unless
that parent equals ROOT."
  ;; It's practically always a bug to use a "normal" window accessor
  ;; function (like 'window-parent') in the body of this function.  When
  ;; called for the first time, the frame may have only one live window
  ;; and all other windows are deleted.  Hence most "normal" window
  ;; accessor function will throw an invalid argument error.  So instead
  ;; of 'window-parent' you have to use 'window-alist-parent'.
  (let ((parent (window-alist-parent window))
	next first side size)
    (when (and parent (not (eq parent root)))
      ;; Traverse WINDOW (which is always the first sibling) and its
      ;; next siblings.
      (while (setq next (window-alist-next window))
	;; Calculate SIDE argument for 'split-window' taking into
	;; account 'window-rotate-clockwise' and whether WINDOW is in a
	;; horizontal of vertical combination.  If one of WINDOW's
	;; ancestors has a non-nil 'no-rotate' parameter, don't rotate
	;; its descendants.
	(setq side
	      (cond
	       ((window-alist-no-rotate parent)
		(if (window-alist-left parent) 'right 'below))
	       (window-rotate-clockwise
		(if (window-alist-left parent) 'below 'left))
	       (t
		(if (window-alist-left parent) 'above 'right))))
	(setq size (round
		    (if (or (and (window-alist-no-rotate parent)
				 (window-alist-left parent))
			    (memq side '(left right)))
			(* (if (window-valid-p parent)
			       (window-pixel-width parent)
			     (window-pixel-width window))
			   (window-alist-height window))
		      (* (if (window-valid-p parent)
			     (window-pixel-height parent)
			   (window-pixel-height window))
			 (window-alist-width window)))))
	;; Save current window configuration in case of an error.
	(if (window-alist-buffer next)
	    ;; If the next sibling shows a buffer, split it with
	    ;; WINDOW's old parent as new parent.
	    (let* ((prev (window-alist-prev window))
		   (limit (window-alist-combination-limit parent))
		   ;; Set 'window-combination-limit' to make sure
		   ;; that a new parent is created when WINDOW's
		   ;; previous sibling and WINDOW do not have the
		   ;; same parent.
		   (window-combination-limit
		    (or limit
			(not prev)
			(not (eq (window-alist-parent prev)
				 (window-alist-parent window)))))
		   window-combination-resize)
	      (window-rotate-debug
	       (format "%s %s N %s [%s|%s] S %s %s"
		       (window-rotate-debug-side side) window next
		       (window-parent window) parent size
		       (if window-combination-limit "T" "F")))
	      (split-window
	       window size side t
	       (if (and (window-valid-p parent)
			(eq parent (window-parent window)))
		   next
		 (cons next parent)))
;; 	      (y-or-n-p "N")
	      (set-window-combination-limit parent limit))
	  ;; If the next sibling is a parent window set FIRST to its
	  ;; topmost leftmost live descendant.
	  (setq first (window-alist-first next))
	  (let* ((prev (window-alist-prev window))
		 (limit (window-alist-combination-limit parent))
		 ;; Set 'window-combination-limit' to make sure
		 ;; that a new parent is created when WINDOW's
		 ;; previous sibling and WINDOW do not have the
		 ;; same parent.
		 (window-combination-limit
		  (or limit
		      (not prev)
		      (not (eq (window-alist-parent prev)
			       (window-alist-parent window)))))
		 window-combination-resize)
	    (window-rotate-debug
	     (format "%s %s F %s [%s|%s] S %s %s"
		     (window-rotate-debug-side side) window first
		     (window-parent window) parent size
	     (if window-combination-limit "T" "F")))
	    (split-window
	     window size side t
	     (if (and (window-valid-p parent)
		      (eq parent (window-parent window)))
		 first
	       (cons first parent)))
;; 	    (y-or-n-p "F")
	    (set-window-combination-limit parent limit)
	    ;; Recurse starting with WINDOW's first live descendant.
	    ;; The second argument PARENT should terminate the recursion
	    ;; when going up again.
	    (window-rotate-1 first parent)))
	;; Continue with WINDOW's next sibling.
	(setq window (window-alist-next window)))
      ;; Continue with WINDOW's parent unless we are at the ROOT of the
      ;; current recursion.
      (unless (eq (window-alist-parent window) root)
	(window-rotate-1 (window-alist-parent window) root)))))

(defun window-rotate-setup ()
  (interactive)
  (find-file-noselect "/home/martin/temp/window-rotate.el")
  (eval-buffer (get-buffer "window-rotate.el"))
  (let ((foo (get-buffer-create "*foo*"))
	window)
    (delete-other-windows)
    (switch-to-buffer (get-buffer "window-rotate.el"))
    (goto-char (point-min))
    (re-search-forward "(defun window-rotate-1 (window root)")
;;     (edebug-eval-top-level-form)
    (setq window (split-window nil nil t))
    (set-window-buffer window foo)
    (split-window window)
    (setq window (split-window window nil t))
    (split-window window)
    (select-window window)))

(global-set-key [(super P)] 'window-rotate-setup)
(global-set-key [(super p)] 'window-rotate-clockwise)
(global-set-key [(super q)] 'window-rotate-anticlockwise)

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

* Re: Add function to rotate/transpose all windows
  2024-11-05 18:01                                                                               ` martin rudalics
@ 2024-11-08  9:23                                                                                 ` Pranshu Sharma
  2024-11-08 10:06                                                                                   ` Pranshu Sharma
  2024-11-08 15:52                                                                                   ` martin rudalics
  0 siblings, 2 replies; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-08  9:23 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

> or its orthogonal variant.  With your approach I do not see (a (c d))
> after the second split but (a (b c)) just like with mine.  AFAICT your
> algorithm performs exactly the same sequence of splits as mine.  You can
> verify my claim by putting a 'y-or-n-p' after each 'split-window' call
> (in the version I attached they are commented out in the code), making
> two frames on your screen and stepping through your version in one and
> mine in the other window.  You will see that they split windows in
> exactly the same order.

Yes, I veryfied that and it is true

> I also noted that with
>
> C-x 3 C-x o C-x 2 C-x 3 C-x o C-x 2
>
> my and your last 'split-window' call split a parent window - something
> you earlier told me that you didn't want to do.

I quicly changed my mind after saying that, seeing there was no proper
alternative rather than my first approach.  But looking at your
'top-level' first approach you mentioned later, seems like there might
be a way.

> The following is the central idea of my approach.  Suppose F is a first
> live window (initially the 'frame-first-window') - a live window which
> has no previous sibling in the window tree.  And suppose P is the parent
> of F in the window tree that has already been deleted, all relationships
> are from the tree before it has been deleted.  'delete-other-windows'
> now deletes all windows but F.
>
>   P
>  /
> F   P1    P2 ... Pn
>    /     /      /
>   /     /      /
>  F1    F2     Fn
>
> Now I take the next sibling P1 of F - I assume it's a parent window
> because that's the hard case.  I find the first live descendant of P1
> which is F1 (any parent window invariably has such a descendant).  My
> first split is now
>
> (split-window F .. (F1 . P))
>
> because I see that F has not yet its valid parent P.  This resurrects F1
> and makes F and F1 the children of the resurrected P.  If F already had
> its valid parent P, I would have called
>
> (split-window F .. F1)
>
> and P would have got the additional child F1.  Now I recurse with F1
> taking over the part of F.  After I'm done with the entire subtree of
> P1, I do
>
> (split-window P1 .. F2)
>
> so P gets a new child window F2 and continue with F2 taking over the
> part of F1 and F.  After I'm done with the subtree of Pn, I continue
> with P's next sibling and go up until I arrive at the root of the window
> tree.
>

> Note also that I always apply the new size to the window I split.  This
> means that when I split F then F1 initially gets the entire remainder of
> the size of P.  It also means that when I later split P1, 'split-window'
> has to recursively resize the entire tree rooted at P1 and F2 gets the
> rest of the size of P - that of P minus that of F and P1.

Yeah, my approach did the same thing as this. The alternative is to
reverse the list and split them with size negated from root window,
but this gets very bad very fast.

> It might be interesting to try the following variant: Instead of
> recursing immediately into F1, first split all siblings of F so that one
> has a layout of
>
> F  F1  F2  ... Fn
>
> and only now recursively process F1, F2 ... Fn.  This would have the
> advantage that when splitting F1 to fit in the entire subtree rooted at
> P1, its size is already the final size.  And we might not have to split
> parent windows either.  But such a solution will be less trivial to
> code.

this idea seems pretty good, I don't see any downside to this.  I'll
have a go at it.  But will this work with your earilier magic patch that
would somehow automaitclly reconsutruct parent windows?

> Attached find my latest diffs (the documentation has not been completed
> yet) and window-rotate.el.

I applied the patches, and found your think works well in most cases,
however it fails to work in windows of file attached


[-- Attachment #2: Screenshot at 2024-11-08 19-07-17.png --]
[-- Type: image/png, Size: 290475 bytes --]

[-- Attachment #3: Type: text/plain, Size: 138 bytes --]


Wait, now that I look at it, it does not work for windows with >2
siblings, for example C-x 3 C-x 3 gives me 'wrong argument type' error

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

* Re: Add function to rotate/transpose all windows
  2024-11-08  9:23                                                                                 ` Pranshu Sharma
@ 2024-11-08 10:06                                                                                   ` Pranshu Sharma
  2024-11-08 15:52                                                                                     ` martin rudalics
  2024-11-08 15:52                                                                                   ` martin rudalics
  1 sibling, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-08 10:06 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel


Now that I think about it, using the split top level sibling approach
first, but it will requite the (flatten-list) approach, if doing that
why not just go with the first thing I made.



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

* Re: Add function to rotate/transpose all windows
  2024-11-08  9:23                                                                                 ` Pranshu Sharma
  2024-11-08 10:06                                                                                   ` Pranshu Sharma
@ 2024-11-08 15:52                                                                                   ` martin rudalics
  2024-11-09  2:09                                                                                     ` Pranshu Sharma
  1 sibling, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-08 15:52 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 >> It might be interesting to try the following variant: Instead of
 >> recursing immediately into F1, first split all siblings of F so that one
 >> has a layout of
 >>
 >> F  F1  F2  ... Fn
 >>
 >> and only now recursively process F1, F2 ... Fn.  This would have the
 >> advantage that when splitting F1 to fit in the entire subtree rooted at
 >> P1, its size is already the final size.  And we might not have to split
 >> parent windows either.  But such a solution will be less trivial to
 >> code.
 >
 > this idea seems pretty good, I don't see any downside to this.  I'll
 > have a go at it.  But will this work with your earilier magic patch that
 > would somehow automaitclly reconsutruct parent windows?

It wasn't magic - so far I was not able to make it work here.  In all
approaches that work here, I have to pass a (window . parent) cons to
'split-window' as REFER argument.  I do no exclude that one can make it
work but it's not simple and very likely requires the normal window
handling routines to keep an old parent for each window around.

 >> Attached find my latest diffs (the documentation has not been completed
 >> yet) and window-rotate.el.
 >
 > I applied the patches, and found your think works well in most cases,
 > however it fails to work in windows of file attached

I don't see that here.  IIUC you can get that initial configuration via

C-x 3 C-x o C-x 2 C-x 3 C-x 2 C-x 3 C-x 3

and it rotates fine here.  What are the contents of *foo* after
attempting to rotate?

 > Wait, now that I look at it, it does not work for windows with >2
 > siblings, for example C-x 3 C-x 3 gives me 'wrong argument type' error

Strange.  If that happened within 'split-window' it should have been
caught by the 'condition-case'.  If it happens elsewhere, you would have
to run it via edebug.  In either case *foo* should tell how far it got.

martin




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

* Re: Add function to rotate/transpose all windows
  2024-11-08 10:06                                                                                   ` Pranshu Sharma
@ 2024-11-08 15:52                                                                                     ` martin rudalics
  2024-11-09  2:14                                                                                       ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-08 15:52 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > Now that I think about it, using the split top level sibling approach
 > first, but it will requite the (flatten-list) approach, if doing that
 > why not just go with the first thing I made.

Your last version I tried does use 'flatten-list'.  So what do you mean
here?

I never used 'flatten-list' and I see only one use of it in the Emacs
sources.  The one time I tried to debug your code I gave up because
scanning that flattened list went on for ages.  I think that keeping the
original window structure around until rotating completes is essential
for debugging.  Any modifications like pruning or flattening are only
distracting in this regard.

martin




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

* Re: Add function to rotate/transpose all windows
  2024-11-08 15:52                                                                                   ` martin rudalics
@ 2024-11-09  2:09                                                                                     ` Pranshu Sharma
  2024-11-09  8:48                                                                                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-09  2:09 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

>> this idea seems pretty good, I don't see any downside to this.  I'll
>> have a go at it.  But will this work with your earilier magic patch that
>> would somehow automaitclly reconsutruct parent windows?
>
> It wasn't magic - so far I was not able to make it work here.  In all
> approaches that work here, I have to pass a (window . parent) cons to
> 'split-window' as REFER argument.  I do no exclude that one can make it
> work but it's not simple and very likely requires the normal window
> handling routines to keep an old parent for each window around.

Ah, I see.  This approach definetly seems better tho, cause there is no
hidden stuff going on.

>>> Attached find my latest diffs (the documentation has not been completed
>>> yet) and window-rotate.el.
>>
>> I applied the patches, and found your think works well in most cases,
>> however it fails to work in windows of file attached
>
> I don't see that here.  IIUC you can get that initial configuration via
>
> C-x 3 C-x o C-x 2 C-x 3 C-x 2 C-x 3 C-x 3
>
> and it rotates fine here.  What are the contents of *foo* after
> attempting to rotate?

I attached the contents of foo, but with the simpler split of
C-x 3 C-x o C-x 3  which still has the same error

>> Wait, now that I look at it, it does not work for windows with >2
>> siblings, for example C-x 3 C-x 3 gives me 'wrong argument type' error
>
> Strange.  If that happened within 'split-window' it should have been
> caught by the 'condition-case'.  If it happens elsewhere, you would have
> to run it via edebug.  In either case *foo* should tell how far it got.

I'll see if you can tell anything by looking at foo, otherwise I will
have to do that.

Looking at the code, you have not set the size argument yet, meaning that it
sis splitting only half and half approach.

When you do this you will see the flaw of splitting parent windows: When
you split a parent window, all the child of the window being split are
scaled equally.  Emacs does not handle this properly, for example look
at the screenshot I attached, this is why I initally did not want to
split parent windows.


[-- Attachment #2: Screenshot at 2024-11-09 12-04-44.png --]
[-- Type: image/png, Size: 124246 bytes --]

[-- Attachment #3: foo --]
[-- Type: application/octet-stream, Size: 1853 bytes --]

((#<window 29>
	   (buffer . #<buffer *unsent wide reply to martin rudalics*>)
	   (parent . #<window 27>) (next) (prev . #<window 28>)
	   (width . 481) (height . 1014))
 (#<window 28>
	   (buffer . #<buffer *unsent wide reply to martin rudalics*>)
	   (parent . #<window 27>) (next . #<window 29>)
	   (prev . #<window 3 on *foo*>) (width . 481) (height . 1014))
 (#<window 3 on *foo*> (buffer . #<buffer *foo*>)
	   (parent . #<window 27>) (next . #<window 28>) (prev)
	   (width . 958) (height . 1014))
 (#<window 27> (left . #<window 3 on *foo*>) (top) (parent)
	   (next . #<window 4 on  *Minibuf-0*>) (prev)
	   (first . #<window 3 on *foo*>) (width . 1920)
	   (height . 1014)))

1 .. w #<window 3 on *foo*> .. n #<window 28> .. p #<window 27>
1 .. w #<window 28 on *unsent wide reply to martin rudalics*> .. n #<window 29> .. p #<window 27>
((#<window 29>
	   (buffer . #<buffer *unsent wide reply to martin rudalics*>)
	   (parent . #<window 27>) (next)
	   (prev . #<window 28 on *unsent wide reply to martin
		 rudalics*>)
	   (width . 481) (height . 1014))
 (#<window 28 on *unsent wide reply to martin rudalics*>
	   (buffer . #<buffer *unsent wide reply to martin rudalics*>)
	   (parent . #<window 27>) (next . #<window 29>)
	   (prev . #<window 3 on *foo*>) (width . 481) (height . 1014))
 (#<window 3 on *foo*> (buffer . #<buffer *foo*>)
	   (parent . #<window 27>)
	   (next . #<window 28 on *unsent wide reply to martin
		 rudalics*>)
	   (prev) (width . 958) (height . 1014))
 (#<window 27> (left . #<window 3 on *foo*>) (top) (parent)
	   (next . #<window 4 on  *Minibuf-0*>) (prev)
	   (first . #<window 3 on *foo*>) (width . 1920)
	   (height . 1014)))

1 .. w #<window 28 on *unsent wide reply to martin rudalics*> .. s above .. r (#<window 29> . #<window 27>)
(error REFER’s cdr must specify a deleted, former parent window)

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

* Re: Add function to rotate/transpose all windows
  2024-11-08 15:52                                                                                     ` martin rudalics
@ 2024-11-09  2:14                                                                                       ` Pranshu Sharma
  2024-11-09  8:48                                                                                         ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-09  2:14 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> Now that I think about it, using the split top level sibling approach
>> first, but it will requite the (flatten-list) approach, if doing that
>> why not just go with the first thing I made.
>
> Your last version I tried does use 'flatten-list'.  So what do you mean
> here?
>
> I never used 'flatten-list' and I see only one use of it in the Emacs
> sources.

Just out of curiosity, where?

> The one time I tried to debug your code I gave up because scanning
> that flattened list went on for ages.  I think that keeping the
> original window structure around until rotating completes is essential
> for debugging.  Any modifications like pruning or flattening are only
> distracting in this regard.

I don't have anything aginast flattening list, but the problem is that
when you are using it, it means that you are finding a window's first
live descendent.  Like you said about 2 weeks ago, this is not ideal, as
you are splitting window A with C, when they are not siblings.
___________
|     |  C |
|  A  |----|
|     |  B |
|_____|____|

This approach will have to be used  in the split top level siblings approach.



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

* Re: Add function to rotate/transpose all windows
  2024-11-09  2:09                                                                                     ` Pranshu Sharma
@ 2024-11-09  8:48                                                                                       ` martin rudalics
  2024-11-09 10:55                                                                                         ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-09  8:48 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

 >> It wasn't magic - so far I was not able to make it work here.  In all
 >> approaches that work here, I have to pass a (window . parent) cons to
 >> 'split-window' as REFER argument.  I do no exclude that one can make it
 >> work but it's not simple and very likely requires the normal window
 >> handling routines to keep an old parent for each window around.
 >
 > Ah, I see.  This approach definetly seems better tho, cause there is no
 > hidden stuff going on.

But the caller has to remember which parent was the old one and pass it
as argument.

 > I attached the contents of foo, but with the simpler split of
 > C-x 3 C-x o C-x 3  which still has the same error

I suspect you're testing an old version of window-rotate.el or the wrong
patch for 'split-window'.  I resend them both.

 > I'll see if you can tell anything by looking at foo, otherwise I will
 > have to do that.

This is definitively an old version.  The new version outputs something
like

((#<window 16 on *foo*> (buffer . #<buffer *foo*>)
	   (parent . #<window 14>) (prev . #<window 15 on *foo*>)
	   (next) (width . 0.25) (height . 1.0))
  (#<window 15 on *foo*> (buffer . #<buffer *foo*>)
	   (parent . #<window 14>) (prev . #<window 11 on *foo*>)
	   (next . #<window 16 on *foo*>) (width . 0.25)
	   (height . 1.0))
  (#<window 11 on *foo*> (buffer . #<buffer *foo*>)
	   (parent . #<window 14>) (prev)
	   (next . #<window 15 on *foo*>) (width . 0.5) (height . 1.0))
  (#<window 14> (left . #<window 11 on *foo*>) (top) (parent) (prev)
	   (next . #<window 4 on  *Minibuf-0*>)
	   (first . #<window 11 on *foo*>) (width . 1.0)
	   (height . 1.0) (combination-limit) (no-rotate)))

First #<window 11 on *foo*>
v #<window 11 on *foo*> N #<window 15> [nil|#<window 14>] S 385 T
v #<window 15 on *foo*> N #<window 16> [#<window 14>|#<window 14>] S 192 F

to *foo* where the last lines explain the 'split-window' calls and are
interpreted, from left to right

- "v" represents a split below ("^" above "<" left and ">" right are the
   other indicators)

- the window to split

- "N" is followed by the live window to recreate, a next sibling (it
   could also be "F" for first descendant)

- [|] specify the old and new parent of the window to split

- "S" is followed by the new size in pixels of the window to split

- a final "T" or "F" stand for the combination limit of the parent.

With C-x 3 C-x 3 there must be at least one line terminated by an "F"
which means a horizontal combination of at least three windows was made.
You can see that also in the parent windows that were used: The first
split resurrects window 14 as parent of 11 and 15.  The second split
reuses window 14 as new parent of windows 15 and 16.

 > Looking at the code, you have not set the size argument yet, meaning that it
 > sis splitting only half and half approach.
 >
 > When you do this you will see the flaw of splitting parent windows: When
 > you split a parent window, all the child of the window being split are
 > scaled equally.  Emacs does not handle this properly, for example look
 > at the screenshot I attached, this is why I initally did not want to
 > split parent windows.

Try again with the code I attached.

martin

[-- Attachment #2: window-rotate.txt --]
[-- Type: text/plain, Size: 3921 bytes --]

> This is the top-down way, where say you have the tree
> (a (b (c d) e))
> You go like
> (a) -> (a (b)) -> (a (b c)) -> (a (b (c d)))...
> the way this one works is bottom up, kind of like:
> (a) -> (a (c d)) -> (a (b (c d)))...

I don't see that here - I assume that you made the configuration via
something like

C-x 3 C-x o C-x 2 C-x 2 C-x o C-x 3

or its orthogonal variant.  With your approach I do not see (a (c d))
after the second split but (a (b c)) just like with mine.  AFAICT your
algorithm performs exactly the same sequence of splits as mine.  You can
verify my claim by putting a 'y-or-n-p' after each 'split-window' call
(in the version I attached they are commented out in the code), making
two frames on your screen and stepping through your version in one and
mine in the other window.  You will see that they split windows in
exactly the same order.

I also noted that with

C-x 3 C-x o C-x 2 C-x 3 C-x o C-x 2

my and your last 'split-window' call split a parent window - something
you earlier told me that you didn't want to do.

The following is the central idea of my approach.  Suppose F is a first
live window (initially the 'frame-first-window') - a live window which
has no previous sibling in the window tree.  And suppose P is the parent
of F in the window tree that has already been deleted, all relationships
are from the tree before it has been deleted.  'delete-other-windows'
now deletes all windows but F.

  P
 /
F   P1    P2 ... Pn
   /     /      /
  /     /      /
 F1    F2     Fn

Now I take the next sibling P1 of F - I assume it's a parent window
because that's the hard case.  I find the first live descendant of P1
which is F1 (any parent window invariably has such a descendant).  My
first split is now

(split-window F .. (F1 . P))

because I see that F has not yet its valid parent P.  This resurrects F1
and makes F and F1 the children of the resurrected P.  If F already had
its valid parent P, I would have called

(split-window F .. F1)

and P would have got the additional child F1.  Now I recurse with F1
taking over the part of F.  After I'm done with the entire subtree of
P1, I do

(split-window P1 .. F2)

so P gets a new child window F2 and continue with F2 taking over the
part of F1 and F.  After I'm done with the subtree of Pn, I continue
with P's next sibling and go up until I arrive at the root of the window
tree.

Note here: Initially F's parent is nil.  When I split F to resurrect F1
both get the new parent P.  P is already the final parent of F but not
of F1.  When I later split F1 it will get its final parent as well.  In
neither of these splits, 'split-window' has any idea which parent window
to use.  It could try to find one among the dead windows the one whose
first child is F (or F1) but I haven't implemented such a mechanism and
I'm not sure whether it's worth the hassle.  That's why I have the
rotation routine initially remember the old parents of all windows and
pass them to 'split-window' as cdr of the REFER argument.

Note also that I always apply the new size to the window I split.  This
means that when I split F then F1 initially gets the entire remainder of
the size of P.  It also means that when I later split P1, 'split-window'
has to recursively resize the entire tree rooted at P1 and F2 gets the
rest of the size of P - that of P minus that of F and P1.

It might be interesting to try the following variant: Instead of
recursing immediately into F1, first split all siblings of F so that one
has a layout of

F  F1  F2  ... Fn

and only now recursively process F1, F2 ... Fn.  This would have the
advantage that when splitting F1 to fit in the entire subtree rooted at
P1, its size is already the final size.  And we might not have to split
parent windows either.  But such a solution will be less trivial.

Attached find my latest diffs (the documentation has not been completed
yet) and window-rotate.el.

martin

[-- Attachment #3: split-window.diff --]
[-- Type: text/x-patch, Size: 37781 bytes --]

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 0b8d7d3b76d..e67fc256b3d 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -22,6 +22,7 @@ Windows
 * Deleting Windows::        Removing a window from its frame.
 * Recombining Windows::     Preserving the frame layout when splitting and
                               deleting windows.
+* Resurrecting deleted windows:: Restoring indivdual windows.
 * Cyclic Window Ordering::  Moving around the existing windows.
 * Buffers and Windows::     Each window displays the contents of a buffer.
 * Switching Buffers::       Higher-level functions for switching to a buffer.
@@ -1355,7 +1356,7 @@ Splitting Windows
 Examples of such windows are side windows (@pxref{Side Windows}) and
 atomic windows (@pxref{Atomic Windows}).
 
-@defun split-window &optional window size side pixelwise
+@defun split-window &optional window size side pixelwise refer
 This function creates a new live window next to the window
 @var{window}.  If @var{window} is omitted or @code{nil}, it defaults
 to the selected window.  That window is split, and reduced in
@@ -1364,7 +1365,7 @@ Splitting Windows
 The optional second argument @var{size} determines the sizes of
 @var{window} and/or the new window.  If it is omitted or @code{nil},
 both windows are given equal sizes; if there is an odd line, it is
-allocated to the new window.  If @var{size} is a positive number,
+allotted to the new window.  If @var{size} is a positive number,
 @var{window} is given @var{size} lines (or columns, depending on the
 value of @var{side}).  If @var{size} is a negative number, the new
 window is given @minus{}@var{size} lines (or columns).
@@ -1374,13 +1375,13 @@ Splitting Windows
 Sizes}).  Thus, it signals an error if splitting would result in making
 a window smaller than those variables specify.  However, a
 non-@code{nil} value for @var{size} causes those variables to be
-ignored; in that case, the smallest allowable window is considered to be
-one that has space for a text that is one line tall and/or two columns
-wide.
+ignored; in that case, the smallest allowable sizes are determined by
+the values of @code{window-safe-min-height} and
+@code{window-safe-min-width}.
 
 Hence, if @var{size} is specified, it's the caller's responsibility to
-check whether the emanating windows are large enough to encompass all of
-their decorations like a mode line or a scroll bar.  The function
+check whether the emanating windows are large enough to encompass all
+areas like a mode line or a scroll bar.  The function
 @code{window-min-size} (@pxref{Window Sizes}) can be used to determine
 the minimum requirements of @var{window} in this regard.  Since the new
 window usually inherits areas like the mode line or the scroll bar from
@@ -1399,14 +1400,62 @@ Splitting Windows
 window is placed on the left of @var{window}.  In both these cases,
 @var{size} specifies a total window width, in columns.
 
+As a rule, if @var{window} already forms a combination (@pxref{Windows
+and Frames}) that matches @var{side} (a horizontal combination matches
+@var{side} if it is @code{left} or @code{right}, a vertical combination
+matches @var{side} if it is @code{above} or @code{below}) and
+@code{window-combination-limit} (@pxref{Recombining Windows}) is
+@code{nil}, this function reuses @var{window}'s parent in the window
+tree as parent of the new window.
+
+However, if @var{window} is in a combination that does not match
+@var{side} or if @code{window-combination-limit} is non-@code{nil}, this
+function makes a new parent window that replaces @var{window} in the
+window tree and makes @var{window} and the new window its sole child
+windows.  This standard behavior can be overridden via the @var{refer}
+argument.
+
 The optional fourth argument @var{pixelwise}, if non-@code{nil}, means
 to interpret @var{size} in units of pixels, instead of lines and
 columns.
 
-If @var{window} is a live window, the new window inherits various
-properties from it, including margins and scroll bars.  If
-@var{window} is an internal window, the new window inherits the
-properties of the window selected within @var{window}'s frame.
+If the optional fifth argument @var{refer} is non-@code{nil}, it
+specifies a reference window used for setting up properties of the new
+window.  If non-@code{nil}, @var{refer} can be either a window or a cons
+cell of two windows.
+
+If @var{refer} is a cons cell, its @sc{car} has to specify a deleted,
+former live window - a window that has shown a buffer before - on the
+same frame as @var{window}.  That buffer must be still live.  The
+@sc{cdr} has to specify a deleted window that was, before its deletion,
+a parent window on the same frame as @var{window}.  In this case, rather
+then making new windows, this function replaces @var{window} with the
+@sc{cdr} of @var{refer} in the window tree and makes @var{window} and
+@var{refer}'s @sc{car} its new child windows.  Buffer, start and point
+positions of @var{refer}'s @sc{car} are set to the values they had
+immediately before @var{refer}'s @sc{car} was deleted the last time.
+Decorations and parameters remain unaltered from their values before
+@var{refer}'s @sc{car} and @sc{cdr} were deleted.
+
+Alternatively, @var{refer} may specify a deleted, former live window - a
+window that has shown a buffer before - on the same frame as
+@var{window}.  That buffer must be still live.  In this case, this
+function do not make a new window but rather makes @var{refer} live
+again and inserts it into the window tree at the position and with the
+sizes the new window would have been given.  Buffer, start and point
+positions of @var{refer} are set to the values they had immediately
+before @var{refer} was deleted the last time.  Decorations and
+parameters remain unaltered from their values before @var{refer} was
+deleted.  The parent of @var{refer} is then determined as if it were a
+window created anew.
+
+Otherwise, @var{refer} must specify a live window.  In this case, the
+new window will inherit properties like buffer, start and point
+positions and some decorations from @var{refer}.  If @var{refer} is
+@code{nil} or omitted, then if @var{window} is live, any such properties
+are inherited from @var{window}.  If, however, @var{window} is an
+internal window, the new window will inherit these properties from the
+window selected on @var{window}'s frame.
 
 The behavior of this function may be altered by the window parameters
 of @var{window}, so long as the variable
@@ -2048,6 +2097,157 @@ Recombining Windows
 windows.
 
 
+@node Resurrecting deleted windows
+@section Resurrecting deleted windows
+@cindex resurrecting deleted windows
+
+After a window has been deleted (@pxref{Deleting Windows}) it cannot be
+used any more by functions that require a valid window as their argument
+even if some Lisp variable still references that window.  When the last
+reference to a window has ceased to exist, the window's Lisp object will
+be eventually recycled by the garbage collector.
+
+There are two ways to resurrect a deleted window whose object has not
+been yet recycled by the collector: The first is to keep a reference to
+that window in a saved window configuration (@pxref{Window
+Configurations}) and then call @code{set-window-configuration} with that
+configuration as argument.  The second one is to keep a reference to
+that window in a variable or let-bind it and then use that reference as
+@var{refer} argument in @code{split-window} (@pxref{Splitting Windows}).
+
+The major difference between these two is that
+@code{set-window-configuration} restores the frame layout that existed
+before deleting the window.  The @code{split-window} approach, on the
+other hand, allows for arbitrary variations of the layout.
+
+Consider the following example starting with a frame containing a single
+window showing the buffer @file{*scratch*}:
+
+@example
+@group
+(let* ((old (selected-window))
+       (new (split-window old nil 'right))
+       overlay)
+  (with-current-buffer (get-buffer-create "*Messages*")
+    (set-window-buffer new (current-buffer))
+    (setq overlay (make-overlay (point-min) (point-max)))
+    (overlay-put overlay 'face 'highlight)
+    (overlay-put overlay 'window new)
+    (message "new %s parent %s" new (window-parent new))
+    (sit-for 3))
+  (delete-window new)
+  (setq new (split-window old nil 'left))
+  (set-window-buffer new (get-buffer-create "*Messages*"))
+  (format "new %s parent %s" new (window-parent new)))
+@end group
+@end example
+
+When you run that code in @file{*scratch*} it will first split the
+window showing @file{*scratch*} to display @file{*Messages*} in a new
+window on the right.  It also sets up an overlay with a window property
+to highlight the text of @file{*Messages*} in the new window and
+displays a message showing the new window and its parent in the window
+tree.  It then deletes the new window and resurrects it on the left of
+the @file{*scratch*} window again displaying a message showing the new
+window and its parent in the window tree.
+
+Note that both, new window and its parent have changed after moving the
+@file{*Messages*} window to the left.  Also, the highlighting disappears
+because any properties set up for the new window on the right are lost
+when the new window is re-created on the left.
+
+The following code uses the @var{refer} argument of @code{split-window}
+instead.
+
+@example
+@group
+(let* ((old (selected-window))
+       (new (split-window old nil 'right))
+       overlay)
+  (with-current-buffer (get-buffer-create "*Messages*")
+    (set-window-buffer new (current-buffer))
+    (setq overlay (make-overlay (point-min) (point-max)))
+    (overlay-put overlay 'face 'highlight)
+    (overlay-put overlay 'window new)
+    (message "new %s parent %s" new (window-parent new))
+    (sit-for 3))
+  (delete-window new)
+  (split-window old nil 'left nil new)
+  (format "new %s parent %s" new (window-parent new)))
+@end group
+@end example
+
+Note that all properties of the resurrected window like its decorations,
+parameters as well as any overlays with a window property are preserved
+as if that window had never been deleted.  The only things that changed
+are its position in the window tree and consequently the values returned
+by @code{window-left-child} of its parent window as well as the values
+returned by @code{window-prev-sibling} and @code{window-next-sibling} of
+the window and its sibling.
+
+The following code passes both, the new window on the right and its
+parent, via the @var{refer} argument to @code{split-window}: instead.
+
+@example
+@group
+(let* ((old (selected-window))
+       (new (split-window old nil 'right))
+       (parent (window-parent new))
+       overlay)
+  (with-current-buffer (get-buffer-create "*Messages*")
+    (set-window-buffer new (current-buffer))
+    (setq overlay (make-overlay (point-min) (point-max)))
+    (overlay-put overlay 'face 'highlight)
+    (overlay-put overlay 'window new)
+    (message "new %s parent %s" new (window-parent new))
+    (sit-for 3))
+  (delete-window new)
+  (split-window old nil 'left nil (cons new parent))
+  (format "new %s parent %s" new (window-parent new)))
+@end group
+@end example
+
+Note that the parent window has been resurrected along with the new
+window.
+
+Resurrecting dead windows is useful to preserve the identity of windows
+in actions that are supposed to do that like moving windows around on a
+frame or hiding them temporarily.  Any properties of such a window like
+its decorations, the buffer it has shown previously, that buffer's start
+and point position in the window, the window's dedicated status, its
+cursor type are left untouched and there's no need to recreate them from
+scratch.  For internal windows, the value of that window's combination
+limit is preerved which means that the window can be recombined
+(@pxref{Recombining Windows}) as before.
+
+Due to certain limitations in the way windows can be split, making a
+sequence of changes to the window structure can be more tricky.  It's
+still fairly simple to rotate three windows as follows:
+
+@example
+@group
+(let* ((old (selected-window))
+       (new-1 (split-window old nil 'right))
+       (parent-1 (window-parent old))
+       (new-2 (split-window new-1 nil 'below))
+       (parent-2 (window-parent new-2))
+       new)
+  (message "old %s (%s) .. new-1 %s (%s) .. new-2 %s (%s)"
+	   old (window-parent old)
+	   new-1 (window-parent new-1)
+	   new-2 (window-parent new-2))
+  (sit-for 3)
+  (delete-other-windows old)
+  (setq new (split-window old nil 'below nil (cons new-1 parent-1)))
+  (split-window new nil 'right nil (cons new-2 parent-2))
+  (format "old %s (%s) .. new-1 %s (%s) .. new-2 %s (%s)"
+	  old (window-parent old)
+	  new-1 (window-parent new-1)
+	  new-2 (window-parent new-2)))
+@end group
+@end example
+
+
 @node Cyclic Window Ordering
 @section Cyclic Ordering of Windows
 @cindex cyclic ordering of windows
@@ -7126,13 +7326,16 @@ Window Hooks
 window.
 
 @defun window-old-buffer &optional window
-This function returns the buffer shown in @var{window} at the last
-time window change functions were run for @var{window}'s frame.  If it
-returns @code{nil}, @var{window} has been created after that.  If it
-returns @code{t}, @var{window} was not shown at that time but has been
-restored from a previously saved window configuration afterwards.
-Otherwise, the return value is the buffer shown by @var{window} at
-that time.
+This function returns the buffer shown in @var{window} at the last time
+window change functions were run for @var{window}'s frame.  If it
+returns @code{nil}, @var{window} is either an internal window or has
+been created after that.  If it returns @code{t}, @var{window} was not
+shown at that time but has been restored from a previously saved window
+configuration afterwards.  Otherwise, the return value is the buffer
+shown by @var{window} at that time.  As a special case, if @var{window}
+has been deleted, this function returns the last buffer @var{window} had
+shown at that time.  @var{window} can be any window and defaults to the
+selected one.
 @end defun
 
 @defun window-old-pixel-width &optional window
diff --git a/lisp/window.el b/lisp/window.el
index b50770cbd7e..060716df936 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -5511,54 +5511,95 @@ window--combination-resizable
       (setq sibling (window-next-sibling sibling)))
     (/ size (1+ number))))
 
-(defun split-window (&optional window size side pixelwise)
+(defun split-window (&optional window size side pixelwise refer)
   "Make a new window adjacent to WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 Return the new window which is always a live window.
 
-Optional argument SIZE a positive number means make WINDOW SIZE
-lines or columns tall.  If SIZE is negative, make the new window
--SIZE lines or columns tall.  If and only if SIZE is non-nil, its
-absolute value can be less than `window-min-height' or
-`window-min-width'; so this command can make a new window as
-small as one line or two columns.  SIZE defaults to half of
-WINDOW's size.
+If the optional argument SIZE is a positive number, shrink WINDOW
+to SIZE lines or columns in order to accommodate the new window.
+If SIZE is a negative number, make the new window -SIZE lines or
+columns tall.  In both cases, the absolute value of SIZE can be
+less than `window-min-height' or `window-min-width'; so this
+function can make a new window as small as one line or two
+columns.  If SIZE is not a number, make the new window occupy
+half of WINDOW's size.
 
 Optional third argument SIDE nil (or `below') specifies that the
-new window shall be located below WINDOW.  SIDE `above' means the
-new window shall be located above WINDOW.  In both cases SIZE
+new window shall be made below WINDOW.  SIDE `above' means the
+new window shall be made above WINDOW.  In both cases SIZE
 specifies the new number of lines for WINDOW (or the new window
 if SIZE is negative) including space reserved for the mode and/or
-header line.
+header line, scroll bars and window dividers.
 
-SIDE t (or `right') specifies that the new window shall be
-located on the right side of WINDOW.  SIDE `left' means the new
-window shall be located on the left of WINDOW.  In both cases
-SIZE specifies the new number of columns for WINDOW (or the new
-window provided SIZE is negative) including space reserved for
-fringes and the scrollbar or a divider column.
+SIDE t (or `right') specifies that the new window shall be made
+on the right side of WINDOW.  SIDE `left' means the new window
+shall be made on the left of WINDOW.  In both cases, SIZE
+specifies the new number of columns for WINDOW (or the new window
+provided SIZE is negative) including any space reserved for
+fringes, scroll bar and window dividers.
 
 For compatibility reasons, SIDE `up' and `down' are interpreted
 as `above' and `below'.  Any other non-nil value for SIDE is
 currently handled like t (or `right').
 
+As a rule, if WINDOW already forms a combination that matches the SIDE
+parameter and `window-combination-limit' is nil, reuse WINDOW's parent
+in the window tree as parent of the new window.  If WINDOW is in a
+combination that is orthogonal to the SIDE parameter or if
+`window-combination-limit' is non-nil, make a new parent window that
+replaces WINDOW in the window tree and make WINDOW and the new window
+its sole child windows.  This standard behavior can be overridden via
+the REFER argument.
+
 PIXELWISE, if non-nil, means to interpret SIZE pixelwise.
 
+If the optional fifth argument REFER is non-nil, it specifies a
+reference window used for setting up properties of the new window.
+REFER can be either a window or a cons cell of two windows.
+
+If REFER is a cons cell, its car has to specify a deleted, former live
+window - a window that has shown a buffer before - on the same frame as
+WINDOW.  That buffer must be still live.  The cdr has to specify a
+deleted window that was a parent window on the same frame as WINDOW
+before it was deleted.  In this case, rather then making new windows,
+replace WINDOW with the cdr of REFER in the window tree and make WINDOW
+and REFER's car its new child windows.  Buffer, start and point
+positions of REFER's car are set to the values they had immediately
+before REFER's car was deleted the last time.  Decorations and
+parameters remain unaltered from their values before REFER's car and cdr
+were deleted.
+
+Alternatively REFER may specify a deleted, former live window - a window
+that has shown a buffer before - on the same frame as WINDOW.  In this
+case do not make a new window but rather make REFER live again and
+insert it into the window tree at the position and with the sizes the
+new window would have been given.  Buffer, start and point positions of
+REFER are set to the values they had immediately before REFER was
+deleted the last time.  Decorations and parameters remain unaltered from
+their values before REFER was deleted.  Throw an error if REFER's buffer
+has been deleted after REFER itself was deleted.
+
+Otherwise REFER must specify a live window.  In this case, the new
+window will inherit properties like buffer, start and point position and
+some decorations from REFER.  If REFER is nil or omitted, then if WINDOW
+is live, any such properties are inherited from WINDOW.  If, however,
+WINDOW is an internal window, the new window will inherit these
+properties from the window selected on WINDOW's frame.
+
 If the variable `ignore-window-parameters' is non-nil or the
 `split-window' parameter of WINDOW equals t, do not process any
-parameters of WINDOW.  Otherwise, if the `split-window' parameter
-of WINDOW specifies a function, call that function with all three
-arguments and return the value returned by that function.
-
-Otherwise, if WINDOW is part of an atomic window, \"split\" the
-root of that atomic window.  The new window does not become a
-member of that atomic window.
-
-If WINDOW is live, properties of the new window like margins and
-scrollbars are inherited from WINDOW.  If WINDOW is an internal
-window, these properties as well as the buffer displayed in the
-new window are inherited from the window selected on WINDOW's
-frame.  The selected window is not changed by this function."
+parameters of WINDOW.  Otherwise, if the `split-window' parameter of
+WINDOW specifies a function, call that function with the three first
+arguments WINDOW, SIZE and SIDE and return the value returned by that
+function.
+
+Otherwise, if WINDOW is part of an atomic window, \"split\" the root of
+that atomic window.  The new window does not become a member of that
+atomic window.
+
+The selected window and the selected window on WINDOW's frame are not
+changed by this function."
   (setq window (window-normalize-window window))
   (let* ((side (cond
 		((not side) 'below)
@@ -5598,14 +5639,14 @@ split-window
        ((and (window-parameter window 'window-atom)
 	     (setq atom-root (window-atom-root window))
 	     (not (eq atom-root window)))
-	(throw 'done (split-window atom-root size side pixelwise)))
+	(throw 'done (split-window atom-root size side pixelwise refer)))
        ;; If WINDOW's frame has a side window and WINDOW specifies the
        ;; frame's root window, split the frame's main window instead
        ;; (Bug#73627).
        ((and (eq window (frame-root-window frame))
 	     (window-with-parameter 'window-side nil frame))
 	(throw 'done (split-window (window-main-window frame)
-				   size side pixelwise)))
+				   size side pixelwise refer)))
        ;; If WINDOW is a side window or its first or last child is a
        ;; side window, throw an error unless `window-combination-resize'
        ;; equals 'side.
@@ -5644,8 +5685,8 @@ split-window
 		   (window-combined-p window horizontal)))
 	     ;; 'old-pixel-size' is the current pixel size of WINDOW.
 	     (old-pixel-size (window-size window horizontal t))
-	     ;; 'new-size' is the specified or calculated size of the
-	     ;; new window.
+	     ;; 'new-pixel-size' is the specified or calculated size
+	     ;; of the new window.
 	     new-pixel-size new-parent new-normal)
 	(cond
 	 ((not pixel-size)
@@ -5766,8 +5807,9 @@ split-window
 	   window (- (if new-parent 1.0 (window-normal-size window horizontal))
 		     new-normal)))
 
-	(let* ((new (split-window-internal window new-pixel-size side new-normal)))
-	  (window--pixel-to-total frame horizontal)
+	(let ((new (split-window-internal
+		    window new-pixel-size side new-normal refer)))
+          (window--pixel-to-total frame horizontal)
 	  ;; Assign window-side parameters, if any.
 	  (cond
 	   ((eq window-combination-resize 'side)
diff --git a/src/window.c b/src/window.c
index 34968ac824f..6c7749ddae1 100644
--- a/src/window.c
+++ b/src/window.c
@@ -652,15 +652,16 @@ DEFUN ("window-buffer", Fwindow_buffer, Swindow_buffer, 0, 1, 0,
 
 DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
        doc: /* Return the old buffer displayed by WINDOW.
-WINDOW must be a live window and defaults to the selected one.
+WINDOW can be any window and defaults to the selected one.
 
 The return value is the buffer shown in WINDOW at the last time window
-change functions were run.  It is nil if WINDOW was created after
-that.  It is t if WINDOW has been restored from a window configuration
-after that.  */)
+change functions were run or WINDOW is a former live window that was
+deleted.  It is nil if WINDOW was created after that.  It is t if WINDOW
+has been restored from a window configuration after that.  It is always
+nil if WINDOW is an internal window.  */)
   (Lisp_Object window)
 {
-  struct window *w = decode_live_window (window);
+  struct window *w = decode_any_window (window);
 
   return (NILP (w->old_buffer)
 	  /* A new window.  */
@@ -668,8 +669,8 @@ DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
 	  : (w->change_stamp != WINDOW_XFRAME (w)->change_stamp)
 	  /* A window restored from a configuration.  */
 	  ? Qt
-	  /* A window that was live the last time seen by window
-	     change functions.  */
+	  /* A window that was live the last time seen by window change
+	     functions or was deleted.  */
 	  : w->old_buffer);
 }
 
@@ -4491,45 +4492,6 @@ allocate_window (void)
 				       PVEC_WINDOW);
 }
 
-/* Make new window, have it replace WINDOW in window-tree, and make
-   WINDOW its only vertical child (HORFLAG means make WINDOW its only
-   horizontal child).   */
-static void
-make_parent_window (Lisp_Object window, bool horflag)
-{
-  Lisp_Object parent;
-  register struct window *o, *p;
-
-  o = XWINDOW (window);
-  p = allocate_window ();
-  memcpy ((char *) p + sizeof (union vectorlike_header),
-	  (char *) o + sizeof (union vectorlike_header),
-	  word_size * VECSIZE (struct window));
-  /* P's buffer slot may change from nil to a buffer...  */
-  adjust_window_count (p, 1);
-  XSETWINDOW (parent, p);
-
-  p->sequence_number = ++sequence_number;
-
-  replace_window (window, parent, true);
-
-  wset_next (o, Qnil);
-  wset_prev (o, Qnil);
-  wset_parent (o, parent);
-  /* ...but now P becomes an internal window.  */
-  wset_start (p, Qnil);
-  wset_pointm (p, Qnil);
-  wset_old_pointm (p, Qnil);
-  wset_buffer (p, Qnil);
-  wset_combination (p, horflag, window);
-  wset_combination_limit (p, Qnil);
-  /* Reset any previous and next buffers of p which have been installed
-     by the memcpy above.  */
-  wset_prev_buffers (p, Qnil);
-  wset_next_buffers (p, Qnil);
-  wset_window_parameters (p, Qnil);
-}
-
 /* Make new window from scratch.  */
 Lisp_Object
 make_window (void)
@@ -5073,7 +5035,7 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
 }
 
 
-DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 4, 0,
+DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal, 4, 5, 0,
        doc: /* Split window OLD.
 Second argument PIXEL-SIZE specifies the number of pixels of the
 new window.  It must be a positive integer.
@@ -5088,32 +5050,33 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 the right side of WINDOW.  SIDE `left' means the new window shall be
 located on the left of WINDOW.  In both cases PIXEL-SIZE specifies the
 width of the new window including space reserved for fringes and the
-scrollbar or a divider column.
+scroll bar or a divider column.
 
 Fourth argument NORMAL-SIZE specifies the normal size of the new window
-according to the SIDE argument.
+according to the SIDE argument.  Optional fifth argument REFER is as for
+'split-window'.
 
 The new pixel and normal sizes of all involved windows must have been
 set correctly.  See the code of `split-window' for how this is done.  */)
-  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side, Lisp_Object normal_size)
-{
-  /* OLD (*o) is the window we have to split.  (*p) is either OLD's
-     parent window or an internal window we have to install as OLD's new
-     parent.  REFERENCE (*r) must denote a live window, or is set to OLD
-     provided OLD is a leaf window, or to the frame's selected window.
-     NEW (*n) is the new window created with some parameters taken from
-     REFERENCE (*r).  */
-  Lisp_Object new, frame, reference;
-  struct window *o, *p, *n, *r, *c;
-  struct frame *f;
+  (Lisp_Object old, Lisp_Object pixel_size, Lisp_Object side,
+   Lisp_Object normal_size, Lisp_Object refer)
+{
+  /* OLD (*o) is the window to split.  REFER (*r) is a reference window,
+     either an arbitrary live window or a former live, now deleted
+     window on the same frame as OLD.  NEW (*n) is the new window
+     created anew or resurrected from REFER (*r), if specified.  *p
+     refers either to OLD's parent window that will become NEW's parent
+     window too or to a new internal window that becomes OLD's and NEW's
+     new parent.  */
+  struct window *o = decode_valid_window (old);
+  Lisp_Object frame = WINDOW_FRAME (o);
+  struct frame *f = XFRAME (frame);
+  struct window *p, *n, *r, *c;
   bool horflag
     /* HORFLAG is true when we split side-by-side, false otherwise.  */
     = EQ (side, Qt) || EQ (side, Qleft) || EQ (side, Qright);
-
-  CHECK_WINDOW (old);
-  o = XWINDOW (old);
-  frame = WINDOW_FRAME (o);
-  f = XFRAME (frame);
+  Lisp_Object new, parent = Qnil;
+  bool dead = false;
 
   CHECK_FIXNUM (pixel_size);
   EMACS_INT total_size
@@ -5131,14 +5094,74 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	   ? WINDOW_VERTICAL_COMBINATION_P (XWINDOW (o->parent))
 	   : WINDOW_HORIZONTAL_COMBINATION_P (XWINDOW (o->parent))));
 
-  /* We need a live reference window to initialize some parameters.  */
-  if (WINDOW_LIVE_P (old))
-    /* OLD is live, use it as reference window.  */
-    reference = old;
+  /* Set up reference window.  */
+  if (NILP (refer))
+    {
+      if (WINDOW_LIVE_P (old))
+	/* OLD is live, use it as reference window.  */
+	refer = old;
+      else
+	/* Use the frame's selected window as reference window.  */
+	refer = FRAME_SELECTED_WINDOW (f);
+
+      r = XWINDOW (refer);
+    }
+  else if (CONSP (refer))
+    {
+      /* If REFER is a cons, then its car must be a deleted, former live
+	 window and its cdr must be a deleted former parent window.  Set
+	 PARENT to the cdr of REFER and REFER to its car.  WINDOW and
+	 REFER end up as the sole children of PARENT which replaces
+	 WINDOW in the window tree.  As a special case, if REFER's cdr
+	 is t, reuse REFER's car's old parent as new parent provided it
+	 is a deleted fromer parent window.  */
+      parent = Fcdr (refer);
+      refer = Fcar (refer);
+      r = decode_any_window (refer);
+
+      if (!NILP (r->contents) || !BUFFERP (r->old_buffer))
+	error ("REFER's car must specify a deleted, former live window");
+      else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	error ("The buffer formerly shown by REFER's car has been killed");
+      else if (!EQ (r->frame, frame))
+	error ("REFER's car must specify a window on same frame as WINDOW");
+
+      if (EQ (parent, Qt))
+	/* If REFER's cdr is t, use the old parent of REFER's car as new
+	   parent.  */
+	parent = r->parent;
+
+      p = decode_any_window (parent);
+
+      if (!NILP (p->contents) || BUFFERP (p->old_buffer))
+	error ("REFER's cdr must specify a deleted, former parent window");
+      else if (!EQ (p->frame, frame))
+	error ("REFER's cdr must specify window on same frame as WINDOW");
+
+      dead = true;
+    }
   else
-    /* Use the frame's selected window as reference window.  */
-    reference = FRAME_SELECTED_WINDOW (f);
-  r = XWINDOW (reference);
+    {
+      r = decode_any_window (refer);
+
+      if (NILP (r->contents))
+	/* Presumably a deleted, former live window.  Check whether its
+	   contents can be used.  */
+	{
+	  if (!BUFFERP (r->old_buffer))
+	    error ("REFER must specify a former live window (must have shown a buffer)");
+	  else if (!BUFFER_LIVE_P (XBUFFER (r->old_buffer)))
+	    error ("The buffer formerly shown by REFER has been killed");
+	  else if (!EQ (r->frame, frame))
+	    error ("REFER must specify a window on same frame as WINDOW");
+
+	  dead = true;
+	}
+      else if (!NILP (parent))
+	error ("If REFER is a cons, its car must not specify a live window");
+      else if (!WINDOW_LIVE_P (refer))
+	error ("REFER is not a live window (does not show a buffer)");
+    }
 
   /* The following bugs are caught by `split-window'.  */
   if (MINI_WINDOW_P (o))
@@ -5149,16 +5172,18 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     /* `window-combination-resize' non-nil means try to resize OLD's siblings
        proportionally.  */
     {
-      p = XWINDOW (o->parent);
+      struct window *op = XWINDOW (o->parent);
+
       /* Temporarily pretend we split the parent window.  */
       wset_new_pixel
-	(p, make_fixnum ((horflag ? p->pixel_width : p->pixel_height)
+	(op, make_fixnum ((horflag ? op->pixel_width : op->pixel_height)
 			 - XFIXNUM (pixel_size)));
-      if (!window_resize_check (p, horflag))
+      if (!window_resize_check (op, horflag))
 	error ("Window sizes don't fit");
       else
 	/* Undo the temporary pretension.  */
-	wset_new_pixel (p, make_fixnum (horflag ? p->pixel_width : p->pixel_height));
+	wset_new_pixel
+	  (op, make_fixnum (horflag ? op->pixel_width : op->pixel_height));
     }
   else
     {
@@ -5178,8 +5203,24 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
       Lisp_Object new_normal
 	= horflag ? o->normal_cols : o->normal_lines;
 
-      make_parent_window (old, horflag);
-      p = XWINDOW (o->parent);
+      if (NILP (parent))
+	/* This is the crux of the old make_parent_window.  */
+	{
+	  p = allocate_window ();
+	  XSETWINDOW (parent, p);
+	  p->sequence_number = ++sequence_number;
+	  wset_frame (p, frame);
+	}
+      else
+	/* Pacify GCC.  */
+	p = XWINDOW (parent);
+
+      replace_window (old, parent, true);
+      wset_next (o, Qnil);
+      wset_prev (o, Qnil);
+      wset_parent (o, parent);
+      wset_combination (p, horflag, old);
+
       if (EQ (Vwindow_combination_limit, Qt))
 	/* Store t in the new parent's combination_limit slot to avoid
 	   that its children get merged into another window.  */
@@ -5195,7 +5236,12 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
     p = XWINDOW (o->parent);
 
   fset_redisplay (f);
-  new = make_window ();
+
+  if (dead)
+    new = refer;
+  else
+    new = make_window ();
+
   n = XWINDOW (new);
   wset_frame (n, frame);
   wset_parent (n, o->parent);
@@ -5222,16 +5268,19 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   n->window_end_valid = false;
   n->last_cursor_vpos = 0;
 
-  /* Get special geometry settings from reference window.  */
-  n->left_margin_cols = r->left_margin_cols;
-  n->right_margin_cols = r->right_margin_cols;
-  n->left_fringe_width = r->left_fringe_width;
-  n->right_fringe_width = r->right_fringe_width;
-  n->fringes_outside_margins = r->fringes_outside_margins;
-  n->scroll_bar_width = r->scroll_bar_width;
-  n->scroll_bar_height = r->scroll_bar_height;
-  wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
-  wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+  if (!dead)
+    {
+      /* Get special geometry settings from reference window.  */
+      n->left_margin_cols = r->left_margin_cols;
+      n->right_margin_cols = r->right_margin_cols;
+      n->left_fringe_width = r->left_fringe_width;
+      n->right_fringe_width = r->right_fringe_width;
+      n->fringes_outside_margins = r->fringes_outside_margins;
+      n->scroll_bar_width = r->scroll_bar_width;
+      n->scroll_bar_height = r->scroll_bar_height;
+      wset_vertical_scroll_bar_type (n, r->vertical_scroll_bar_type);
+      wset_horizontal_scroll_bar_type (n, r->horizontal_scroll_bar_type);
+    }
 
   /* Directly assign orthogonal coordinates and sizes.  */
   if (horflag)
@@ -5260,6 +5309,7 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
 	sum = sum + XFIXNUM (c->new_total);
       c = NILP (c->next) ? 0 : XWINDOW (c->next);
     }
+
   wset_new_total (n, make_fixnum ((horflag
 				   ? p->total_cols
 				   : p->total_lines)
@@ -5267,10 +5317,30 @@ DEFUN ("split-window-internal", Fsplit_window_internal, Ssplit_window_internal,
   wset_new_normal (n, normal_size);
 
   block_input ();
+
+  if (dead)
+    {
+      /* Get dead window back its old buffer and markers.  */
+      wset_buffer (n, n->old_buffer);
+      set_marker_restricted
+	(n->start, make_fixnum (XMARKER (n->start)->charpos), n->contents);
+      set_marker_restricted
+	(n->pointm, make_fixnum (XMARKER (n->pointm)->charpos), n->contents);
+      set_marker_restricted
+	(n->old_pointm, make_fixnum (XMARKER (n->old_pointm)->charpos),
+	 n->contents);
+
+      Vwindow_list = Qnil;
+      /* Remove window from the table of dead windows.  */
+      Fremhash (make_fixnum (n->sequence_number),
+		window_dead_windows_table);
+    }
+
   window_resize_apply (p, horflag);
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  */
+
   set_window_buffer (new, r->contents, true, true);
+
   FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();
 
@@ -5368,6 +5438,8 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
       else
 	{
+	  /* Store WINDOW's buffer in old_buffer.  */
+	  wset_old_buffer (w, w->contents);
 	  unshow_buffer (w);
 	  unchain_marker (XMARKER (w->pointm));
 	  unchain_marker (XMARKER (w->old_pointm));
@@ -7712,6 +7784,8 @@ delete_all_child_windows (Lisp_Object window)
     }
   else if (BUFFERP (w->contents))
     {
+      /* Store WINDOW's buffer in old_buffer.  */
+      wset_old_buffer (w, w->contents);
       unshow_buffer (w);
       unchain_marker (XMARKER (w->pointm));
       unchain_marker (XMARKER (w->old_pointm));
@@ -9064,12 +9138,9 @@ syms_of_window (void)
     doc: /* Hash table of dead windows.
 Each entry in this table maps a window number to a window object.
 Entries are added by `delete-window-internal' and are removed by the
-garbage collector.
-
-This table is maintained by code in window.c and is made visible in
-Elisp for testing purposes only.  */);
+garbage collector.  */);
   window_dead_windows_table
-    = CALLN (Fmake_hash_table, QCweakness, Qt);
+    = CALLN (Fmake_hash_table, QCweakness, Qvalue);
 
   defsubr (&Sselected_window);
   defsubr (&Sold_selected_window);

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

* Re: Add function to rotate/transpose all windows
  2024-11-09  2:14                                                                                       ` Pranshu Sharma
@ 2024-11-09  8:48                                                                                         ` martin rudalics
  0 siblings, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-11-09  8:48 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 >> I never used 'flatten-list' and I see only one use of it in the Emacs
 >> sources.
 >
 > Just out of curiosity, where?

In the function 'isearch-search-fun-in-noncontiguous-region' in
isearch.el.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-11-09  8:48                                                                                       ` martin rudalics
@ 2024-11-09 10:55                                                                                         ` Pranshu Sharma
  2024-11-09 18:06                                                                                           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-09 10:55 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> This is definitively an old version.  The new version outputs something
> like

Turns out it was.  I think file you attached in this email was the wrong
one, but the one in the previous email works fine.

> Try again with the code I attached.

It works, but how did you get over this limitation?

Other then that, I don't see any problem with this code.  I think all it
needs is some polishing, and it should be easy enough to add the support
for flipping windows.  wdyt?



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

* Re: Add function to rotate/transpose all windows
  2024-11-09 10:55                                                                                         ` Pranshu Sharma
@ 2024-11-09 18:06                                                                                           ` martin rudalics
  2024-11-10 10:09                                                                                             ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-09 18:06 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > It works, but how did you get over this limitation?

Which limitation?  The one that parent windows get resized?  I do use
the normal size of windows (a float between 0 and 1) which does not
change when you make the frame or some ancestor window smaller or
larger.  By design, it should work.  Although when rotating we "flip"
the normal sizes - what was a normal height becomes a normal width and
vice-versa.  Slightly scary ...

 > Other then that, I don't see any problem with this code.  I think all it
 > needs is some polishing, and it should be easy enough to add the support
 > for flipping windows.

For some value of easy.  Can you try to do that?  You would have to
write it from scratch since the rotation code will hardly suit.

Note also that the rotation code

- always rotates the entire frame (you want it to rotate arbitrary
   parent windows only)

- doesn't care about side windows (a similar problem - it should rotate
   the main window instead)

- might fail with atomic windows

- doesn't care about fixed size windows

- might fail with small windows.

The latter two probably mean that we should run the algorithm with fixed
sizes and minimum window sizes in place first.  If 'split-window'
complains, re-run the algorithm ignoring fixed-size windows and minimum
sizes.

And always keep in mind that once our functions exist, people will use
them on-the-fly, expecting them to "just work". They won't forgive the
smallest misbehavior.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-11-09 18:06                                                                                           ` martin rudalics
@ 2024-11-10 10:09                                                                                             ` Pranshu Sharma
  2024-11-10 16:36                                                                                               ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-10 10:09 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> Which limitation?  The one that parent windows get resized?  I do use
> the normal size of windows (a float between 0 and 1) which does not
> change when you make the frame or some ancestor window smaller or
> larger.  By design, it should work.  Although when rotating we "flip"
> the normal sizes - what was a normal height becomes a normal width and
> vice-versa.  Slightly scary ...

The on where I showed in earlier screenshot, where the parent window
being split gets messed up.

>> Other then that, I don't see any problem with this code.  I think
>> all it
>> needs is some polishing, and it should be easy enough to add the
>> support
>> for flipping windows.
>
> For some value of easy.  Can you try to do that?  You would have to
> write it from scratch since the rotation code will hardly suit.

Ok, I'll first try to get the other functions working with the existing
code, and if it starts going south I'll start from scratch.

> Note also that the rotation code
>
> - always rotates the entire frame (you want it to rotate arbitrary
>   parent windows only)
>
> - doesn't care about side windows (a similar problem - it should
> rotate
>   the main window instead)
>
> - might fail with atomic windows

What's an atomic window? I read the manual but still couldn't understand

> - doesn't care about fixed size windows
>
> - might fail with small windows.

Highly unlikley, I tried it with very small splits and it worked.

> The latter two probably mean that we should run the algorithm with
> fixed sizes and minimum window sizes in place first.  If
> 'split-window' complains, re-run the algorithm ignoring fixed-size
> windows and minimum sizes.

What is the expected thing to happen in fixed size windows?

> And always keep in mind that once our functions exist, people will use
> them on-the-fly, expecting them to "just work". They won't forgive the
> smallest misbehavior.

ngl, when you say it like that it sounds like I am making food for a kim
jong ung



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

* Re: Add function to rotate/transpose all windows
  2024-11-10 10:09                                                                                             ` Pranshu Sharma
@ 2024-11-10 16:36                                                                                               ` martin rudalics
  2024-11-11 14:47                                                                                                 ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-10 16:36 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > The on where I showed in earlier screenshot, where the parent window
 > being split gets messed up.

IIRC I fixed that earlier by binding 'window-combination-limit'
appropriately.

 >> - might fail with atomic windows
 >
 > What's an atomic window? I read the manual but still couldn't understand>> And always keep in mind that once our functions exist, people will use
 >> them on-the-fly, expecting them to "just work". They won't forgive the
 >> smallest misbehavior.
 >


An atomic window is a parent window - considered the root of the atomic
window - such that all its descendants have the 'window-atom' parameter
set to t.  'split-window' when called with any of these windows as first
argument makes the new window always on a side of the atomic window.
With other words it splits the root instead.  Also, deleting one of
these windows deletes the root instead and deleting all other windows
deletes all other windows but the root.

The idea of an atomic window is that although it is build from internal
and live windows, it behaves like a live window for the operations
mentioned above.  It's the smallest unit for them and you cannot split
it just like you cannot split an atom chemically.

I added a 'no-rotate' parameter that can be used for atomic windows so
they do not rotate inside.  But I have not tested it well yet.

 >> - doesn't care about fixed size windows
 >>
 >> - might fail with small windows.
 >
 > Highly unlikley, I tried it with very small splits and it worked.

Note that I've bound 'window-min-width' and 'window-min-height' to zero.
'split-window' will automatically choose the nearest admissible size but
it will fail in a trivial case where you do say a couple of C-x 3 in a
frame that is 80 columns wide but only two lines high.  Hopefully the
'condition-case' will catch them all - precalculating sizes and
rejecting a rotate when they do not fit would be no fun.

 >> The latter two probably mean that we should run the algorithm with
 >> fixed sizes and minimum window sizes in place first.  If
 >> 'split-window' complains, re-run the algorithm ignoring fixed-size
 >> windows and minimum sizes.
 >
 > What is the expected thing to happen in fixed size windows?

That they do not change their height, width or both however you rotate
them.  If you do say C-h f setq and you have 'temp-buffer-resize-mode'
turned on, Emacs will try to resize the help window and later changes of
the window structure will usually try to keep the size of the help
window fixed, if that is possible at all.

 > ngl, when you say it like that it sounds like I am making food for a kim
 > jong ung

It's more like making a diet plan for him.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-11-10 16:36                                                                                               ` martin rudalics
@ 2024-11-11 14:47                                                                                                 ` Pranshu Sharma
  2024-11-11 16:55                                                                                                   ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-11 14:47 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

> IIRC I fixed that earlier by binding 'window-combination-limit'
> appropriately.

I still wasn't able to get it work using that, but what I think will
work is `window-combination-resize'.

>>> - might fail with atomic windows
>>
>> What's an atomic window? I read the manual but still couldn't
>> understand>> And always keep in mind that once our functions exist,
>> people will use
>>> them on-the-fly, expecting them to "just work". They won't forgive
>>> the
>>> smallest misbehavior.
>>
>
>
> An atomic window is a parent window - considered the root of the
> atomic window - such that all its descendants have the 'window-atom'
> parameter set to t.  'split-window' when called with any of these
> windows as first argument makes the new window always on a side of the
> atomic window.  With other words it splits the root instead.  Also,
> deleting one of these windows deletes the root instead and deleting
> all other windows deletes all other windows but the root.
>
> The idea of an atomic window is that although it is build from
> internal and live windows, it behaves like a live window for the
> operations mentioned above.  It's the smallest unit for them and you
> cannot split it just like you cannot split an atom chemically.

I see, it's a good idea.  Sadly `gnus-use-atomic-windows' does not work
when horizontal and vertical splits are mixed, probably going to report
this bug later, but as am emacs user and therefore denier of
multithreading, I'll wait till this is resolved first.  But I got to
admit, this was probably not the best name, but then again I can't think
of a better name.

> I added a 'no-rotate' parameter that can be used for atomic windows so
> they do not rotate inside.  But I have not tested it well yet.

I cannot imagine this going well at all, but I'll test later.

>>> - doesn't care about fixed size windows
>>>
>>> - might fail with small windows.
>>
>> Highly unlikley, I tried it with very small splits and it worked.
>
> Note that I've bound 'window-min-width' and 'window-min-height' to
> zero.
> 'split-window' will automatically choose the nearest admissible size
> but
> it will fail in a trivial case where you do say a couple of C-x 3 in a
> frame that is 80 columns wide but only two lines high.  Hopefully the
> 'condition-case' will catch them all - precalculating sizes and
> rejecting a rotate when they do not fit would be no fun.

How about setting a window configuration, then splitting the window, and
it goes boom-boom,  we can just revert to that?

>>> The latter two probably mean that we should run the algorithm with
>>> fixed sizes and minimum window sizes in place first.  If
>>> 'split-window' complains, re-run the algorithm ignoring fixed-size
>>> windows and minimum sizes.
>>
>> What is the expected thing to happen in fixed size windows?
>
> That they do not change their height, width or both however you rotate
> them.  If you do say C-h f setq and you have 'temp-buffer-resize-mode'
> turned on, Emacs will try to resize the help window and later changes
> of
> the window structure will usually try to keep the size of the help
> window fixed, if that is possible at all.

It is simply not possible then to rotate or transpose windows if they
are fixed size, imagine a fixed size top level split function, how would
that get rotated?

I think maybe we can do a map windows at the start to check if any are
fixed size, and if yes then we stop.

So so far, my game plan is I won't do any of the window alist stuff,
rather just the normal window-pixel-tree but include internal windows as
well.

>> ngl, when you say it like that it sounds like I am making food for a
>> kim
>> jong ung
>
> It's more like making a diet plan for him.

If I was Kim, I wouldn't mind some extra ice cream at the cost of just
another kg



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

* Re: Add function to rotate/transpose all windows
  2024-11-11 14:47                                                                                                 ` Pranshu Sharma
@ 2024-11-11 16:55                                                                                                   ` martin rudalics
  2024-11-12 13:50                                                                                                     ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-11 16:55 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 >> IIRC I fixed that earlier by binding 'window-combination-limit'
 >> appropriately.
 >
 > I still wasn't able to get it work using that, but what I think will
 > work is `window-combination-resize'.

This won't do what you want - it will simply give all windows the same
relative size, something 'balance-windows' does.  But you want to
preserve the relative sizes of windows.  Try with my algorithm - it
preserves proportional sizes quite well.

 > How about setting a window configuration, then splitting the window, and
 > it goes boom-boom,  we can just revert to that?

That's exactly what my algorithm does: It automatically restores the old
configuration and prints a message about what went wrong.>
 >>> ngl, when you say it like that it sounds like I am making food for a
 >>> kim
 >>> jong ung
 >>
 >> It's more like making a diet plan for him.
 >
 > If I was Kim, I wouldn't mind some extra ice cream at the cost of just
 > another kg


 > It is simply not possible then to rotate or transpose windows if they
 > are fixed size, imagine a fixed size top level split function, how would
 > that get rotated?

Emcas ignores the fixed-sizeness when it has no other choice.  Like when
you delete a window and the only remaining window on the frame is of
fixed size.  But first of all flipping windows should always preserve
fixed sizes.  And rotating windows should at least try to do that.

 > I think maybe we can do a map windows at the start to check if any are
 > fixed size, and if yes then we stop.
 >
 > So so far, my game plan is I won't do any of the window alist stuff,
 > rather just the normal window-pixel-tree but include internal windows as
 > well.

The alist approach has one decisive advantage: When you later find out
that you would like to know a specific window property before you delete
windows you can simply add that property.  With the pixel-tree approach
you will have to add it to the pixel tree and get one "a" more in the
ca..rs you use for accessing the tree's elements.

And, as you can see from the function 'window-alist-check', it's fairly
easy to compare the alists before and after rotation.  Try to write a
similar comparison algorithm with pixel-trees.

martin




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

* Re: Add function to rotate/transpose all windows
  2024-11-11 16:55                                                                                                   ` martin rudalics
@ 2024-11-12 13:50                                                                                                     ` Pranshu Sharma
  2024-11-12 17:46                                                                                                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-12 13:50 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>>> IIRC I fixed that earlier by binding 'window-combination-limit'
>>> appropriately.
>>
>> I still wasn't able to get it work using that, but what I think will
>> work is `window-combination-resize'.
>
> This won't do what you want - it will simply give all windows the same
> relative size, something 'balance-windows' does.  But you want to
> preserve the relative sizes of windows.  Try with my algorithm - it
> preserves proportional sizes quite well.

I see, I don't quite get how your algorithim works, so I'll just use the
reverse list and split at root window approach which also works.  They
should have same result, but this one will have less moving parts.

>
>> It is simply not possible then to rotate or transpose windows if they
>> are fixed size, imagine a fixed size top level split function, how would
>> that get rotated?
>
> Emcas ignores the fixed-sizeness when it has no other choice.  Like when
> you delete a window and the only remaining window on the frame is of
> fixed size.  But first of all flipping windows should always preserve
> fixed sizes.  And rotating windows should at least try to do that.
>

Flipping windows won't change the size using the current approach, but
the window will be split to match it's old height, idk if that will give
bug or not.  I would rather it just simply doesn't work with fied
windows, instead of confusing the user who probably doesnt even know
emacs has fixed windows.

>> I think maybe we can do a map windows at the start to check if any are
>> fixed size, and if yes then we stop.
>>
>> So so far, my game plan is I won't do any of the window alist stuff,
>> rather just the normal window-pixel-tree but include internal windows as
>> well.
>
> The alist approach has one decisive advantage: When you later find out
> that you would like to know a specific window property before you delete
> windows you can simply add that property.  With the pixel-tree approach
> you will have to add it to the pixel tree and get one "a" more in the
> ca..rs you use for accessing the tree's elements.

wdym by the last sentence?

Using my approach, I will anyways be able to modify the return value of
window-pixel-sizes while checking for fixed size windows, but it
probably won't be in alist form.  This will allow me to lose the helper
functions like window-alist-parent.

Also, the only thing I'm checking for is if a window is atomic and fixed
size, right?

to check the fixed size, I'll just use window-fixed-size-p, and to check
if atomic (window-parameter window 'window-atom).

I think the window atomic facility should hve more interactive commands,
can imagine it being quite useful.



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

* Re: Add function to rotate/transpose all windows
  2024-11-12 13:50                                                                                                     ` Pranshu Sharma
@ 2024-11-12 17:46                                                                                                       ` martin rudalics
  2024-11-16 13:36                                                                                                         ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-12 17:46 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > Flipping windows won't change the size using the current approach, but
 > the window will be split to match it's old height, idk if that will give
 > bug or not.  I would rather it just simply doesn't work with fied
 > windows, instead of confusing the user who probably doesnt even know
 > emacs has fixed windows.

Flipping should preserve the size of all windows automatically.  But
better make sure that it does.

 >> With the pixel-tree approach
 >> you will have to add it to the pixel tree and get one "a" more in the
 >> ca..rs you use for accessing the tree's elements.
 >
 > wdym by the last sentence?

After you deleted the other windows you can no more access properties of
the deleted windows like their parent, combination-limit, size,
fixed-sizeness, atomicity and other properties.  So IIUC you have to
store all these in the pixel-tree and access them from there.  This
means that you will probably need more deeply nested cadr, caadr, caddr
or nth calls to access those elements in the pixel trees.

 > Also, the only thing I'm checking for is if a window is atomic and fixed
 > size, right?
 >
 > to check the fixed size, I'll just use window-fixed-size-p,

It's 'window-size-fixed-p' and it does not work for windows that have
been deleted.  'window-fixed-size-p' consults only the buffer-local
value of the variable 'window-size-fixed' for a window's buffer.  It's
used in 'balance-windows-area' only.

 > and to check
 > if atomic (window-parameter window 'window-atom).

Yes.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-11-12 17:46                                                                                                       ` martin rudalics
@ 2024-11-16 13:36                                                                                                         ` Pranshu Sharma
  2024-11-16 16:54                                                                                                           ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-16 13:36 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

>
> After you deleted the other windows you can no more access properties of
> the deleted windows like their parent, combination-limit, size,
> fixed-sizeness, atomicity and other properties.  So IIUC you have to
> store all these in the pixel-tree and access them from there.  This
> means that you will probably need more deeply nested cadr, caadr, caddr
> or nth calls to access those elements in the pixel trees.
>

Ok, I played around with the splitting parent windows approach.,but
foudn it goes lil pump when working with more complex splits, see screenshot
attached of what happens when calling your algorithim on a more complex
split.

So I've tried going back with the inital approach that does not split
parent windows, but it gives me the "error: "REFER’s cdr must specify a
deleted, former parent window", when REFER's cdr is the new
(window-tree-pixel-sizes)'s cdar, which should always be a prent window.
Search for 'spew' in the file I attached to see where this is happing.


[-- Attachment #2: error --]
[-- Type: image/png, Size: 291505 bytes --]

[-- Attachment #3: window-transpose.el --]
[-- Type: application/emacs-lisp, Size: 8951 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-11-16 13:36                                                                                                         ` Pranshu Sharma
@ 2024-11-16 16:54                                                                                                           ` martin rudalics
  2024-11-17  2:45                                                                                                             ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-16 16:54 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

When I set up my frame thusly

(defun window-transpose-setup ()
   (interactive)
   (let ((foo (get-buffer-create "*foo*"))
	window)
     (delete-other-windows)
     (setq window (split-window nil nil t))
     (set-window-buffer window foo)
     (split-window window)
     (setq window (split-window window nil t))
     (split-window window)
     (select-window window)))

and then run 'rotate-windows-clockwise' I'm told

Debugger entered--Lisp error: (wrong-type-argument window-valid-p #<window 44>)
   window-pixel-height(#<window 44>)
   window--transpose-1(((nil . #<window 47>) (979 . 1680) (#<window 44> (979 . 840)) ((t . #<window 49>) (979 . 840) ((nil . #<window 51>) (490 . 840) (#<window 48> (490 . 420)) ((t . #<window 53>) (490 . 420) (#<window 52 on *foo*> (245 . 420)) (#<window 54> (245 . 420)))) (#<window 50> (489 . 840)))) #<window 44> (left . below) nil)
   window--transpose(#<window 47> (left . below) nil)
   rotate-windows-clockwise(nil)
   funcall-interactively(rotate-windows-clockwise nil)
   call-interactively(rotate-windows-clockwise nil nil)
   command-execute(rotate-windows-clockwise)

which is one of the problems I tried to make you aware of.  IIUC window
44 is a deleted parent window and you try to get its pixel height.  But
Emacs doesn't allow to retrieve the width or height of a deleted window.

Again I can only suggest to set up a window alist _before_ deleting
anything so you can retrieve the sizes of the deleted window from there.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-11-16 16:54                                                                                                           ` martin rudalics
@ 2024-11-17  2:45                                                                                                             ` Pranshu Sharma
  2024-11-17 10:22                                                                                                               ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-17  2:45 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

> When I set up my frame thusly
>
> (defun window-transpose-setup ()
>   (interactive)
>   (let ((foo (get-buffer-create "*foo*"))
> 	window)
>     (delete-other-windows)
>     (setq window (split-window nil nil t))
>     (set-window-buffer window foo)
>     (split-window window)
>     (setq window (split-window window nil t))
>     (split-window window)
>     (select-window window)))
>
> and then run 'rotate-windows-clockwise' I'm told
>
> which is one of the problems I tried to make you aware of.  IIUC window
> 44 is a deleted parent window and you try to get its pixel height.  But
> Emacs doesn't allow to retrieve the width or height of a deleted window.
>

In this case, cwin was the undead window that exists.  I just had a bug
in delete-other-windows-internal where the WINDOW argument was nil.  I
just changed that, and didn't touch the window--transpose-1 code and it
works now.  Look in the attached file.


[-- Attachment #2: window-transpose.el --]
[-- Type: application/emacs-lisp, Size: 8635 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-11-17  2:45                                                                                                             ` Pranshu Sharma
@ 2024-11-17 10:22                                                                                                               ` martin rudalics
  2024-11-17 15:03                                                                                                                 ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-17 10:22 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > In this case, cwin was the undead window that exists.  I just had a bug
 > in delete-other-windows-internal where the WINDOW argument was nil.  I
 > just changed that, and didn't touch the window--transpose-1 code and it
 > works now.  Look in the attached file.

OK.  Now to the problematic part:

	   (split-window cwin split-size
			 split-type t
			 ;; THIS IS WHERE IS GOES OF THE MAP spew
			 (cons win (cdar subtree))
			 ;; To get a basic working thing, comment above line and uncoment below line
			 ;; win

Here window-rotate.el checks

	       (if (and (window-valid-p parent)
			(eq parent (window-parent window)))
		   next
		 (cons next parent)))

when processing the next window in a combination and

	     (if (and (window-valid-p parent)
		      (eq parent (window-parent window)))
		 first
	       (cons first parent)))

when descending to the first live child where

- window is the window to split and

- parent is the parent of window before 'delete-other-windows-internal'

If parent has been already restored (has become valid again) you must
not pass it as cdr of REFER since then 'split-window-internal' will
complain with the error you've seen.  'split-window-internal' must
reject that argument because otherwise it may even crash Emacs.

So what you have to do is to pass only the former live window as REFER
argument and not use a cons in that case.  I've tried with

	      (split-window
	       cwin
	       split-size
	       split-type
	       t
	       (progn
		 (while (listp (caaddr win))
		   (setq win (caddr win)))
		 (if (and (window-valid-p (cdar win))
			  (eq (cdar win) (window-parent (caaddr win))))
		     (caaddr win)
		 (cons (caaddr win) (cdar win)))))

and

	   (split-window cwin split-size
			 split-type t
			 ;; THIS IS WHERE IS GOES OF THE MAP spew
			 (if (and (window-valid-p (cdar subtree))
				  (eq (cdar subtree) (window-parent win)))
			     win
			   (cons win (cdar subtree)))
			 ;; To get a basic working thing, comment above line and uncoment below line
			 ;; win
			 ))))

and they seem to work.

martin




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

* Re: Add function to rotate/transpose all windows
  2024-11-17 10:22                                                                                                               ` martin rudalics
@ 2024-11-17 15:03                                                                                                                 ` Pranshu Sharma
  2024-11-17 16:38                                                                                                                   ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-17 15:03 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

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

martin rudalics <rudalics@gmx.at> writes:

>> In this case, cwin was the undead window that exists.  I just had a bug
>> in delete-other-windows-internal where the WINDOW argument was nil.  I
>> just changed that, and didn't touch the window--transpose-1 code and it
>> works now.  Look in the attached file.
>
> OK.  Now to the problematic part:
>
> 	   (split-window cwin split-size
> 			 split-type t
> 			 ;; THIS IS WHERE IS GOES OF THE MAP spew
> 			 (cons win (cdar subtree))
> 			 ;; To get a basic working thing, comment above line and uncoment below line
> 			 ;; win
>
> Here window-rotate.el checks
>
> 	       (if (and (window-valid-p parent)
> 			(eq parent (window-parent window)))
> 		   next
> 		 (cons next parent)))
>
> when processing the next window in a combination and
>
> 	     (if (and (window-valid-p parent)
> 		      (eq parent (window-parent window)))
> 		 first
> 	       (cons first parent)))
>
> when descending to the first live child where
>
> - window is the window to split and
>
> - parent is the parent of window before 'delete-other-windows-internal'
>
> If parent has been already restored (has become valid again) you must
> not pass it as cdr of REFER since then 'split-window-internal' will
> complain with the error you've seen.  'split-window-internal' must
> reject that argument because otherwise it may even crash Emacs.
>
> So what you have to do is to pass only the former live window as REFER
> argument and not use a cons in that case.

Ok, I applied this and it works fine now.  Look in the file
attached.  Also checkout the cycle-windows function which I added in.  I
know I still have to deal with fixed size and atomic windows, but
otherwise it is looking fine now.


[-- Attachment #2: window-transpose.el --]
[-- Type: application/emacs-lisp, Size: 9800 bytes --]

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

* Re: Add function to rotate/transpose all windows
  2024-11-17 15:03                                                                                                                 ` Pranshu Sharma
@ 2024-11-17 16:38                                                                                                                   ` martin rudalics
  2024-11-18  0:37                                                                                                                     ` Pranshu Sharma
  0 siblings, 1 reply; 111+ messages in thread
From: martin rudalics @ 2024-11-17 16:38 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > Ok, I applied this and it works fine now.  Look in the file
 > attached.

I'm not convinced yet.  Here

      (split-window
	       cwin
	       split-size
	       split-type
	       t
	       (progn
		 (while (listp (caaddr win))
		   (setq win (caddr win)))
		 (caaddr win)))

you never pass a parent window to 'split-window' but I think you should.
With my standard setup I get

((#<window 17 on *foo*> . #<window 18>)
  (#<window 19 on *foo*> . #<window 18>)
  (#<window 15 on *foo*> . #<window 14>)
  (#<window 6 on window-transpose.el> . #<window 12>)
  (#<window 13 on *foo*> . #<window 16>))

before 'rotate-windows-clockwise' and

((#<window 17 on *foo*> . #<window 18>)
  (#<window 19 on *foo*> . #<window 18>)
  (#<window 15 on *foo*> . #<window 14>)
  (#<window 6 on window-transpose.el> . #<window 20>)
  (#<window 13 on *foo*> . #<window 21>))

afterwards: Both parent windows 12 and 16 have been lost and new parent
windows 20 and 21 have been created.

martin



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

* Re: Add function to rotate/transpose all windows
  2024-11-17 16:38                                                                                                                   ` martin rudalics
@ 2024-11-18  0:37                                                                                                                     ` Pranshu Sharma
  2024-11-18  8:55                                                                                                                       ` martin rudalics
  0 siblings, 1 reply; 111+ messages in thread
From: Pranshu Sharma @ 2024-11-18  0:37 UTC (permalink / raw)
  To: martin rudalics; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

martin rudalics <rudalics@gmx.at> writes:

>> Ok, I applied this and it works fine now.  Look in the file
>> attached.
>
> I'm not convinced yet.  Here
>
>      (split-window
> 	       cwin
> 	       split-size
> 	       split-type
> 	       t
> 	       (progn
> 		 (while (listp (caaddr win))
> 		   (setq win (caddr win)))
> 		 (caaddr win)))
>
> you never pass a parent window to 'split-window' but I think you should.
> With my standard setup I get

I can't quite get it to work, imagine this scinario:

+-----+------+
| A   |__B___|
|     |  C   |
+-----+------+

What the above split does is that is splits A, with refer B, and these
windows aren't really siblings.  So if I do pass the refer of A and B,
it should be from A's parent, but then when I pass B with refer (cons C
(B and C's parent)), won't it bug out since B's parent is being set twice?



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

* Re: Add function to rotate/transpose all windows
  2024-11-18  0:37                                                                                                                     ` Pranshu Sharma
@ 2024-11-18  8:55                                                                                                                       ` martin rudalics
  0 siblings, 0 replies; 111+ messages in thread
From: martin rudalics @ 2024-11-18  8:55 UTC (permalink / raw)
  To: Pranshu Sharma; +Cc: Juri Linkov, Eli Zaretskii, emacs-devel

 > I can't quite get it to work, imagine this scinario:
 >
 > +-----+------+
 > | A   |__B___|
 > |     |  C   |
 > +-----+------+
 >
 > What the above split does is that is splits A, with refer B, and these
 > windows aren't really siblings.  So if I do pass the refer of A and B,
 > it should be from A's parent, but then when I pass B with refer (cons C
 > (B and C's parent)), won't it bug out since B's parent is being set twice?

I tell you what I'm doing here: Let's call P the original parent of A
and Q that of B and C.  The first split is a "first child" below split
with A as window to split and a cons of B and P as REFER.  The second
split is a "next sibling" left split with B as window to split and a
cons of C and Q as REFER.  Both splits are done with combination limit
set to t.

So 'split-window-internal' will not use the current parent of A in the
first split (which is nil anyway) but make a new parent which it sets to
P (since that is passed as second argument of REFER).

In the second split it will not use the current parent of A and B (which
is the already resurrected P) but again make a new parent which it sets
to Q (since that is passed as second argument of REFER).

So the parent of B is indeed set twice: When B is resurrected it is set
to P.  When C is resurrected it is set to Q and Q's parent is set to P.

'split-window-internal' makes a new parent window (which it resurrects
in the cases above immediately) when 'window-combination-limit' is
non-nil.  Setting that value up is done in window-rotate.el in the
following excerpt where 'window' is the window to split (first A and
then B in our case):

	    (let* ((prev (window-alist-prev window))
		   (limit (window-alist-combination-limit parent))
		   ;; Set 'window-combination-limit' to make sure
		   ;; that a new parent is created when WINDOW's
		   ;; previous sibling and WINDOW do not have the
		   ;; same parent.
		   (window-combination-limit
		    (or limit
			(not prev)
			(not (eq (window-alist-parent prev)
				 (window-alist-parent window)))))

Here 'prev' is the original previous sibling of 'window' and 'parent'
its original parent (P in the first split and Q in the second).  I set
'window-combination-limit' to t in the following cases:

- 'parent' already has its combination limit set in which case I have to
   preserve it

- 'window' has no previous sibling

- the original parent of 'prev' is not the original parent of 'window'.

In our example, neither A in the first split nor B in the second split
have a previous sibling so I set 'window-combination-limit' to t in both
cases.

BTW if you bind 'window-combination-limit' to t don't forget to reset
it's effect for the parent window via

(set-window-combination-limit parent limit)

right after the split.  Otherwise the parent window cannot be recombined
later on although that would be principally possible.

martin



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

end of thread, other threads:[~2024-11-18  8:55 UTC | newest]

Thread overview: 111+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-09-24 13:45 Add function to rotate/transpose all windows pranshu sharma
2024-09-24 13:53 ` Eli Zaretskii
2024-09-25  8:05   ` martin rudalics
2024-09-25  8:34     ` pranshu sharma
2024-09-25  9:31       ` martin rudalics
2024-09-25 10:50         ` pranshu sharma
2024-09-25 13:53           ` martin rudalics
2024-09-25 15:31             ` pranshu sharma
2024-09-26 14:10       ` martin rudalics
2024-09-26 14:22         ` Eli Zaretskii
2024-09-27 17:29           ` martin rudalics
2024-09-28  7:52             ` pranshu sharma
2024-09-28  9:26               ` martin rudalics
2024-09-28 10:53                 ` pranshu sharma
2024-09-28 14:48                   ` martin rudalics
2024-09-29  7:36                     ` pranshu sharma
2024-09-29  8:40                       ` martin rudalics
2024-09-29  9:23                         ` pranshu sharma
2024-09-29 14:48                           ` martin rudalics
2024-09-30  6:29                             ` pranshu sharma
2024-09-30  8:57                               ` martin rudalics
2024-10-01  9:17                                 ` pranshu sharma
2024-10-02  9:04                                   ` martin rudalics
2024-10-03  7:06                                     ` pranshu sharma
2024-10-03  8:17                                       ` martin rudalics
2024-10-03 10:09                                         ` pranshu sharma
2024-10-03 14:18                                           ` martin rudalics
2024-10-04  5:50                                             ` pranshu sharma
2024-10-04  8:08                                               ` martin rudalics
2024-10-04 15:10                                                 ` pranshu sharma
2024-10-05 14:43                                                   ` martin rudalics
2024-10-06  2:54                                                     ` pranshu sharma
2024-10-06 15:02                                                       ` martin rudalics
2024-10-06 15:52                                                         ` pranshu sharma
2024-10-07  8:33                                                           ` martin rudalics
2024-10-07  9:42                                                             ` pranshu sharma
2024-10-03 15:12                                           ` Eli Zaretskii
2024-10-08 18:35                                       ` Juri Linkov
2024-10-09  6:59                                         ` pranshu sharma
2024-10-09 16:21                                           ` Juri Linkov
2024-10-10 11:49                                             ` pranshu sharma
2024-10-10 16:57                                               ` Juri Linkov
2024-10-13  5:43                                                 ` pranshu sharma
2024-10-13  8:17                                                   ` martin rudalics
2024-10-14 17:36                                                     ` Juri Linkov
2024-10-15  8:34                                                     ` pranshu sharma
2024-10-15 16:16                                                       ` Juri Linkov
2024-10-18 14:52                                                     ` pranshu sharma
2024-10-18 17:48                                                       ` martin rudalics
2024-10-18 18:37                                                         ` Eli Zaretskii
2024-10-19  1:45                                                         ` pranshu sharma
2024-10-19  6:45                                                           ` Eli Zaretskii
2024-10-19 18:19                                                             ` Juri Linkov
2024-10-19  8:33                                                           ` martin rudalics
2024-10-20  8:19                                                       ` martin rudalics
2024-10-20 14:11                                                         ` Pranshu Sharma
2024-10-20 17:37                                                           ` martin rudalics
2024-10-21  5:54                                                             ` Pranshu Sharma
2024-10-21  8:14                                                               ` martin rudalics
2024-10-21  9:23                                                               ` martin rudalics
2024-10-21 13:37                                                             ` Pranshu Sharma
2024-10-22 18:12                                                               ` martin rudalics
2024-10-24 14:38                                                                 ` Pranshu Sharma
2024-10-24 18:39                                                                   ` martin rudalics
2024-10-25 14:24                                                                     ` Pranshu Sharma
2024-10-25 17:09                                                                       ` martin rudalics
2024-10-26  9:14                                                                         ` Pranshu Sharma
2024-10-27  8:23                                                                           ` martin rudalics
2024-11-02 14:06                                                                             ` Pranshu Sharma
2024-11-05 18:01                                                                               ` martin rudalics
2024-11-08  9:23                                                                                 ` Pranshu Sharma
2024-11-08 10:06                                                                                   ` Pranshu Sharma
2024-11-08 15:52                                                                                     ` martin rudalics
2024-11-09  2:14                                                                                       ` Pranshu Sharma
2024-11-09  8:48                                                                                         ` martin rudalics
2024-11-08 15:52                                                                                   ` martin rudalics
2024-11-09  2:09                                                                                     ` Pranshu Sharma
2024-11-09  8:48                                                                                       ` martin rudalics
2024-11-09 10:55                                                                                         ` Pranshu Sharma
2024-11-09 18:06                                                                                           ` martin rudalics
2024-11-10 10:09                                                                                             ` Pranshu Sharma
2024-11-10 16:36                                                                                               ` martin rudalics
2024-11-11 14:47                                                                                                 ` Pranshu Sharma
2024-11-11 16:55                                                                                                   ` martin rudalics
2024-11-12 13:50                                                                                                     ` Pranshu Sharma
2024-11-12 17:46                                                                                                       ` martin rudalics
2024-11-16 13:36                                                                                                         ` Pranshu Sharma
2024-11-16 16:54                                                                                                           ` martin rudalics
2024-11-17  2:45                                                                                                             ` Pranshu Sharma
2024-11-17 10:22                                                                                                               ` martin rudalics
2024-11-17 15:03                                                                                                                 ` Pranshu Sharma
2024-11-17 16:38                                                                                                                   ` martin rudalics
2024-11-18  0:37                                                                                                                     ` Pranshu Sharma
2024-11-18  8:55                                                                                                                       ` martin rudalics
2024-10-14 17:32                                                   ` Juri Linkov
2024-09-28  7:58             ` pranshu sharma
2024-09-28  8:18             ` Eli Zaretskii
2024-09-28  9:40               ` martin rudalics
2024-09-28 11:35                 ` Eli Zaretskii
2024-09-28 14:58                   ` martin rudalics
2024-09-28 15:28                     ` Eli Zaretskii
2024-10-07  8:33                       ` martin rudalics
2024-09-28 13:22                 ` pranshu sharma
2024-09-28 14:21                   ` Eli Zaretskii
2024-09-28 14:49                   ` martin rudalics
2024-09-27 10:06         ` pranshu sharma
2024-09-27 17:29           ` martin rudalics
2024-09-24 17:40 ` Petteri Hintsanen
2024-09-24 19:34 ` Charles Choi
2024-09-25  2:00   ` Emanuel Berg
2024-09-25  7:00   ` pranshu sharma

Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.