Add functions 'combine-windows' and 'uncombine-window'

* src/window.c (make_parent_window, Fcombine_windows)
(Funcombine_window): New functions.
(Fsplit_window_internal): Use make_parent_window.
* doc/lispref/windows.texi (Recombining Windows): Describe new
functions 'combine-windows' and 'uncombine-window'.
(Window Configurations): Explain "window clones".
* etc/NEWS: Mention new functions 'combine-windows' and
'uncombine-window'.
This commit is contained in:
Martin Rudalics 2025-10-18 10:33:10 +02:00
parent 81bc83c1a5
commit ef2b032567
3 changed files with 268 additions and 12 deletions

View file

@ -2099,6 +2099,51 @@ Deleting any of the live windows @var{W2}, @var{W3} or @var{W4} will
distribute its space proportionally among the two remaining live
windows.
The following two functions allow to expressly make a new parent window
for several adjacent windows and to undo that operation.
@cindex combining windows
@defun combine-windows first last
This function combines the windows from @var{first} to @var{last}
inclusive. @var{first} and @var{last} must be valid windows in the same
combination, that is, windows with the same parent window. If neither
@var{first} has a previous nor @var{last} has a next sibling, it returns
that parent window. Otherwise, it makes a new parent window whose first
child window becomes @var{first} and whose last child window becomes
@var{last}, inserts that parent window in the window tree in lieu of the
windows starting with @var{first} and ending with @var{last} and returns
the new parent window.
This function is useful to operate on several siblings of the same
parent window as a whole. Consider three sibling windows @var{W1},
@var{W2} and @var{W3} aligned side by side. To make a window below
@var{W1} and @var{W2} but not below @var{W3}, first combine @var{W1} and
@var{W2} using this function and then split the window it returned. It
can be also used to obtain the combined state of @var{W1} and @var{W2}
by running @code{window-state-get} (@pxref{Window Configurations}) on
the return value.
@end defun
@cindex uncombining windows
@defun uncombine-window window
This function uncombines the specified @var{window}. @var{window}
should be an internal window whose parent window is an internal window
of the same type. This means, that @var{window} and its parent should
be either both horizontal or both vertical window combinations. If this
is the case, it makes the child windows of @var{window} child windows of
@var{window}'s parent and returns @code{t}. Otherwise, it leaves the
current configuration of @var{window}'s frame unchanged and returns
@code{nil}.
This function is useful to undo the effect of @code{combine-windows}.
For example, to calculate the state of two adjacent windows @var{W1} and
@var{W2}, proceed as follows: Run @code{combine-windows} with @var{W1}
and @var{W2} as arguments and store the result in @var{W}. Then run
@code{window-state-get} with the argument @var{W}. Finally, run this
function with @var{W} as its argument to obtain the window configuration
that existed before running @code{combine-windows}.
@end defun
@node Resurrecting Windows
@section Resurrecting Windows
@ -7035,9 +7080,9 @@ sessions. @xref{Uniquify,,, emacs, The GNU Emacs Manual}.
@end defun
The value returned by @code{window-state-get} can be used in the same
session to make a clone of a window in another window. It can be also
written to disk and read back in another session. In either case, use
the following function to restore the state of the window.
session to clone a window (see below). It can be also written to disk
and read back in another session. In either case, use the following
function to restore the state of the window.
@defun window-state-put state &optional window ignore
This function puts the window state @var{state} into @var{window}.
@ -7059,11 +7104,22 @@ is @code{safe}, this means windows can get as small as one line
and/or two columns.
@end defun
@cindex window clone
@cindex clone of window
In the context of window states, the @dfn{clone of a window} is a window
that has the same decorations and contents as the window whose state was
used to produce it, but is actually represented by another window
object. Operating on the original or the clone of a window does not
affect the other in any way. Note that while @code{window-state-get}
clones existing windows, these clones are not yet valid windows. They
become valid only after @code{window-state-put} has put them into a live
frame.
By default, @code{set-window-configuration} and @code{window-state-put}
may delete a window from the restored configuration when they find out
that its buffer was killed since the corresponding configuration or
state has been recorded. The variable described next can be used to
fine-tune that behavior.
may delete a window from the restored configuration or state when they
find out that its buffer was killed since the corresponding
configuration or state has been recorded. The variable described next
can be used to fine-tune that behavior.
@cindex restoring windows whose buffers have been killed
@defvar window-restore-killed-buffer-windows

View file

@ -413,6 +413,13 @@ during the execution of particular commands.
This moves in the opposite direction of 'other-window' and is for its
default keybinding consistent with 'repeat-mode'.
+++
*** New functions 'combine-windows' and 'uncombine-window'.
'combine-windows' is useful to make a new parent window for several
adjacent windows and subsequently operate on that parent.
'uncombine-window' can then be used to restore the window configuration
to the state it had before running 'combine-windows'.
** Frames
+++

View file

@ -5133,6 +5133,200 @@ resize_frame_windows (struct frame *f, int size, bool horflag)
}
/** Make parent window on FRAME and return its object. */
static Lisp_Object
make_parent_window (Lisp_Object frame)
{
Lisp_Object parent;
struct window *p = allocate_window ();
p->sequence_number = ++sequence_number;
wset_frame (p, frame);
XSETWINDOW (parent, p);
return parent;
}
DEFUN ("combine-windows", Fcombine_windows, Scombine_windows, 2, 2, 0,
doc: /* Combine windows from FIRST to LAST.
FIRST and LAST must be valid windows in the same combination, that is,
windows with the same parent window. If neither FIRST has a previous
nor LAST has a next sibling, return that parent window. Otherwise, make
a new parent window whose first child window becomes FIRST and whose
last child window becomes LAST, insert that parent window in the window
tree in lieu of the windows starting with FIRST and ending with LAST and
return the new parent window. */)
(Lisp_Object first, Lisp_Object last)
{
struct window *f = decode_valid_window (first);
struct window *l = decode_valid_window (last);
struct window *w = f;
if (f == l)
/* Don't make a matryoshka window. */
error ("Invalid window to parentify");
while (w != l && !NILP (w->next))
w = XWINDOW (w->next);
if (w != l)
{
w = l;
while (w != f && !NILP (w->next))
w = XWINDOW (w->next);
if (w == f)
/* Invert FIRST and LAST. */
{
f = l;
l = w;
XSETWINDOW (first, f);
XSETWINDOW (last, l);
}
else
error ("Invalid window to parentify");
}
if (NILP (f->prev) && NILP (l->next))
return f->parent;
/* Make new parent window PARENT. */
Lisp_Object parent = make_parent_window (f->frame);
struct window *p = XWINDOW (parent);
double normal_size = 0.0;
/* Splice in PARENT into the window tree. */
p->parent = f->parent;
p->horizontal = XWINDOW (p->parent)->horizontal;
p->contents = first;
if (NILP (f->prev))
/* FIRST has no previous sibling. */
XWINDOW (p->parent)->contents = parent;
else
/* FIRST has a previous sibling. */
{
XWINDOW (f->prev)->next = parent;
p->prev = f->prev;
f->prev = Qnil;
}
if (!NILP (l->next))
/* LAST has a next sibling. */
{
XWINDOW (l->next)->prev = parent;
p->next = l->next;
l->next = Qnil;
}
/* Fix parent slots for PARENT's new children. */
w = f;
while (w)
{
w->parent = parent;
if (w == l)
break;
else
{
if (p->horizontal)
normal_size = normal_size + XFLOAT_DATA (f->normal_cols);
else
normal_size = normal_size + XFLOAT_DATA (f->normal_lines);
w = XWINDOW (w->next);
}
}
/* Set up PARENT's positions and sizes. */
p->pixel_left = f->pixel_left;
p->left_col = f->left_col;
p->pixel_top = f->pixel_top;
p->top_line = f->top_line;
if (p->horizontal)
{
p->pixel_width = l->pixel_left + l->pixel_width - f->pixel_left;
p->total_cols = l->left_col + l->total_cols - f->left_col;
p->pixel_height = f->pixel_height;
p->total_lines = f->total_lines;
}
else
{
p->pixel_height = l->pixel_top + l->pixel_height - f->pixel_top;
p->total_lines = l->top_line + l->total_lines - f->top_line;
p->pixel_width = f->pixel_width;
p->total_cols = f->total_cols;
}
if (p->horizontal)
{
p->normal_cols = make_float (normal_size);
p->normal_lines = f->normal_lines;
}
else
{
p->normal_cols = f->normal_cols;
p->normal_lines = make_float (normal_size);
}
return parent;
}
DEFUN ("uncombine-window", Funcombine_window, Suncombine_window, 1, 1, 0,
doc: /* Uncombine specified WINDOW.
WINDOW should be an internal window whose parent window is an internal
window of the same type. This means, that WINDOW and its parent should
be either both horizontal or both vertical window combinations. If this
is the case, make the child windows of WINDOW child windows of WINDOW's
parent and return t. Otherwise, leave the current configuration of
WINDOW's frame unchanged and return nil. */)
(Lisp_Object window)
{
struct window *w = decode_valid_window (window);
Lisp_Object parent = w->parent;
if (MINI_WINDOW_P (w))
error ("Cannot uncombine a mini window");
if (WINDOW_INTERNAL_P (w) && !NILP (parent)
&& w->horizontal == XWINDOW (parent)->horizontal)
{
struct window *p = XWINDOW (w->parent);
/* WINDOW's first child. */
Lisp_Object first = w->contents;
struct window *f = XWINDOW (first);
/* WINDOW's last child. */
Lisp_Object last = Qnil;
struct window *l = f;
/* Make WINDOW's parent new parent of WINDOW's children. */
while (!NILP (l->next))
{
wset_parent (l, parent);
l = XWINDOW (l->next);
}
wset_parent (l, parent);
XSETWINDOW (last, l);
wset_prev (f, w->prev);
if (NILP (f->prev))
wset_combination (p, p->horizontal, first);
else
wset_next (XWINDOW (f->prev), first);
wset_next (l, w->next);
if (!NILP (l->next))
wset_prev (XWINDOW (w->next), last);
return Qt;
}
else
return Qnil;
}
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
@ -5302,12 +5496,9 @@ set correctly. See the code of `split-window' for how this is done. */)
= horflag ? o->normal_cols : o->normal_lines;
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);
parent = make_parent_window (frame);
p = XWINDOW (parent);
}
else
/* Pacify GCC. */
@ -9345,6 +9536,8 @@ name to `'ignore'. */);
defsubr (&Sselect_window);
defsubr (&Sforce_window_update);
defsubr (&Ssplit_window_internal);
defsubr (&Scombine_windows);
defsubr (&Suncombine_window);
defsubr (&Sscroll_up);
defsubr (&Sscroll_down);
defsubr (&Sscroll_left);