From: Pascal Bourguignon <spam@mouse-potato.com>
Subject: Re: New balance-windows
Date: Sun, 07 Aug 2005 20:23:46 +0200 [thread overview]
Message-ID: <87mznttxl9.fsf@thalassa.informatimago.com> (raw)
In-Reply-To: mailman.2796.1123363907.20277.help-gnu-emacs@gnu.org
Lennart Borgman <lennart.borgman.073@student.lu.se> writes:
> Pascal Bourguignon wrote:
>
>>>Is the problem really that welldefined in Emacs? Do you know how the
>>>windows have been splitted? The difference above may perhaps be seen
>>>as emerging from that difficulty?
>>>
>>>
>>
>>Indeed, but the data is available at least internally, so it should be
>>easy to patch emacs to publish window parameters like it's done for
>>frame parameters for example.
>>
>>File: elisp, Node: Window Internals
>>
>>
> Thanks, I did not know this was available. Then the windows in each
> frame can be seen as a tree using those fields you mentioned. But what
> should then be done with this? What should the mapping between this
> tree and the visual view be? It looks to me that the level in the tree
> alone will not give any useful hint. The topology (ie whether the
> splitting was horizontal or vertical) must be taken into account, but
> maybe the level in the tree can be used too? It actually looks a bit
> difficult...
Well, it's only available to the C programmer...
Since window parents are not normal emacs lisp windows, I assume we'd
export the window internals as a new data type. Let's call it "split".
(frame-split-root [frame]) --> split or nil
(window-configuration-split winconf) --> split or nil
(splitp object) --> boolean
(split-direction split) --> :vertical | :horizontal
;; (window-split-vertically) produces a :vertical split-direction.
(split-edges sow) --> (left top right bottom)
;; Perhaps (defalias split-edges window-edges)
(split-cuts split) --> (BReak BReak ...)
;; The bottom or right of the correponding child.
(split-children split) --> (child child ...)
;; A split child may be a window or a split.
(split-parent sow) --> split
(enlarge-split sow increment preserve-before)
;; The parent is null for root splits or windows,
;; othewise it's always a split.
(or (null (split-parent sow))
(splitp (split-parent sow)))
;; the edges of the split are the same as the edge of an alone window:
(equal (split-edges split) (progn (delete-other-windows) (window-edges)))
;; the length is the number of collapsed windows or splits:
(= (length (split-cuts split)) (length (split-children split)))
;; child splits are perpendicular:
(every (lambda (child-split)
(not (eq (split-direction split) (split-direction child-split))))
(delete-if-not (function splitp) (split-children split)))
;; split cuts are sorted (hence the order of the corresponding split-children
;; list):
(dotimes (i (1- (length (split-cuts split))))
(< (nth i (split-cuts split)) (nth (1+ i) (split-cuts split))))
(dotimes (i (length (split-cuts split)))
(and
;; all children are either a split or a window:
(or (windowp (nth i (split-children split)))
(splitp (nth i (split-children split))))
;; the parent of each children of split is the split itself:
(eq split (split-parent (nth i (split-children split))))
;; the cuts are the bottom or right of the corresponding child:
(= (nth i (split-cuts split))
(funcall (if (eq (split-direction split) :vertical)
(function bottom)
(function right))
(split-edges (nth i (split-children split)))))
;; the sides are the same for all children:
(every (lambda (side)
(= (funcall side (split-edges split))
(funcall side (split-edges (nth i (split-children split))))))
(if (eq (split-direction split) :vertical)
(list (function left) (function right))
(list (function top) (function bottom))))
))
Several parallel cuts are collapsed into one split.
(progn (delete-other-windows)
(split-window-vertically)
(split-window-vertically))
and:
(progn (delete-other-windows)
(split-window-vertically)
(other-window 1)
(split-window-vertically))
would both give (= 3 (length (split-cuts (frame-split-root)))).
And:
(equal (progn (delete-other-windows)
(split-window-vertically)
(split-window-vertically)
(frame-split-root))
(progn (delete-other-windows)
(split-window-vertically)
(other-window 1)
(split-window-vertically)
(frame-split-root)))
Then the balancing algorithm could be as simple as:
(require 'cl)
(defun plusp (x) (< 0 x))
(defun left (x) (first x))
(defun top (x) (second x))
(defun right (x) (third x))
(defun bottom (x) (fourth x))
(defun split-height (split)
(- (bottom (split-edges split)) (top (split-edges split))))
(defun split-width (split)
(- (right (split-edges split)) (left (split-edges split))))
(defun balance-split (split)
(labels ((count-children (sow direction)
;; This could be cached into the split-or-window structure
(if (splitp sow)
(reduce (if (eq (split-direction sow) direction)
(function +)
(function max))
(mapcar (lambda (child)
(count-children child direction))
(split-children sow)))
1)))
(let* ((relative-sizes
(mapcar (lambda (child)
(count-children child (split-direction split)))
(split-children split)))
(total (reduce (function +) relative-sizes))
(split-size (if (eq (split-direction split) :vertical)
(split-height split)
(split-width split))))
(loop
for child-size in relative-sizes
for child in (butlast (split-children split))
for new-size = (truncate (* split-size child-size) total)
do (enlarge-split child
(- new-size
(if (eq (split-direction split) :vertical)
(split-height child)
(split-width child)))
(split-direction split)
t))
(dolist (child (split-children split))
(when (splitp child)
(balance-split child))))))
(defun balance-windows ()
(interactive)
(when (splitp (frame-split-root))
(balance-split (frame-split-root))))
And give excelent results, see at the end for an example.
;; The following is a simulator for the above specified split data structure.
(defstruct sow direction parent children right bottom)
(defun split-or-window-previous (sow)
(let ((parent (split-or-window-parent sow)))
(when parent
(let* ((children (split-or-window-children parent))
(i (position sow children)))
(and i (plusp i) (nth (1- i) children))))))
(defun split-or-window-left (sow)
(let ((parent (split-or-window-parent sow)))
(if parent
(if (eq (split-or-window-direction parent) :horizontal)
(let ((previous (split-or-window-previous sow)))
(if previous
(split-or-window-right previous)
(split-or-window-left parent)))
(split-or-window-left parent))
0)))
(defun split-or-window-top (sow)
(let ((parent (split-or-window-parent sow)))
(if parent
(if (eq (split-or-window-direction parent) :vertical)
(let ((previous (split-or-window-previous sow)))
(if previous
(split-or-window-bottom previous)
(split-or-window-top parent)))
(split-or-window-top parent))
0)))
(defun splitp (sow)
(and (split-or-window-p sow)
(split-or-window-children sow)))
(defun split-edges (sow)
(list (split-or-window-left sow)
(split-or-window-top sow)
(split-or-window-right sow)
(split-or-window-bottom sow)))
(defvar *current-window* (make-split-or-window :right 42 :bottom 42))
(defvar *frame-split-root* *current-window*)
(defun frame-split-root (&optional frame) *frame-split-root*)
(defvar *window-list* (list *current-window*))
(defun window-list* () *window-list*)
(defun current-window () *current-window*)
(defun initialize (width height)
(setf *current-window* (make-split-or-window :right width :bottom height)
*frame-split-root* *current-window*
*window-list* (list *current-window*)))
(defun split-direction (split)
(assert (splitp split))
(split-or-window-direction split))
(defun split-cuts (split)
(assert (splitp split))
(mapcar (if (eq (split-direction split) :vertical)
(function bottom)
(function right)) (split-children split)))
(defun split-children (split)
(assert (splitp split))
(split-or-window-children split))
(defun split-parent (sow)
(split-or-window-parent sow))
(defun enlarge-split (sow increment direction preserve-before)
(unless (zerop increment)
(if (eq direction :vertical)
(incf (split-or-window-bottom sow) increment)
(incf (split-or-window-right sow) increment))
(when (splitp sow)
(if (eq (split-direction sow) direction)
;; change the size in same direction
;; TODO: check increment vs. last size and preserve-before
(let ((last-child (first (last (split-or-window-children
sow)))))
(enlarge-split last-child increment direction preserve-before))
;; change the size orthogonally
(dolist (child (split-or-window-children sow))
(enlarge-split child increment direction preserve-before))))
(let ((parent (split-or-window-parent sow)))
(when (and parent (eq (split-direction parent) direction))
(let ((next (second (memq sow (split-or-window-children parent)))))
(when next
(if (eq direction :vertical)
(incf (split-or-window-top next) increment)
(incf (split-or-window-left next) increment))))))))
(defun insert-after (new old list) (push new (cdr (memq old list))))
(defun split (direction &optional size)
(flet ((split-size (split) (if (eq direction :vertical)
(split-height split)
(split-width split)))
(make-split (direction window parent)
(let* ((edges (split-edges window))
(result (make-split-or-window
:parent parent
:children (list window)
:direction direction
:right (right edges)
:bottom (bottom edges))))
(setf (split-or-window-parent window) result))))
(setf size (or size (/ (split-size (current-window)) 2)))
(unless (< 1 size)
(error "split: Window too small."))
(when (<= (split-size (current-window)) size)
(error "split: Too big size asked."))
(let ((split (split-parent (current-window))))
(cond
((null split)
;; make a new root split:
(setf split (make-split direction (current-window) nil))
(setf *frame-split-root* split))
((not (eq direction (split-direction split)))
;; make a new perpendicular split
(let ((perpendicular (make-split direction (current-window) split)))
(setf (split-or-window-children split)
(nsubstitute perpendicular (current-window)
(split-or-window-children split)))
(assert (memq perpendicular (split-or-window-children split)))
(setf split perpendicular))))
(assert (eq split (split-parent (current-window))))
(assert (memq (current-window) (split-children split)))
(assert (eq direction (split-direction split)))
(let* ((dx (if (eq direction :vertical) 0 size))
(dy (if (eq direction :vertical) size 0))
(edges (split-edges (current-window)))
;; make a new window:
(other (make-split-or-window
:parent split
:right (right edges)
:bottom (bottom edges))))
(if (eq direction :vertical)
(setf (split-or-window-bottom (current-window)) (+ (top edges) dy))
(setf (split-or-window-right (current-window)) (+ (left edges) dx)))
(insert-after other (current-window) (split-or-window-children split))
(insert-after other (current-window) *window-list*)
(assert (memq (current-window) *window-list*))
(assert (memq other *window-list*))
(assert (memq (current-window) (split-or-window-children split)))
(assert (memq other (split-or-window-children split)))))))
(defun other-window* (n &optional all-frames)
(let ((i (mod (+ (position (current-window) (window-list*)) n)
(length (window-list*)))))
(setf *current-window* (nth i (window-list*)))))
(defun windowp* (window) (memq window (window-list*)))
(defun delete-other-windows* (&optional window)
(when (windowp* window)
(setf *current-window* window))
(setf window (current-window))
(when (split-parent window)
(let ((root (frame-split-root)))
(setf (split-or-window-left window) (split-or-window-left root)
(split-or-window-top window) (split-or-window-top root)
(split-or-window-right window) (split-or-window-right root)
(split-or-window-bottom window) (split-or-window-bottom root)))
(setf (split-or-window-parent window) nil)
(setf *frame-split-root* window
*window-list* (list window))))
(defun split-describe (split &optional level)
(setf level (or level ""))
(if (splitp split)
(progn
(insert (format "%s#<split %s %s %s %s>\n"
level
(format ":direction %S" (split-direction split))
(format ":parent %S" (not (null (split-parent split))))
(format ":edges %S" (split-edges split))
(format ":children %S" (length (split-children split)))))
(dolist (child (split-children split))
(split-describe child (concat " " level))))
(insert (format "%s#<window %s %s>\n"
level
(format ":parent %S" (not (null (split-parent split))))
(format ":edges %S" (split-edges split))))))
(defun split-draw (split)
(with-current-buffer (get-buffer-create "*splits*")
(erase-buffer)
(unless (eq major-mode 'picture-mode)
(picture-mode))
(picture-open-line (1+ (bottom (split-edges split))))
(labels ((goto (x y)
(message "goto %s %s" x y)
(goto-line y)
(beginning-of-line)
(picture-forward-column x))
(draw-rectangle (edges)
(picture-draw-rectangle
(progn (goto (left edges) (top edges)) (point))
(progn (goto (right edges) (bottom edges)) (point))))
(draw-split (split)
(draw-rectangle (split-edges split))
(if (splitp split)
(dolist (child (split-children split))
(draw-split child)))))
(draw-split split)))
(sit-for 1)
(force-mode-line-update t))
Let's make some windows:
(progn
(initialize 60 56)
(delete-other-windows*)
(split :vertical)
(split :horizontal)
(split :vertical)
(split :vertical)
(balance-windows) (split-draw (frame-split-root))
(other-window* 4)
(split :vertical)
(split :horizontal)
(split :horizontal)
(other-window* 1)
(split :vertical)
(balance-windows) (split-draw (frame-split-root))
(other-window* 7)
(split :vertical)
(split :vertical)
(split :vertical)
(balance-windows) (split-draw (frame-split-root))
(split-describe (frame-split-root)))
#<split :direction :vertical :parent nil :edges (0 0 60 56) :children 3>
#<split :direction :horizontal :parent t :edges (0 0 60 32) :children 2>
#<split :direction :vertical :parent t :edges (0 0 30 32) :children 3>
#<window :parent t :edges (0 0 30 10)>
#<window :parent t :edges (0 10 30 20)>
#<window :parent t :edges (0 20 30 32)>
#<split :direction :vertical :parent t :edges (30 0 60 32) :children 4>
#<window :parent t :edges (30 0 60 8)>
#<window :parent t :edges (30 8 60 16)>
#<window :parent t :edges (30 16 60 24)>
#<window :parent t :edges (30 24 60 32)>
#<split :direction :horizontal :parent t :edges (0 32 60 48) :children 3>
#<window :parent t :edges (0 32 20 48)>
#<split :direction :vertical :parent t :edges (20 32 40 48) :children 2>
#<window :parent t :edges (20 32 40 40)>
#<window :parent t :edges (20 40 40 48)>
#<window :parent t :edges (40 32 60 48)>
#<window :parent t :edges (0 48 60 56)>
+-----------------------------+-----------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| +-----------------------------+
| | |
+-----------------------------| |
| | |
| | |
| | |
| | |
| | |
| +-----------------------------+
| | |
| | |
| | |
+-----------------------------| |
| | |
| | |
| | |
| +-----------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
+-------------------+-------------------+-------------------+
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| +-------------------| |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
+-----------------------------------------------------------+
| |
| |
| |
| |
| |
| |
| |
+-----------------------------------------------------------+
The current algorithm something like the following, that doesn't look
like "balanced" to me:
+-----------------------------+-----------------------------+
| | |
| | too small |
| | |
| +-----------------------------+
| | |
| | |
| | |
+-----------------------------+-----------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
+-----------------------------+-----------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
+-------------+---------------+-----------------------------+
| | | |
| | | |
| | | |
| | | |
| | | |
| +---------------| too big |
| | | |
| | | |
| | | |
| | | |
| | | |
+-------------+---------------+-----------------------------+
| |
| |
| |
| |
| |
| |
| |
| too big |
| |
| |
| |
+-----------------------------------------------------------+
Finally: I'm posting my balance-windows / balance-split here rather
than on gnu.emacs.develp because I feel it's more a user-level
feature, if only the right internals were exported.
(Perhaps some users will prefer the current version of balance-windows.)
--
__Pascal Bourguignon__ http://www.informatimago.com/
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS d? s++:++ a+ C+++ UL++++ P--- L+++ E+++ W++ N+++ o-- K- w---
O- M++ V PS PE++ Y++ PGP t+ 5+ X++ R !tv b+++ DI++++ D++
G e+++ h+ r-- z?
------END GEEK CODE BLOCK------
next prev parent reply other threads:[~2005-08-07 18:23 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2005-08-03 0:47 Making the width of three windows equal Samuel
2005-08-03 1:01 ` Pascal Bourguignon
2005-08-03 2:13 ` Samuel
2005-08-03 3:11 ` Pascal Bourguignon
2005-08-06 11:59 ` New balance-windows (Was Re: Making the width of three windows equal) Ehud Karni
2005-08-06 13:27 ` New balance-windows Ehud Karni
[not found] ` <mailman.2758.1123335041.20277.help-gnu-emacs@gnu.org>
2005-08-06 16:42 ` Pascal Bourguignon
2005-08-07 17:15 ` New balance-windows (Was Re: Making the width of three windows equal) Richard M. Stallman
2005-08-08 9:27 ` Ehud Karni
2005-08-09 0:27 ` Richard M. Stallman
2005-08-10 0:05 ` New balance-windows Stefan Monnier
2005-08-10 1:48 ` Stefan Monnier
[not found] ` <mailman.3158.1123633927.20277.help-gnu-emacs@gnu.org>
2005-08-21 0:33 ` David Combs
[not found] ` <mailman.2979.1123493766.20277.help-gnu-emacs@gnu.org>
2005-08-08 17:55 ` Pascal Bourguignon
[not found] ` <mailman.2888.1123436059.20277.help-gnu-emacs@gnu.org>
2005-08-07 18:31 ` Pascal Bourguignon
[not found] ` <mailman.2754.1123329756.20277.help-gnu-emacs@gnu.org>
2005-08-06 16:39 ` Pascal Bourguignon
2005-08-06 16:58 ` Lennart Borgman
[not found] ` <mailman.2773.1123347813.20277.help-gnu-emacs@gnu.org>
2005-08-06 20:45 ` Pascal Bourguignon
2005-08-06 21:14 ` Lennart Borgman
[not found] ` <mailman.2796.1123363907.20277.help-gnu-emacs@gnu.org>
2005-08-07 18:23 ` Pascal Bourguignon [this message]
2005-08-07 18:59 ` Lennart Borgman
[not found] ` <mailman.2899.1123441421.20277.help-gnu-emacs@gnu.org>
2005-08-07 20:42 ` Pascal Bourguignon
2005-08-06 21:05 ` Ehud Karni
2005-08-07 2:17 ` Lennart Borgman
2005-08-08 9:36 ` Ehud Karni
2005-08-08 9:47 ` Lennart Borgman
[not found] ` <mailman.2988.1123495783.20277.help-gnu-emacs@gnu.org>
2005-08-08 10:18 ` David Kastrup
2005-08-08 11:18 ` Ehud Karni
2005-08-08 12:05 ` Lennart Borgman
[not found] ` <mailman.3002.1123500982.20277.help-gnu-emacs@gnu.org>
2005-08-08 18:11 ` Pascal Bourguignon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87mznttxl9.fsf@thalassa.informatimago.com \
--to=spam@mouse-potato.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).