diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index e042418634d..5393db9b562 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index 0b8d624d923..9ade5212f6f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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 +++ diff --git a/src/window.c b/src/window.c index dee17ae445a..f19f2041c77 100644 --- a/src/window.c +++ b/src/window.c @@ -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);