X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/18fde8504f25571350dbcfa7803f5a9b840e5a52..0a957b2f7720a1bd177b37c32e11b4dee8f807c0:/lisp/window.el diff --git a/lisp/window.el b/lisp/window.el index c797111f11..26d1bdc9d3 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -1,7 +1,7 @@ ;;; window.el --- GNU Emacs window commands aside from those written in C -;; Copyright (C) 1985, 1989, 1992, 1993, 1994, 2000, 2001, 2002, 2004, 2005 -;; Free Software Foundation, Inc. +;; Copyright (C) 1985, 1989, 1992, 1993, 1994, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. ;; Maintainer: FSF ;; Keywords: internal @@ -10,7 +10,7 @@ ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 2, or (at your option) +;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, @@ -20,8 +20,8 @@ ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: @@ -31,7 +31,7 @@ (defvar window-size-fixed nil "*Non-nil in a buffer means windows displaying the buffer are fixed-size. -If the value is`height', then only the window's height is fixed. +If the value is `height', then only the window's height is fixed. If the value is `width', then only the window's width is fixed. Any other non-nil value fixes both the width and the height. Emacs won't change the size of any window displaying that buffer, @@ -40,26 +40,34 @@ unless you explicitly change the size, or Emacs has no other choice.") (defmacro save-selected-window (&rest body) "Execute BODY, then select the window that was selected before BODY. -Also restore the selected window of each frame as it was at the start -of this construct. -However, if a window has become dead, don't get an error, -just refrain from reselecting it. -Return the value of the last form in BODY." +The value returned is the value of the last form in BODY. + +This macro saves and restores the current buffer, since otherwise +its normal operation could potentially make a different +buffer current. It does not alter the buffer list ordering. + +This macro saves and restores the selected window, as well as +the selected window in each frame. If the previously selected +window of some frame is no longer live at the end of BODY, that +frame's selected window is left alone. If the selected window is +no longer live, then whatever window is selected at the end of +BODY remains selected." `(let ((save-selected-window-window (selected-window)) ;; It is necessary to save all of these, because calling ;; select-window changes frame-selected-window for whatever ;; frame that window is in. (save-selected-window-alist - (mapcar (lambda (frame) (list frame (frame-selected-window frame))) + (mapcar (lambda (frame) (cons frame (frame-selected-window frame))) (frame-list)))) - (unwind-protect - (progn ,@body) - (dolist (elt save-selected-window-alist) - (and (frame-live-p (car elt)) - (window-live-p (cadr elt)) - (set-frame-selected-window (car elt) (cadr elt)))) - (if (window-live-p save-selected-window-window) - (select-window save-selected-window-window))))) + (save-current-buffer + (unwind-protect + (progn ,@body) + (dolist (elt save-selected-window-alist) + (and (frame-live-p (car elt)) + (window-live-p (cdr elt)) + (set-frame-selected-window (car elt) (cdr elt)))) + (if (window-live-p save-selected-window-window) + (select-window save-selected-window-window)))))) (defun window-body-height (&optional window) "Return number of lines in window WINDOW for actual buffer text. @@ -92,9 +100,9 @@ If ALL-FRAMES is anything else, count only the selected frame." (defun window-current-scroll-bars (&optional window) "Return the current scroll-bar settings in window WINDOW. -Value is a cons (VERTICAL . HORISONTAL) where VERTICAL specifies the +Value is a cons (VERTICAL . HORIZONTAL) where VERTICAL specifies the current location of the vertical scroll-bars (left, right, or nil), -and HORISONTAL specifies the current location of the horisontal scroll +and HORIZONTAL specifies the current location of the horizontal scroll bars (top, bottom, or nil)." (let ((vert (nth 2 (window-scroll-bars window))) (hor nil)) @@ -112,7 +120,7 @@ bars (top, bottom, or nil)." PROC is called with a window as argument. Optional second arg MINIBUF t means count the minibuffer window even -if not active. MINIBUF nil or omitted means count the minibuffer iff +if not active. MINIBUF nil or omitted means count the minibuffer only if it is active. MINIBUF neither t nor nil means not to count the minibuffer even if it is active. @@ -157,7 +165,7 @@ value is returned. If no window satisfies PREDICATE, DEFAULT is returned. Optional second arg MINIBUF t means count the minibuffer window even -if not active. MINIBUF nil or omitted means count the minibuffer iff +if not active. MINIBUF nil or omitted means count the minibuffer only if it is active. MINIBUF neither t nor nil means not to count the minibuffer even if it is active. @@ -184,6 +192,18 @@ Anything else means restrict to the selected frame." (defalias 'some-window 'get-window-with-predicate) +;; This should probably be written in C (i.e., without using `walk-windows'). +(defun get-buffer-window-list (buffer &optional minibuf frame) + "Return list of all windows displaying BUFFER, or nil if none. +BUFFER can be a buffer or a buffer name. +See `walk-windows' for the meaning of MINIBUF and FRAME." + (let ((buffer (if (bufferp buffer) buffer (get-buffer buffer))) windows) + (walk-windows (function (lambda (window) + (if (eq (window-buffer window) buffer) + (setq windows (cons window windows))))) + minibuf frame) + windows)) + (defun minibuffer-window-active-p (window) "Return t if WINDOW (a minibuffer window) is now active." (eq window (active-minibuffer-window))) @@ -208,75 +228,299 @@ If WINDOW is nil or omitted, it defaults to the currently selected window." (or (= (nth 2 edges) (nth 2 (window-edges (previous-window)))) (= (nth 0 edges) (nth 0 (window-edges (next-window)))))))) - -(defun balance-windows () - "Make all visible windows the same height (approximately)." + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; `balance-windows' subroutines using `window-tree' + +;;; Translate from internal window tree format + +(defun bw-get-tree (&optional window-or-frame) + "Get a window split tree in our format. + +WINDOW-OR-FRAME must be nil, a frame, or a window. If it is nil, +then the whole window split tree for `selected-frame' is returned. +If it is a frame, then this is used instead. If it is a window, +then the smallest tree containing that window is returned." + (when window-or-frame + (unless (or (framep window-or-frame) + (windowp window-or-frame)) + (error "Not a frame or window: %s" window-or-frame))) + (let ((subtree (bw-find-tree-sub window-or-frame))) + (when subtree + (if (integerp subtree) + nil + (bw-get-tree-1 subtree))))) + +(defun bw-get-tree-1 (split) + (if (windowp split) + split + (let ((dir (car split)) + (edges (car (cdr split))) + (childs (cdr (cdr split)))) + (list + (cons 'dir (if dir 'ver 'hor)) + (cons 'b (nth 3 edges)) + (cons 'r (nth 2 edges)) + (cons 't (nth 1 edges)) + (cons 'l (nth 0 edges)) + (cons 'childs (mapcar #'bw-get-tree-1 childs)))))) + +(defun bw-find-tree-sub (window-or-frame &optional get-parent) + (let* ((window (when (windowp window-or-frame) window-or-frame)) + (frame (when (windowp window) (window-frame window))) + (wt (car (window-tree frame)))) + (when (< 1 (length (window-list frame 0))) + (if window + (bw-find-tree-sub-1 wt window get-parent) + wt)))) + +(defun bw-find-tree-sub-1 (tree win &optional get-parent) + (unless (windowp win) (error "Not a window: %s" win)) + (if (memq win tree) + (if get-parent + get-parent + tree) + (let ((childs (cdr (cdr tree))) + child + subtree) + (while (and childs (not subtree)) + (setq child (car childs)) + (setq childs (cdr childs)) + (when (and child (listp child)) + (setq subtree (bw-find-tree-sub-1 child win get-parent)))) + (if (integerp subtree) + (progn + (if (= 1 subtree) + tree + (1- subtree))) + subtree + )))) + +;;; Window or object edges + +(defun bw-l (obj) + "Left edge of OBJ." + (if (windowp obj) (nth 0 (window-edges obj)) (cdr (assq 'l obj)))) +(defun bw-t (obj) + "Top edge of OBJ." + (if (windowp obj) (nth 1 (window-edges obj)) (cdr (assq 't obj)))) +(defun bw-r (obj) + "Right edge of OBJ." + (if (windowp obj) (nth 2 (window-edges obj)) (cdr (assq 'r obj)))) +(defun bw-b (obj) + "Bottom edge of OBJ." + (if (windowp obj) (nth 3 (window-edges obj)) (cdr (assq 'b obj)))) + +;;; Split directions + +(defun bw-dir (obj) + "Return window split tree direction if OBJ. +If OBJ is a window return 'both. If it is a window split tree +then return its direction." + (if (symbolp obj) + obj + (if (windowp obj) + 'both + (let ((dir (cdr (assq 'dir obj)))) + (unless (memq dir '(hor ver both)) + (error "Can't find dir in %s" obj)) + dir)))) + +(defun bw-eqdir (obj1 obj2) + "Return t if window split tree directions are equal. +OBJ1 and OBJ2 should be either windows or window split trees in +our format. The directions returned by `bw-dir' are compared and +t is returned if they are `eq' or one of them is 'both." + (let ((dir1 (bw-dir obj1)) + (dir2 (bw-dir obj2))) + (or (eq dir1 dir2) + (eq dir1 'both) + (eq dir2 'both)))) + +;;; Building split tree + +(defun bw-refresh-edges (obj) + "Refresh the edge information of OBJ and return OBJ." + (unless (windowp obj) + (let ((childs (cdr (assq 'childs obj))) + (ol 1000) + (ot 1000) + (or -1) + (ob -1)) + (dolist (o childs) + (when (> ol (bw-l o)) (setq ol (bw-l o))) + (when (> ot (bw-t o)) (setq ot (bw-t o))) + (when (< or (bw-r o)) (setq or (bw-r o))) + (when (< ob (bw-b o)) (setq ob (bw-b o)))) + (setq obj (delq 'l obj)) + (setq obj (delq 't obj)) + (setq obj (delq 'r obj)) + (setq obj (delq 'b obj)) + (add-to-list 'obj (cons 'l ol)) + (add-to-list 'obj (cons 't ot)) + (add-to-list 'obj (cons 'r or)) + (add-to-list 'obj (cons 'b ob)) + )) + obj) + +;;; Balance windows + +(defun balance-windows (&optional window-or-frame) + "Make windows the same heights or widths in window split subtrees. + +When called non-interactively WINDOW-OR-FRAME may be either a +window or a frame. It then balances the windows on the implied +frame. If the parameter is a window only the corresponding window +subtree is balanced." (interactive) - (let ((count -1) levels newsizes level-size - ;; Don't count the lines that are above the uppermost windows. - ;; (These are the menu bar lines, if any.) - (mbl (nth 1 (window-edges (frame-first-window (selected-frame))))) - (last-window (previous-window (frame-first-window (selected-frame)))) - ;; Don't count the lines that are past the lowest main window. - total) - ;; Bottom edge of last window determines what size we have to work with. - (setq total - (+ (window-height last-window) - (nth 1 (window-edges last-window)))) - - ;; Find all the different vpos's at which windows start, - ;; then count them. But ignore levels that differ by only 1. - (let (tops (prev-top -2)) - (walk-windows (function (lambda (w) - (setq tops (cons (nth 1 (window-edges w)) - tops)))) - 'nomini) - (setq tops (sort tops '<)) - (while tops - (if (> (car tops) (1+ prev-top)) - (setq prev-top (car tops) - count (1+ count))) - (setq levels (cons (cons (car tops) count) levels)) - (setq tops (cdr tops))) - (setq count (1+ count))) - ;; Subdivide the frame into desired number of vertical levels. - (setq level-size (/ (- total mbl) count)) - (save-selected-window - ;; Set up NEWSIZES to map windows to their desired sizes. - ;; If a window ends at the bottom level, don't include - ;; it in NEWSIZES. Those windows get the right sizes - ;; by adjusting the ones above them. - (walk-windows (function - (lambda (w) - (let ((newtop (cdr (assq (nth 1 (window-edges w)) - levels))) - (newbot (cdr (assq (+ (window-height w) - (nth 1 (window-edges w))) - levels)))) - (if newbot - (setq newsizes - (cons (cons w (* level-size (- newbot newtop))) - newsizes)))))) - 'nomini) - ;; Make walk-windows start with the topmost window. - (select-window (previous-window (frame-first-window (selected-frame)))) - (let (done (count 0)) - ;; Give each window its precomputed size, or at least try. - ;; Keep trying until they all get the intended sizes, - ;; but not more than 3 times (to prevent infinite loop). - (while (and (not done) (< count 3)) - (setq done t) - (setq count (1+ count)) - (walk-windows (function (lambda (w) - (select-window w) - (let ((newsize (cdr (assq w newsizes)))) - (when newsize - (enlarge-window (- newsize - (window-height)) - nil t) - (unless (= (window-height) newsize) - (setq done nil)))))) - 'nomini)))))) + (let ( + (wt (bw-get-tree window-or-frame)) + (w) + (h) + (tried-sizes) + (last-sizes) + (windows (window-list nil 0)) + (counter 0)) + (when wt + (while (not (member last-sizes tried-sizes)) + (when last-sizes (setq tried-sizes (cons last-sizes tried-sizes))) + (setq last-sizes (mapcar (lambda (w) + (window-edges w)) + windows)) + (when (eq 'hor (bw-dir wt)) + (setq w (- (bw-r wt) (bw-l wt)))) + (when (eq 'ver (bw-dir wt)) + (setq h (- (bw-b wt) (bw-t wt)))) + (bw-balance-sub wt w h))))) + +(defun bw-adjust-window (window delta horizontal) + "Wrapper around `adjust-window-trailing-edge' with error checking. +Arguments WINDOW, DELTA and HORIZONTAL are passed on to that function." + ;; `adjust-window-trailing-edge' may fail if delta is too large. + (while (>= (abs delta) 1) + (condition-case err + (progn + (adjust-window-trailing-edge window delta horizontal) + (setq delta 0)) + (error + ;;(message "adjust: %s" (error-message-string err)) + (setq delta (/ delta 2)))))) + +(defun bw-balance-sub (wt w h) + (setq wt (bw-refresh-edges wt)) + (unless w (setq w (- (bw-r wt) (bw-l wt)))) + (unless h (setq h (- (bw-b wt) (bw-t wt)))) + (if (windowp wt) + (progn + (when w + (let ((dw (- w (- (bw-r wt) (bw-l wt))))) + (when (/= 0 dw) + (bw-adjust-window wt dw t)))) + (when h + (let ((dh (- h (- (bw-b wt) (bw-t wt))))) + (when (/= 0 dh) + (bw-adjust-window wt dh nil))))) + (let* ((childs (cdr (assq 'childs wt))) + (lastchild (car (last childs))) + (cw (when w (/ w (if (bw-eqdir 'hor wt) (length childs) 1)))) + (ch (when h (/ h (if (bw-eqdir 'ver wt) (length childs) 1))))) + (dolist (c childs) + (bw-balance-sub c cw ch))))) + +;;; A different solution to balance-windows + +(defun window-fixed-size-p (&optional window direction) + "Non-nil if WINDOW cannot be resized in DIRECTION. +DIRECTION can be nil (i.e. any), `height' or `width'." + (with-current-buffer (window-buffer window) + (let ((fixed (and (boundp 'window-size-fixed) window-size-fixed))) + (when fixed + (not (and direction + (member (cons direction window-size-fixed) + '((height . width) (width . height))))))))) + +(defvar window-area-factor 1 + "Factor by which the window area should be over-estimated. +This is used by `balance-windows-area'. +Changing this globally has no effect.") + +(defun balance-windows-area () + "Make all visible windows the same area (approximately). +See also `window-area-factor' to change the relative size of specific buffers." + (interactive) + (let* ((unchanged 0) (carry 0) (round 0) + ;; Remove fixed-size windows. + (wins (delq nil (mapcar (lambda (win) + (if (not (window-fixed-size-p win)) win)) + (window-list nil 'nomini)))) + (changelog nil) + next) + ;; Resizing a window changes the size of surrounding windows in complex + ;; ways, so it's difficult to balance them all. The introduction of + ;; `adjust-window-trailing-edge' made it a bit easier, but it is still + ;; very difficult to do. `balance-window' above takes an off-line + ;; approach: get the whole window tree, then balance it, then try to + ;; adjust the windows so they fit the result. + ;; Here, instead, we take a "local optimization" approach, where we just + ;; go through all the windows several times until nothing needs to be + ;; changed. The main problem with this approach is that it's difficult + ;; to make sure it terminates, so we use some heuristic to try and break + ;; off infinite loops. + ;; After a round without any change, we allow a second, to give a chance + ;; to the carry to propagate a minor imbalance from the end back to + ;; the beginning. + (while (< unchanged 2) + ;; (message "New round") + (setq unchanged (1+ unchanged) round (1+ round)) + (dolist (win wins) + (setq next win) + (while (progn (setq next (next-window next)) + (window-fixed-size-p next))) + ;; (assert (eq next (or (cadr (member win wins)) (car wins)))) + (let* ((horiz + (< (car (window-edges win)) (car (window-edges next)))) + (areadiff (/ (- (* (window-height next) (window-width next) + (buffer-local-value 'window-area-factor + (window-buffer next))) + (* (window-height win) (window-width win) + (buffer-local-value 'window-area-factor + (window-buffer win)))) + (max (buffer-local-value 'window-area-factor + (window-buffer win)) + (buffer-local-value 'window-area-factor + (window-buffer next))))) + (edgesize (if horiz + (+ (window-height win) (window-height next)) + (+ (window-width win) (window-width next)))) + (diff (/ areadiff edgesize))) + (when (zerop diff) + ;; Maybe diff is actually closer to 1 than to 0. + (setq diff (/ (* 3 areadiff) (* 2 edgesize)))) + (when (and (zerop diff) (not (zerop areadiff))) + (setq diff (/ (+ areadiff carry) edgesize)) + ;; Change things smoothly. + (if (or (> diff 1) (< diff -1)) (setq diff (/ diff 2)))) + (if (zerop diff) + ;; Make sure negligible differences don't accumulate to + ;; become significant. + (setq carry (+ carry areadiff)) + (bw-adjust-window win diff horiz) + ;; (sit-for 0.5) + (let ((change (cons win (window-edges win)))) + ;; If the same change has been seen already for this window, + ;; we're most likely in an endless loop, so don't count it as + ;; a change. + (unless (member change changelog) + (push change changelog) + (setq unchanged 0 carry 0))))))) + ;; We've now basically balanced all the windows. + ;; But there may be some minor off-by-one imbalance left over, + ;; so let's do some fine tuning. + ;; (bw-finetune wins) + ;; (message "Done in %d rounds" round) + )) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; I think this should be the default; I think people will prefer it--rms. (defcustom split-window-keep-point t @@ -288,7 +532,7 @@ This is convenient on slow terminals, but point can move strangely. This option applies only to `split-window-vertically' and functions that call it. `split-window' always keeps the original -point in both children," +point in both children." :type 'boolean :group 'windows) @@ -386,7 +630,11 @@ the height exactly, but attempts to be conservative, by allocating more lines than are actually needed in the case where some error may be present." (let ((delta (- height (window-text-height window)))) (unless (zerop delta) - (let ((window-min-height 1)) + ;; Setting window-min-height to a value like 1 can lead to very + ;; bizarre displays because it also allows Emacs to make *other* + ;; windows 1-line tall, which means that there's no more space for + ;; the modeline. + (let ((window-min-height (min 2 height))) ;One text line plus a modeline. (if (and window (not (eq window (selected-window)))) (save-selected-window (select-window window) @@ -451,7 +699,7 @@ in some window." (1+ (vertical-motion (buffer-size) window)))))) (defun fit-window-to-buffer (&optional window max-height min-height) - "Make WINDOW the right size to display its contents exactly. + "Make WINDOW the right height to display its contents exactly. If WINDOW is omitted or nil, it defaults to the selected window. If the optional argument MAX-HEIGHT is supplied, it is the maximum height the window is allowed to be, defaulting to the frame height. @@ -494,10 +742,7 @@ header-line." ;; desired-height lines, constrained by MIN-HEIGHT and MAX-HEIGHT. (- (max (min desired-height max-height) (or min-height window-min-height)) - window-height)) - ;; We do our own height checking, so avoid any restrictions due to - ;; window-min-height. - (window-min-height 1)) + window-height))) ;; Don't try to redisplay with the cursor at the end ;; on its own line--that would force a scroll and spoil things. @@ -542,7 +787,7 @@ If WINDOW is omitted or nil, it defaults to the selected window. Do not shrink to less than `window-min-height' lines. Do nothing if the buffer contains more lines than the present window height, or if some of the window's contents are scrolled out of view, -or if shrinking this window would also shrink another window. +or if shrinking this window would also shrink another window, or if the window is the only window of its frame." (interactive) (when (null window) @@ -568,17 +813,25 @@ or if the window is the only window of its frame." "Kill the current buffer and delete the selected window." (interactive) (let ((window-to-delete (selected-window)) + (buffer-to-kill (current-buffer)) (delete-window-hook (lambda () (condition-case nil (delete-window) (error nil))))) - (add-hook 'kill-buffer-hook delete-window-hook t t) - (if (kill-buffer (current-buffer)) - ;; If `delete-window' failed before, we rerun it to regenerate - ;; the error so it can be seen in the minibuffer. - (when (eq (selected-window) window-to-delete) - (delete-window)) - (remove-hook 'kill-buffer-hook delete-window-hook t)))) + (unwind-protect + (progn + (add-hook 'kill-buffer-hook delete-window-hook t t) + (if (kill-buffer (current-buffer)) + ;; If `delete-window' failed before, we rerun it to regenerate + ;; the error so it can be seen in the echo area. + (when (eq (selected-window) window-to-delete) + (delete-window)))) + ;; If the buffer is not dead for some reason (probably because + ;; of a `quit' signal), remove the hook again. + (condition-case nil + (with-current-buffer buffer-to-kill + (remove-hook 'kill-buffer-hook delete-window-hook t)) + (error nil))))) (defun quit-window (&optional kill window) "Quit the current buffer. Bury it, and maybe delete the selected frame. @@ -626,21 +879,144 @@ and the buffer that is killed or buried is the one in that window." ;; Maybe get rid of the window. (and window (not window-handled) (not window-solitary) (delete-window window)))) + +(defvar mouse-autoselect-window-timer nil + "Timer used by delayed window autoselection.") + +(defvar mouse-autoselect-window-position nil + "Last mouse position recorded by delayed window autoselection.") + +(defvar mouse-autoselect-window-window nil + "Last window recorded by delayed window autoselection.") + +(defvar mouse-autoselect-window-state nil + "When non-nil, special state of delayed window autoselection. +Possible values are `suspend' \(suspend autoselection after a menu or +scrollbar interaction\) and `select' \(the next invocation of +'handle-select-window' shall select the window immediately\).") + +(defun mouse-autoselect-window-cancel (&optional force) + "Cancel delayed window autoselection. +Optional argument FORCE means cancel unconditionally." + (unless (and (not force) + ;; Don't cancel while the user drags a scroll bar. + (eq this-command 'scroll-bar-toolkit-scroll) + (memq (nth 4 (event-end last-input-event)) + '(handle end-scroll))) + (setq mouse-autoselect-window-state nil) + (when (timerp mouse-autoselect-window-timer) + (cancel-timer mouse-autoselect-window-timer)) + (remove-hook 'pre-command-hook 'mouse-autoselect-window-cancel))) + +(defun mouse-autoselect-window-start (mouse-position &optional window suspend) + "Start delayed window autoselection. +MOUSE-POSITION is the last position where the mouse was seen as returned +by `mouse-position'. Optional argument WINDOW non-nil denotes the +window where the mouse was seen. Optional argument SUSPEND non-nil +means suspend autoselection." + ;; Record values for MOUSE-POSITION, WINDOW, and SUSPEND. + (setq mouse-autoselect-window-position mouse-position) + (when window (setq mouse-autoselect-window-window window)) + (setq mouse-autoselect-window-state (when suspend 'suspend)) + ;; Install timer which runs `mouse-autoselect-window-select' after + ;; `mouse-autoselect-window' seconds. + (setq mouse-autoselect-window-timer + (run-at-time + (abs mouse-autoselect-window) nil 'mouse-autoselect-window-select))) + +(defun mouse-autoselect-window-select () + "Select window with delayed window autoselection. +If the mouse position has stabilized in a non-selected window, select +that window. The minibuffer window is selected only if the minibuffer is +active. This function is run by `mouse-autoselect-window-timer'." + (condition-case nil + (let* ((mouse-position (mouse-position)) + (window + (condition-case nil + (window-at (cadr mouse-position) (cddr mouse-position) + (car mouse-position)) + (error nil)))) + (cond + ((or (menu-or-popup-active-p) + (and window + (not (coordinates-in-window-p (cdr mouse-position) window)))) + ;; A menu / popup dialog is active or the mouse is on the scroll-bar + ;; of WINDOW, temporarily suspend delayed autoselection. + (mouse-autoselect-window-start mouse-position nil t)) + ((eq mouse-autoselect-window-state 'suspend) + ;; Delayed autoselection was temporarily suspended, reenable it. + (mouse-autoselect-window-start mouse-position)) + ((and window (not (eq window (selected-window))) + (or (not (numberp mouse-autoselect-window)) + (and (> mouse-autoselect-window 0) + ;; If `mouse-autoselect-window' is positive, select + ;; window if the window is the same as before. + (eq window mouse-autoselect-window-window)) + ;; Otherwise select window if the mouse is at the same + ;; position as before. Observe that the first test after + ;; starting autoselection usually fails since the value of + ;; `mouse-autoselect-window-position' recorded there is the + ;; position where the mouse has entered the new window and + ;; not necessarily where the mouse has stopped moving. + (equal mouse-position mouse-autoselect-window-position)) + ;; The minibuffer is a candidate window if it's active. + (or (not (window-minibuffer-p window)) + (eq window (active-minibuffer-window)))) + ;; Mouse position has stabilized in non-selected window: Cancel + ;; delayed autoselection and try to select that window. + (mouse-autoselect-window-cancel t) + ;; Select window where mouse appears unless the selected window is the + ;; minibuffer. Use `unread-command-events' in order to execute pre- + ;; and post-command hooks and trigger idle timers. To avoid delaying + ;; autoselection again, set `mouse-autoselect-window-state'." + (unless (window-minibuffer-p (selected-window)) + (setq mouse-autoselect-window-state 'select) + (setq unread-command-events + (cons (list 'select-window (list window)) + unread-command-events)))) + ((or (and window (eq window (selected-window))) + (not (numberp mouse-autoselect-window)) + (equal mouse-position mouse-autoselect-window-position)) + ;; Mouse position has either stabilized in the selected window or at + ;; `mouse-autoselect-window-position': Cancel delayed autoselection. + (mouse-autoselect-window-cancel t)) + (t + ;; Mouse position has not stabilized yet, resume delayed + ;; autoselection. + (mouse-autoselect-window-start mouse-position window)))) + (error nil))) (defun handle-select-window (event) "Handle select-window events." (interactive "e") (let ((window (posn-window (event-start event)))) - (if (and (window-live-p window) - ;; Don't switch if we're currently in the minibuffer. - ;; This tries to work around problems where the minibuffer gets - ;; unselected unexpectedly, and where you then have to move - ;; your mouse all the way down to the minibuffer to select it. - (not (window-minibuffer-p (selected-window))) - ;; Don't switch to a minibuffer window unless it's active. - (or (not (window-minibuffer-p window)) - (minibuffer-window-active-p window))) - (select-window window)))) + (when (and (window-live-p window) + ;; Don't switch if we're currently in the minibuffer. + ;; This tries to work around problems where the minibuffer gets + ;; unselected unexpectedly, and where you then have to move + ;; your mouse all the way down to the minibuffer to select it. + (not (window-minibuffer-p (selected-window))) + ;; Don't switch to a minibuffer window unless it's active. + (or (not (window-minibuffer-p window)) + (minibuffer-window-active-p window))) + (unless (and (numberp mouse-autoselect-window) + (not (zerop mouse-autoselect-window)) + (not (eq mouse-autoselect-window-state 'select)) + (progn + ;; Cancel any delayed autoselection. + (mouse-autoselect-window-cancel t) + ;; Start delayed autoselection from current mouse position + ;; and window. + (mouse-autoselect-window-start (mouse-position) window) + ;; Executing a command cancels delayed autoselection. + (add-hook + 'pre-command-hook 'mouse-autoselect-window-cancel))) + ;; Reset state of delayed autoselection. + (setq mouse-autoselect-window-state nil) + (when mouse-autoselect-window + ;; Run `mouse-leave-buffer-hook' when autoselecting window. + (run-hooks 'mouse-leave-buffer-hook)) + (select-window window))))) (define-key ctl-x-map "2" 'split-window-vertically) (define-key ctl-x-map "3" 'split-window-horizontally)