]> code.delx.au - gnu-emacs/blobdiff - lisp/window.el
ChangeLog fix.
[gnu-emacs] / lisp / window.el
index ee56075bd641defcc0a0a12fee4240a9d801e70c..e8e1c6149fd77e285b8dfb96dcf45ca58dc5057f 100644 (file)
@@ -1,7 +1,8 @@
 ;;; window.el --- GNU Emacs window commands aside from those written in C
 
 ;; Copyright (C) 1985, 1989, 1992, 1993, 1994, 2000, 2001, 2002,
-;;   2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+;;   2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
+;;   Free Software Foundation, Inc.
 
 ;; Maintainer: FSF
 ;; Keywords: internal
@@ -39,40 +40,20 @@ unless you explicitly change the size, or Emacs has no other choice.")
 (make-variable-buffer-local 'window-size-fixed)
 
 (defmacro save-selected-window (&rest body)
-  "Execute BODY, then select the window that was selected before BODY.
+  "Execute BODY, then select the previously selected window.
 The value returned is the value of the last form in BODY.
 
+This macro saves and restores the selected window, as well as the
+selected window in each frame.  If the previously selected window
+is no longer live, then whatever window is selected at the end of
+BODY remains selected.  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.
+
 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) (cons frame (frame-selected-window frame)))
-                 (frame-list))))
-     (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))))))
-
-(defmacro save-selected-window-norecord (&rest body)
-  "Execute BODY, then select, but do not record previously selected window.
-This macro is like `save-selected-window' but changes neither the
-order of recently selected windows nor the buffer list."
+its normal operation could make a different buffer current.  The
+order of recently selected windows and the buffer list ordering
+are not altered by this macro (unless they are altered in BODY)."
   `(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
@@ -96,9 +77,9 @@ WINDOW defaults to the selected window.
 
 The return value does not include the mode line or the header
 line, if any.  If a line at the bottom of the window is only
-partially visible, that line is included in the return value.  If
-you do not want to include a partially visible bottom line in the
-return value, use `window-text-height' instead."
+partially visible, that line is included in the return value.
+If you do not want to include a partially visible bottom line
+in the return value, use `window-text-height' instead."
   (or window (setq window (selected-window)))
   (if (window-minibuffer-p window)
       (window-height window)
@@ -107,6 +88,16 @@ return value, use `window-text-height' instead."
                (if mode-line-format 1 0)
                (if header-line-format 1 0))))))
 
+;; See discussion in bug#4543.
+(defun window-full-height-p (&optional window)
+  "Return non-nil if WINDOW is not the result of a vertical split.
+WINDOW defaults to the selected window.  (This function is not
+appropriate for minibuffers.)"
+  (unless window
+    (setq window (selected-window)))
+  (= (window-height window)
+     (window-height (frame-root-window (window-frame window)))))
+
 (defun one-window-p (&optional nomini all-frames)
   "Return non-nil if the selected window is the only window.
 Optional arg NOMINI non-nil means don't count the minibuffer
@@ -169,8 +160,8 @@ counts, `walk-windows' includes the windows in the frame from
 which you entered the minibuffer, as well as the minibuffer
 window.
 
-ALL-FRAMES nil or omitted means cycle through all windows on
WINDOW's frame, plus the minibuffer window if specified by the
+ALL-FRAMES nil or omitted means cycle through all windows on the
selected frame, plus the minibuffer window if specified by the
  MINIBUF argument, see above.  If the minibuffer counts, cycle
  through all windows on all frames that share that minibuffer
  too.
@@ -182,8 +173,8 @@ ALL-FRAMES 0 means cycle through all windows on all visible and
  iconified frames.
 ALL-FRAMES a frame means cycle through all windows on that frame
  only.
-Anything else means cycle through all windows on WINDOW's frame
- and no others.
+Anything else means cycle through all windows on the selected
frame and no others.
 
 This function changes neither the order of recently selected
 windows nor the buffer list."
@@ -192,9 +183,9 @@ windows nor the buffer list."
   (when (window-minibuffer-p (selected-window))
     (setq minibuf t))
   ;; Make sure to not mess up the order of recently selected
-  ;; windows.  Use `save-selected-window-norecord' and `select-window'
+  ;; windows.  Use `save-selected-window' and `select-window'
   ;; with second argument non-nil for this purpose.
-  (save-selected-window-norecord
+  (save-selected-window
     (when (framep all-frames)
       (select-window (frame-first-window all-frames) 'norecord))
     (let* (walk-windows-already-seen
@@ -261,15 +252,6 @@ meaning of this argument."
      (walk-windows (lambda (w) (setq count (+ count 1)))
                   minibuf)
      count))
-
-(defun window-safely-shrinkable-p (&optional window)
-  "Return t if WINDOW can be shrunk without shrinking other windows.
-WINDOW defaults to the selected window."
-  (with-selected-window (or window (selected-window))
-    (let ((edges (window-edges)))
-      (or (= (nth 2 edges) (nth 2 (window-edges (previous-window))))
-         (= (nth 0 edges) (nth 0 (window-edges (next-window))))))))
-
 \f
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; `balance-windows' subroutines using `window-tree'
@@ -577,159 +559,186 @@ Commands such as `switch-to-buffer-other-window' and
          (function :tag "function"))
   :group 'windows)
 
-(defun special-display-p (buffer-name)
-  "Return non-nil if a buffer named BUFFER-NAME gets a special frame.
-If the value is t, `display-buffer' or `pop-to-buffer' would
-create a special frame for that buffer using the default frame
-parameters.
-
-If the value is a list, it is a list of frame parameters that
-would be used to make a frame for that buffer.  The variables
-`special-display-buffer-names' and `special-display-regexps'
-control this."
-  (let (tmp)
-  (cond
-   ((not (stringp buffer-name)))
-   ;; Make sure to return t in the following two cases.
-   ((member buffer-name special-display-buffer-names) t)
-     ((setq tmp (assoc buffer-name special-display-buffer-names)) (cdr tmp))
-   ((catch 'found
-      (dolist (regexp special-display-regexps)
-       (cond
-        ((stringp regexp)
-         (when (string-match-p regexp buffer-name)
-           (throw 'found t)))
-        ((and (consp regexp) (stringp (car regexp))
-              (string-match-p (car regexp) buffer-name))
-            (throw 'found (cdr regexp))))))))))
-
 (defcustom special-display-buffer-names nil
-  "List of buffer names that should have their own special frames.
+  "List of names of buffers that should be displayed specially.
 Displaying a buffer with `display-buffer' or `pop-to-buffer', if
-its name is in this list, makes a special frame for it using
-`special-display-function'.  See also `special-display-regexps'.
-
-An element of the list can be a list instead of just a string.
-There are two ways to use a list as an element:
-  (BUFFER FRAME-PARAMETERS...)  (BUFFER FUNCTION OTHER-ARGS...)
-In the first case, the FRAME-PARAMETERS are pairs of the form
-\(PARAMETER . VALUE); these parameter values are used to create
-the frame.  In the second case, FUNCTION is called with BUFFER as
-the first argument, followed by the OTHER-ARGS--it can display
-BUFFER in any way it likes.  All this is done by the function
-found in `special-display-function'.
-
-If the specified frame parameters include (same-buffer . t), the
-buffer is displayed in the currently selected window.  Otherwise, if
-they include (same-frame . t), the buffer is displayed in a new window
-in the currently selected frame.
-
-If this variable appears \"not to work\", because you add a name to it
-but that buffer still appears in the selected window, look at the
-values of `same-window-buffer-names' and `same-window-regexps'.
-Those variables take precedence over this one."
-  :type '(repeat (choice :tag "Buffer"
-                        :value ""
-                        (string :format "%v")
-                        (cons :tag "With attributes"
-                              :format "%v"
-                              :value ("" . nil)
-                              (string :format "%v")
-                              (repeat :tag "Attributes"
-                                      (cons :format "%v"
-                                            (symbol :tag "Parameter")
-                                            (sexp :tag "Value"))))))
+its name is in this list, displays the buffer in a way specified
+by `special-display-function'.  `special-display-popup-frame'
+\(the default for `special-display-function') usually displays
+the buffer in a separate frame made with the parameters specified
+by `special-display-frame-alist'.  If `special-display-function'
+has been set to some other function, that function is called with
+the buffer as first, and nil as second argument.
+
+Alternatively, an element of this list can be specified as
+\(BUFFER-NAME FRAME-PARAMETERS), where BUFFER-NAME is a buffer
+name and FRAME-PARAMETERS an alist of \(PARAMETER . VALUE) pairs.
+`special-display-popup-frame' will interpret such pairs as frame
+parameters when it creates a special frame, overriding the
+corresponding values from `special-display-frame-alist'.
+
+As a special case, if FRAME-PARAMETERS contains (same-window . t)
+`special-display-popup-frame' displays that buffer in the
+selected window.  If FRAME-PARAMETERS contains (same-frame . t),
+it displays that buffer in a window on the selected frame.
+
+If `special-display-function' specifies some other function than
+`special-display-popup-frame', that function is called with the
+buffer named BUFFER-NAME as first, and FRAME-PARAMETERS as second
+argument.
+
+Finally, an element of this list can be also specified as
+\(BUFFER-NAME FUNCTION OTHER-ARGS).  In that case,
+`special-display-popup-frame' will call FUNCTION with the buffer
+named BUFFER-NAME as first argument, and OTHER-ARGS as the
+second.  If `special-display-function' specifies some other
+function, that function is called with the buffer named
+BUFFER-NAME as first, and the element's cdr as second argument.
+
+If this variable appears \"not to work\", because you added a
+name to it but the corresponding buffer is displayed in the
+selected window, look at the values of `same-window-buffer-names'
+and `same-window-regexps'.  Those variables take precedence over
+this one.
+
+See also `special-display-regexps'."
+  :type '(repeat
+         (choice :tag "Buffer"
+                 :value ""
+                 (string :format "%v")
+                 (cons :tag "With parameters"
+                       :format "%v"
+                       :value ("" . nil)
+                       (string :format "%v")
+                       (repeat :tag "Parameters"
+                               (cons :format "%v"
+                                     (symbol :tag "Parameter")
+                                     (sexp :tag "Value"))))
+                 (list :tag "With function"
+                       :format "%v"
+                       :value ("" . nil)
+                       (string :format "%v")
+                       (function :tag "Function")
+                       (repeat :tag "Arguments" (sexp)))))
+  :group 'windows
   :group 'frames)
 
+;;;###autoload
+(put 'special-display-buffer-names 'risky-local-variable t)
+
 (defcustom special-display-regexps nil
-  "List of regexps saying which buffers should have their own special frames.
-When displaying a buffer with `display-buffer' or
-`pop-to-buffer', if any regexp in this list matches the buffer
-name, it makes a special frame for the buffer by calling
-`special-display-function'.
-
-An element of the list can be a list instead of just a string.
-There are two ways to use a list as an element:
-  (REGEXP FRAME-PARAMETERS...)  (REGEXP FUNCTION OTHER-ARGS...)
-In the first case, the FRAME-PARAMETERS are pairs of the form
-\(PARAMETER . VALUE); these parameter values are used to create
-the frame.  In the second case, FUNCTION is called with BUFFER as
-the first argument, followed by the OTHER-ARGS--it can display
-the buffer in any way it likes.  All this is done by the function
-found in `special-display-function'.
-
-If the specified frame parameters include (same-buffer . t), the
-buffer is displayed in the currently selected window.  Otherwise,
-if they include (same-frame . t), the buffer is displayed in a
-new window in the currently selected frame.
-
-If this variable appears \"not to work\", because you add a
-regexp to it but the matching buffers still appear in the
+  "List of regexps saying which buffers should be displayed specially.
+Displaying a buffer with `display-buffer' or `pop-to-buffer', if
+any regexp in this list matches its name, displays it specially
+using `special-display-function'.  `special-display-popup-frame'
+\(the default for `special-display-function') usually displays
+the buffer in a separate frame made with the parameters specified
+by `special-display-frame-alist'.  If `special-display-function'
+has been set to some other function, that function is called with
+the buffer as first, and nil as second argument.
+
+Alternatively, an element of this list can be specified as
+\(REGEXP FRAME-PARAMETERS), where REGEXP is a regexp as above and
+FRAME-PARAMETERS an alist of (PARAMETER . VALUE) pairs.
+`special-display-popup-frame' will then interpret these pairs as
+frame parameters when creating a special frame for a buffer whose
+name matches REGEXP, overriding the corresponding values from
+`special-display-frame-alist'.
+
+As a special case, if FRAME-PARAMETERS contains (same-window . t)
+`special-display-popup-frame' displays buffers matching REGEXP in
+the selected window.  \(same-frame . t) in FRAME-PARAMETERS means
+to display such buffers in a window on the selected frame.
+
+If `special-display-function' specifies some other function than
+`special-display-popup-frame', that function is called with the
+buffer whose name matched REGEXP as first, and FRAME-PARAMETERS
+as second argument.
+
+Finally, an element of this list can be also specified as
+\(REGEXP FUNCTION OTHER-ARGS).  `special-display-popup-frame'
+will then call FUNCTION with the buffer whose name matched
+REGEXP as first, and OTHER-ARGS as second argument.  If
+`special-display-function' specifies some other function, that
+function is called with the buffer whose name matched REGEXP
+as first, and the element's cdr as second argument.
+
+If this variable appears \"not to work\", because you added a
+name to it but the corresponding buffer is displayed in the
 selected window, look at the values of `same-window-buffer-names'
 and `same-window-regexps'.  Those variables take precedence over
-this one."
-  :type '(repeat (choice :tag "Buffer"
-                        :value ""
-                        (regexp :format "%v")
-                        (cons :tag "With attributes"
-                              :format "%v"
-                              :value ("" . nil)
-                              (regexp :format "%v")
-                              (repeat :tag "Attributes"
-                                      (cons :format "%v"
-                                            (symbol :tag "Parameter")
-                                            (sexp :tag "Value"))))))
+this one.
+
+See also `special-display-buffer-names'."
+  :type '(repeat
+         (choice :tag "Buffer"
+                 :value ""
+                 (regexp :format "%v")
+                 (cons :tag "With parameters"
+                       :format "%v"
+                       :value ("" . nil)
+                       (regexp :format "%v")
+                       (repeat :tag "Parameters"
+                               (cons :format "%v"
+                                     (symbol :tag "Parameter")
+                                     (sexp :tag "Value"))))
+                 (list :tag "With function"
+                       :format "%v"
+                       :value ("" . nil)
+                       (regexp :format "%v")
+                       (function :tag "Function")
+                       (repeat :tag "Arguments" (sexp)))))
+  :group 'windows
   :group 'frames)
 
+(defun special-display-p (buffer-name)
+  "Return non-nil if a buffer named BUFFER-NAME gets a special frame.
+More precisely, return t if `special-display-buffer-names' or
+`special-display-regexps' contain a string entry equaling or
+matching BUFFER-NAME.  If `special-display-buffer-names' or
+`special-display-regexps' contain a list entry whose car equals
+or matches BUFFER-NAME, the return value is the cdr of that
+entry."
+  (let (tmp)
+    (cond
+     ((not (stringp buffer-name)))
+     ((member buffer-name special-display-buffer-names)
+      t)
+     ((setq tmp (assoc buffer-name special-display-buffer-names))
+      (cdr tmp))
+     ((catch 'found
+       (dolist (regexp special-display-regexps)
+         (cond
+          ((stringp regexp)
+           (when (string-match-p regexp buffer-name)
+             (throw 'found t)))
+          ((and (consp regexp) (stringp (car regexp))
+                (string-match-p (car regexp) buffer-name))
+           (throw 'found (cdr regexp))))))))))
+
 (defcustom special-display-function 'special-display-popup-frame
-  "Function to call to make a new frame for a special buffer.
-It is called with two arguments, the buffer and optional buffer
-specific data, and should return a window displaying that buffer.
-The default value normally makes a separate frame for the buffer,
-using `special-display-frame-alist' to specify the frame
-parameters.
-
-But if the buffer specific data includes (same-buffer . t) then
-the buffer is displayed in the current selected window.
-Otherwise if it includes (same-frame . t) then the buffer is
-displayed in a new window in the currently selected frame.
-
-A buffer is special if it is listed in
+  "Function to call for displaying special buffers.
+This function is called with two arguments - the buffer and,
+optionally, a list - and should return a window displaying that
+buffer.  The default value usually makes a separate frame for the
+buffer using `special-display-frame-alist' to specify the frame
+parameters.  See the definition of `special-display-popup-frame'
+for how to specify such a function.
+
+A buffer is special when its name is either listed in
 `special-display-buffer-names' or matches a regexp in
 `special-display-regexps'."
   :type 'function
   :group 'frames)
 
-(defun same-window-p (buffer-name)
-  "Return non-nil if a buffer named BUFFER-NAME would be shown in the \"same\" window.
-This function returns non-nil if `display-buffer' or
-`pop-to-buffer' would show a buffer named BUFFER-NAME in the
-selected rather than \(as usual\) some other window.  See
-`same-window-buffer-names' and `same-window-regexps'."
-  (cond
-   ((not (stringp buffer-name)))
-   ;; The elements of `same-window-buffer-names' can be buffer
-   ;; names or cons cells whose cars are buffer names.
-   ((member buffer-name same-window-buffer-names))
-   ((assoc buffer-name same-window-buffer-names))
-   ((catch 'found
-      (dolist (regexp same-window-regexps)
-       ;; The elements of `same-window-regexps' can be regexps
-       ;; or cons cells whose cars are regexps.
-       (when (or (and (stringp regexp)
-                      (string-match regexp buffer-name))
-                 (and (consp regexp) (stringp (car regexp))
-                      (string-match-p (car regexp) buffer-name)))
-         (throw 'found t)))))))
-
 (defcustom same-window-buffer-names nil
   "List of names of buffers that should appear in the \"same\" window.
 `display-buffer' and `pop-to-buffer' show a buffer whose name is
 on this list in the selected rather than some other window.
 
 An element of this list can be a cons cell instead of just a
-string.  In that case the car must be a string specifying the
-buffer name.  This is for compatibility with
+string.  In that case, the cell's car must be a string specifying
+the buffer name.  This is for compatibility with
 `special-display-buffer-names'; the cdr of the cons cell is
 ignored.
 
@@ -744,18 +753,39 @@ matches a regexp on this list in the selected rather than some
 other window.
 
 An element of this list can be a cons cell instead of just a
-string.  In that case the car must be a string, which specifies
+string.  In that case, the cell's car must be a regexp matching
 the buffer name.  This is for compatibility with
-`special-display-buffer-names'; the cdr of the cons cell is
-ignored.
+`special-display-regexps'; the cdr of the cons cell is ignored.
 
 See also `same-window-buffer-names'."
   :type '(repeat (regexp :format "%v"))
   :group 'windows)
 
+(defun same-window-p (buffer-name)
+  "Return non-nil if a buffer named BUFFER-NAME would be shown in the \"same\" window.
+This function returns non-nil if `display-buffer' or
+`pop-to-buffer' would show a buffer named BUFFER-NAME in the
+selected rather than \(as usual\) some other window.  See
+`same-window-buffer-names' and `same-window-regexps'."
+  (cond
+   ((not (stringp buffer-name)))
+   ;; The elements of `same-window-buffer-names' can be buffer
+   ;; names or cons cells whose cars are buffer names.
+   ((member buffer-name same-window-buffer-names))
+   ((assoc buffer-name same-window-buffer-names))
+   ((catch 'found
+      (dolist (regexp same-window-regexps)
+       ;; The elements of `same-window-regexps' can be regexps
+       ;; or cons cells whose cars are regexps.
+       (when (or (and (stringp regexp)
+                      (string-match regexp buffer-name))
+                 (and (consp regexp) (stringp (car regexp))
+                      (string-match-p (car regexp) buffer-name)))
+         (throw 'found t)))))))
+
 (defcustom pop-up-frames nil
   "Whether `display-buffer' should make a separate frame.
-If nil, never make a seperate frame.
+If nil, never make a separate frame.
 If the value is `graphic-only', make a separate frame
 on graphic displays only.
 Any other non-nil value means always make a separate frame."
@@ -778,66 +808,78 @@ that frame."
   :type 'boolean
   :group 'windows)
 
-(defcustom split-height-threshold 80
-  "Minimum height of window to be split vertically.
-If the value is a number, `display-buffer' can split a window
-only if it has at least as many lines.  If the value is nil,
-`display-buffer' cannot split a window vertically.
-
-If the window is the only window on its frame, `display-buffer'
-can split it regardless of this value."
-  :type '(choice (const nil) (number :tag "lines"))
+(defcustom split-window-preferred-function 'split-window-sensibly
+  "Function called by `display-buffer' routines to split a window.
+This function is called with a window as single argument and is
+supposed to split that window and return the new window.  If the
+window can (or shall) not be split, it is supposed to return nil.
+The default is to call the function `split-window-sensibly' which
+tries to split the window in a way which seems most suitable.
+You can customize the options `split-height-threshold' and/or
+`split-width-threshold' in order to have `split-window-sensibly'
+prefer either vertical or horizontal splitting.
+
+If you set this to any other function, bear in mind that the
+`display-buffer' routines may call this function two times.  The
+argument of the first call is the largest window on its frame.
+If that call fails to return a live window, the function is
+called again with the least recently used window as argument.  If
+that call fails too, `display-buffer' will use an existing window
+to display its buffer.
+
+The window selected at the time `display-buffer' was invoked is
+still selected when this function is called.  Hence you can
+compare the window argument with the value of `selected-window'
+if you intend to split the selected window instead or if you do
+not want to split the selected window."
+  :type 'function
   :version "23.1"
   :group 'windows)
 
-(defcustom split-width-threshold 160
-  "Minimum width of window to be split horizontally.
-If the value is a number, `display-buffer' can split a window
-only if it has at least as many columns.  If the value is nil,
-`display-buffer' cannot split a window horizontally."
-  :type '(choice (const nil) (number :tag "columns"))
+(defcustom split-height-threshold 80
+  "Minimum height for splitting windows sensibly.
+If this is an integer, `split-window-sensibly' may split a window
+vertically only if it has at least this many lines.  If this is
+nil, `split-window-sensibly' is not allowed to split a window
+vertically.  If, however, a window is the only window on its
+frame, `split-window-sensibly' may split it vertically
+disregarding the value of this variable."
+  :type '(choice (const nil) (integer :tag "lines"))
   :version "23.1"
   :group 'windows)
 
-(defcustom split-window-preferred-function nil
-  "Function used by `display-buffer' to split windows.
-If non-nil, a function called with a window as single argument
-supposed to split that window and return the new window.  If the
-function returns nil the window is not split.
-
-If nil, `display-buffer' will split the window respecting the
-values of `split-height-threshold' and `split-width-threshold'."
-  :type '(choice (const nil) (function :tag "Function"))
+(defcustom split-width-threshold 160
+  "Minimum width for splitting windows sensibly.
+If this is an integer, `split-window-sensibly' may split a window
+horizontally only if it has at least this many columns.  If this
+is nil, `split-window-sensibly' is not allowed to split a window
+horizontally."
+  :type '(choice (const nil) (integer :tag "columns"))
   :version "23.1"
   :group 'windows)
 
-(defun window--splittable-p (window &optional horizontal)
-  "Return non-nil if WINDOW can be split evenly.
-Optional argument HORIZONTAL non-nil means check whether WINDOW
-can be split horizontally.
+(defun window-splittable-p (window &optional horizontal)
+  "Return non-nil if `split-window-sensibly' may split WINDOW.
+Optional argument HORIZONTAL nil or omitted means check whether
+`split-window-sensibly' may split WINDOW vertically.  HORIZONTAL
+non-nil means check whether WINDOW may be split horizontally.
 
-WINDOW can be split vertically when the following conditions
+WINDOW may be split vertically when the following conditions
 hold:
-
 - `window-size-fixed' is either nil or equals `width' for the
   buffer of WINDOW.
-
-- `split-height-threshold' is a number and WINDOW is at least as
+- `split-height-threshold' is an integer and WINDOW is at least as
   high as `split-height-threshold'.
-
 - When WINDOW is split evenly, the emanating windows are at least
   `window-min-height' lines tall and can accommodate at least one
   line plus - if WINDOW has one - a mode line.
 
-WINDOW can be split horizontally when the following conditions
+WINDOW may be split horizontally when the following conditions
 hold:
-
 - `window-size-fixed' is either nil or equals `height' for the
   buffer of WINDOW.
-
-- `split-width-threshold' is a number and WINDOW is at least as
+- `split-width-threshold' is an integer and WINDOW is at least as
   wide as `split-width-threshold'.
-
 - When WINDOW is split evenly, the emanating windows are at least
   `window-min-width' or two (whichever is larger) columns wide."
   (when (window-live-p window)
@@ -866,30 +908,68 @@ hold:
                      (* 2 (max window-min-height
                                (if mode-line-format 2 1))))))))))
 
+(defun split-window-sensibly (window)
+  "Split WINDOW in a way suitable for `display-buffer'.
+If `split-height-threshold' specifies an integer, WINDOW is at
+least `split-height-threshold' lines tall and can be split
+vertically, split WINDOW into two windows one above the other and
+return the lower window.  Otherwise, if `split-width-threshold'
+specifies an integer, WINDOW is at least `split-width-threshold'
+columns wide and can be split horizontally, split WINDOW into two
+windows side by side and return the window on the right.  If this
+can't be done either and WINDOW is the only window on its frame,
+try to split WINDOW vertically disregarding any value specified
+by `split-height-threshold'.  If that succeeds, return the lower
+window.  Return nil otherwise.
+
+By default `display-buffer' routines call this function to split
+the largest or least recently used window.  To change the default
+customize the option `split-window-preferred-function'.
+
+You can enforce this function to not split WINDOW horizontally,
+by setting \(or binding) the variable `split-width-threshold' to
+nil.  If, in addition, you set `split-height-threshold' to zero,
+chances increase that this function does split WINDOW vertically.
+
+In order to not split WINDOW vertically, set \(or bind) the
+variable `split-height-threshold' to nil.  Additionally, you can
+set `split-width-threshold' to zero to make a horizontal split
+more likely to occur.
+
+Have a look at the function `window-splittable-p' if you want to
+know how `split-window-sensibly' determines whether WINDOW can be
+split."
+  (or (and (window-splittable-p window)
+          ;; Split window vertically.
+          (with-selected-window window
+            (split-window-vertically)))
+      (and (window-splittable-p window t)
+          ;; Split window horizontally.
+          (with-selected-window window
+            (split-window-horizontally)))
+      (and (eq window (frame-root-window (window-frame window)))
+          (not (window-minibuffer-p window))
+          ;; If WINDOW is the only window on its frame and is not the
+          ;; minibuffer window, try to split it vertically disregarding
+          ;; the value of `split-height-threshold'.
+          (let ((split-height-threshold 0))
+            (when (window-splittable-p window)
+              (with-selected-window window
+                (split-window-vertically)))))))
+
 (defun window--try-to-split-window (window)
-  "Split WINDOW if it is splittable.
-See `window--splittable-p' for how to determine whether a window
-is splittable.  If WINDOW can be split, return the value returned
-by `split-window' (or `split-window-preferred-function')."
-  (when (and (window-live-p window)
-            (not (frame-parameter (window-frame window) 'unsplittable)))
-    (if (functionp split-window-preferred-function)
-       ;; `split-window-preferred-function' is specified, so use it.
-       (funcall split-window-preferred-function window)
-      (or (and (window--splittable-p window)
-              ;; Split window vertically.
-              (split-window window))
-         (and (window--splittable-p window t)
-              ;; Split window horizontally.
-              (split-window window nil t))
-         (and (eq window (frame-root-window (window-frame window)))
-              (not (window-minibuffer-p window))
-              ;; If WINDOW is the only window on its frame and not the
-              ;; minibuffer window, attempt to split it vertically
-              ;; disregarding the value of `split-height-threshold'.
-              (let ((split-height-threshold 0))
-                (and (window--splittable-p window)
-                     (split-window window))))))))
+  "Try to split WINDOW.
+Return value returned by `split-window-preferred-function' if it
+represents a live window, nil otherwise."
+      (and (window-live-p window)
+          (not (frame-parameter (window-frame window) 'unsplittable))
+          (let ((new-window
+                 ;; Since `split-window-preferred-function' might
+                 ;; throw an error use `condition-case'.
+                 (condition-case nil
+                     (funcall split-window-preferred-function window)
+                   (error nil))))
+            (and (window-live-p new-window) new-window))))
 
 (defun window--frame-usable-p (frame)
   "Return FRAME if it can be used to display a buffer."
@@ -898,9 +978,15 @@ by `split-window' (or `split-window-preferred-function')."
       ;; `frame-root-window' may be an internal window which is considered
       ;; "dead" by `window-live-p'.  Hence if `window' is not live we
       ;; implicitly know that `frame' has a visible window we can use.
-      (when (or (not (window-live-p window))
-               (and (not (window-minibuffer-p window))
-                    (not (window-dedicated-p window))))
+      (unless (and (window-live-p window)
+                   (or (window-minibuffer-p window)
+                       ;; If the window is soft-dedicated, the frame is usable.
+                       ;; Actually, even if the window is really dedicated,
+                       ;; the frame is still usable by splitting it.
+                       ;; At least Emacs-22 allowed it, and it is desirable
+                       ;; when displaying same-frame windows.
+                       nil ; (eq t (window-dedicated-p window))
+                       ))
        frame))))
 
 (defcustom even-window-heights t
@@ -920,7 +1006,7 @@ is higher than WINDOW."
             (not (eq window (selected-window)))
             ;; Don't resize minibuffer windows.
             (not (window-minibuffer-p (selected-window)))
-            (> (window-height (selected-window)) (window-height window)) 
+            (> (window-height (selected-window)) (window-height window))
             (eq (window-frame window) (window-frame (selected-window)))
             (let ((sel-edges (window-edges (selected-window)))
                   (win-edges (window-edges window)))
@@ -950,13 +1036,21 @@ Do not raise the selected frame.  Return WINDOW."
       (raise-frame frame))
     window))
 
-(defun window--display-buffer-2 (buffer window)
+(defun window--display-buffer-2 (buffer window &optional dedicated)
   "Display BUFFER in WINDOW and make its frame visible.
+Set `window-dedicated-p' to DEDICATED if non-nil.
 Return WINDOW."
   (when (and (buffer-live-p buffer) (window-live-p window))
     (set-window-buffer window buffer)
+    (when dedicated
+      (set-window-dedicated-p window dedicated))
     (window--display-buffer-1 window)))
 
+(defvar display-buffer-mark-dedicated nil
+  "If non-nil, `display-buffer' marks the windows it creates as dedicated.
+The actual non-nil value of this variable will be copied to the
+`window-dedicated-p' flag.")
+
 (defun display-buffer (buffer-or-name &optional not-this-window frame)
   "Make buffer BUFFER-OR-NAME appear in some window but don't select it.
 BUFFER-OR-NAME must be a buffer or the name of an existing
@@ -1029,10 +1123,15 @@ consider all visible or iconified frames."
                                 (not (last-nonminibuffer-frame)))
                             0)
                        (last-nonminibuffer-frame))))
-       (and (setq window-to-use (get-buffer-window buffer frames))
-            (or can-use-selected-window
-                (not (eq (selected-window) window-to-use)))))
-      ;; If the buffer is already displayed in some window use that.
+       (setq window-to-use
+             (catch 'found
+               ;; Search frames for a window displaying BUFFER.  Return
+               ;; the selected window only if we are allowed to do so.
+               (dolist (window (get-buffer-window-list buffer 'nomini frames))
+                 (when (or can-use-selected-window
+                           (not (eq (selected-window) window)))
+                   (throw 'found window))))))
+      ;; The buffer is already displayed in some window; use that.
       (window--display-buffer-1 window-to-use))
      ((and special-display-function
           ;; `special-display-p' returns either t or a list of frame
@@ -1043,8 +1142,8 @@ consider all visible or iconified frames."
                        buffer (if (listp pars) pars))))))
      ((or use-pop-up-frames (not frame-to-use))
       ;; We want or need a new frame.
-      (window--display-buffer-2
-       buffer (frame-selected-window (funcall pop-up-frame-function))))
+      (let ((win (frame-selected-window (funcall pop-up-frame-function))))
+        (window--display-buffer-2 buffer win display-buffer-mark-dedicated)))
      ((and pop-up-windows
           ;; Make a new window.
           (or (not (frame-parameter frame-to-use 'unsplittable))
@@ -1059,16 +1158,34 @@ consider all visible or iconified frames."
                 (or (window--try-to-split-window
                      (get-largest-window frame-to-use t))
                     (window--try-to-split-window
-                     (get-lru-window frame-to-use t))))
-          (window--display-buffer-2 buffer window-to-use)))
-     ((setq window-to-use
-           ;; Reuse an existing window.
-           (or (get-lru-window frame-to-use)
-               (get-buffer-window buffer 'visible)
-               (get-largest-window 'visible nil)
-               (get-buffer-window buffer 0)
-               (get-largest-window 0 nil)
-               (frame-selected-window (funcall pop-up-frame-function))))
+                     (get-lru-window frame-to-use t)))))
+      (window--display-buffer-2 buffer window-to-use
+                                display-buffer-mark-dedicated))
+     ((let ((window-to-undedicate
+            ;; When NOT-THIS-WINDOW is non-nil, temporarily dedicate
+            ;; the selected window to its buffer, to avoid that some of
+            ;; the `get-' routines below choose it.  (Bug#1415)
+            (and not-this-window (not (window-dedicated-p))
+                 (set-window-dedicated-p (selected-window) t)
+                 (selected-window))))
+       (unwind-protect
+           (setq window-to-use
+                 ;; Reuse an existing window.
+                 (or (get-lru-window frame-to-use)
+                     (let ((window (get-buffer-window buffer 'visible)))
+                       (unless (and not-this-window
+                                    (eq window (selected-window)))
+                         window))
+                     (get-largest-window 'visible)
+                     (let ((window (get-buffer-window buffer 0)))
+                       (unless (and not-this-window
+                                    (eq window (selected-window)))
+                         window))
+                     (get-largest-window 0)
+                     (frame-selected-window (funcall pop-up-frame-function))))
+         (when (window-live-p window-to-undedicate)
+           ;; Restore dedicated status of selected window.
+           (set-window-dedicated-p window-to-undedicate nil))))
       (window--even-window-heights window-to-use)
       (window--display-buffer-2 buffer window-to-use)))))
 
@@ -1163,8 +1280,7 @@ window."
         (setq size (+ (window-height) size)))
     (setq new-window (split-window nil size))
     (unless split-window-keep-point
-      (save-excursion
-       (set-buffer (window-buffer))
+      (with-current-buffer (window-buffer)
        (goto-char (window-start))
        (setq moved (vertical-motion (window-height)))
        (set-window-start new-window (point))
@@ -1216,7 +1332,7 @@ The selected window remains selected.  Return the new window."
 
 \f
 (defun set-window-text-height (window height)
-  "Sets the height in lines of the text display area of WINDOW to HEIGHT.
+  "Set the height in lines of the text display area of WINDOW to HEIGHT.
 HEIGHT doesn't include the mode line or header line, if any, or
 any partial-height lines in the text display area.
 
@@ -1232,7 +1348,7 @@ where some error may be present."
       ;; 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-norecord
+           (save-selected-window
              (select-window window 'norecord)
              (enlarge-window delta))
          (enlarge-window delta))))))
@@ -1302,84 +1418,112 @@ in some window."
   "Adjust height of WINDOW to display its buffer's contents exactly.
 WINDOW defaults to the selected window.
 Optional argument MAX-HEIGHT specifies the maximum height of the
-window and defaults to the height of WINDOW's frame.
+window and defaults to the maximum permissible height of a window
+on WINDOW's frame.
 Optional argument MIN-HEIGHT specifies the minimum height of the
 window and defaults to `window-min-height'.
 Both, MAX-HEIGHT and MIN-HEIGHT are specified in lines and
 include the mode line and header line, if any.
-Always return nil."
+
+Return non-nil if height was orderly adjusted, nil otherwise.
+
+Caution: This function can delete WINDOW and/or other windows
+when their height shrinks to less than MIN-HEIGHT."
   (interactive)
+  ;; Do all the work in WINDOW and its buffer and restore the selected
+  ;; window and the current buffer when we're done.
+  (let ((old-buffer (current-buffer))
+       value)
+    (with-selected-window (or window (setq window (selected-window)))
+      (set-buffer (window-buffer))
+      ;; Use `condition-case' to handle any fixed-size windows and other
+      ;; pitfalls nearby.
+      (condition-case nil
+         (let* (;; MIN-HEIGHT must not be less than 1 and defaults to
+                ;; `window-min-height'.
+                (min-height (max (or min-height window-min-height) 1))
+                (max-window-height
+                 ;; Maximum height of any window on this frame.
+                 (min (window-height (frame-root-window)) (frame-height)))
+                ;; MAX-HEIGHT must not be larger than max-window-height and
+                ;; defaults to max-window-height.
+                (max-height
+                 (min (or max-height max-window-height) max-window-height))
+                (desired-height
+                 ;; The height necessary to show all of WINDOW's buffer,
+                 ;; constrained by MIN-HEIGHT and MAX-HEIGHT.
+                 (max
+                  (min
+                   ;; For an empty buffer `count-screen-lines' returns zero.
+                   ;; Even in that case we need one line for the cursor.
+                   (+ (max (count-screen-lines) 1)
+                      ;; For non-minibuffers count the mode line, if any.
+                      (if (and (not (window-minibuffer-p)) mode-line-format)
+                          1 0)
+                      ;; Count the header line, if any.
+                      (if header-line-format 1 0))
+                   max-height)
+                  min-height))
+                (delta
+                 ;; How much the window height has to change.
+                 (if (= (window-height) (window-height (frame-root-window)))
+                     ;; Don't try to resize a full-height window.
+                     0
+                   (- desired-height (window-height))))
+                ;; Do something reasonable so `enlarge-window' can make
+                ;; windows as small as MIN-HEIGHT.
+                (window-min-height (min min-height window-min-height)))
+           ;; Don't try to redisplay with the cursor at the end on its
+           ;; own line--that would force a scroll and spoil things.
+           (when (and (eobp) (bolp) (not (bobp)))
+             (set-window-point window (1- (window-point))))
+           ;; Adjust WINDOW's height to the nominally correct one
+           ;; (which may actually be slightly off because of variable
+           ;; height text, etc).
+           (unless (zerop delta)
+             (enlarge-window delta))
+           ;; `enlarge-window' might have deleted WINDOW, so make sure
+           ;; WINDOW's still alive for the remainder of this.
+           ;; Note: Deleting WINDOW is clearly counter-intuitive in
+           ;; this context, but we can't do much about it given the
+           ;; current semantics of `enlarge-window'.
+           (when (window-live-p window)
+             ;; Check if the last line is surely fully visible.  If
+             ;; not, enlarge the window.
+             (let ((end (save-excursion
+                          (goto-char (point-max))
+                          (when (and (bolp) (not (bobp)))
+                            ;; Don't include final newline.
+                            (backward-char 1))
+                          (when truncate-lines
+                            ;; If line-wrapping is turned off, test the
+                            ;; beginning of the last line for
+                            ;; visibility instead of the end, as the
+                            ;; end of the line could be invisible by
+                            ;; virtue of extending past the edge of the
+                            ;; window.
+                            (forward-line 0))
+                          (point))))
+               (set-window-vscroll window 0)
+               (while (and (< desired-height max-height)
+                           (= desired-height (window-height))
+                           (not (pos-visible-in-window-p end)))
+                 (enlarge-window 1)
+                 (setq desired-height (1+ desired-height))))
+             ;; Return non-nil only if nothing "bad" happened.
+             (setq value t)))
+       (error nil)))
+    (when (buffer-live-p old-buffer)
+      (set-buffer old-buffer))
+    value))
 
-  (when (null window)
-    (setq window (selected-window)))
-  (when (null max-height)
-    (setq max-height (frame-height (window-frame window))))
-
-  (let* ((buf
-         ;; Buffer that is displayed in WINDOW
-         (window-buffer window))
-        (window-height
-         ;; The current height of WINDOW
-         (window-height window))
-        (desired-height
-         ;; The height necessary to show the buffer displayed by WINDOW
-         ;; (`count-screen-lines' always works on the current buffer).
-         (with-current-buffer buf
-           (+ (count-screen-lines)
-              ;; If the buffer is empty, (count-screen-lines) is
-              ;; zero.  But, even in that case, we need one text line
-              ;; for cursor.
-              (if (= (point-min) (point-max))
-                  1 0)
-              ;; For non-minibuffers, count the mode-line, if any
-              (if (and (not (window-minibuffer-p window))
-                       mode-line-format)
-                  1 0)
-              ;; Count the header-line, if any
-              (if header-line-format 1 0))))
-        (delta
-         ;; Calculate how much the window height has to change to show
-         ;; desired-height lines, constrained by MIN-HEIGHT and MAX-HEIGHT.
-         (- (max (min desired-height max-height)
-                 (or min-height window-min-height))
-            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.
-    (when (with-current-buffer buf
-           (and (eobp) (bolp) (not (bobp))))
-      (set-window-point window (1- (window-point window))))
-
-    (save-selected-window-norecord
-      (select-window window 'norecord)
-
-      ;; Adjust WINDOW to the nominally correct size (which may actually
-      ;; be slightly off because of variable height text, etc).
-      (unless (zerop delta)
-       (enlarge-window delta))
-
-      ;; Check if the last line is surely fully visible.  If not,
-      ;; enlarge the window.
-      (let ((end (with-current-buffer buf
-                  (save-excursion
-                    (goto-char (point-max))
-                    (when (and (bolp) (not (bobp)))
-                      ;; Don't include final newline
-                      (backward-char 1))
-                    (when truncate-lines
-                      ;; If line-wrapping is turned off, test the
-                      ;; beginning of the last line for visibility
-                      ;; instead of the end, as the end of the line
-                      ;; could be invisible by virtue of extending past
-                      ;; the edge of the window.
-                      (forward-line 0))
-                    (point)))))
-       (set-window-vscroll window 0)
-       (while (and (< desired-height max-height)
-                   (= desired-height (window-height window))
-                   (not (pos-visible-in-window-p end window)))
-         (enlarge-window 1)
-         (setq desired-height (1+ desired-height)))))))
+(defun window-safely-shrinkable-p (&optional window)
+  "Return t if WINDOW can be shrunk without shrinking other windows.
+WINDOW defaults to the selected window."
+  (with-selected-window (or window (selected-window))
+    (let ((edges (window-edges)))
+      (or (= (nth 2 edges) (nth 2 (window-edges (previous-window))))
+         (= (nth 0 edges) (nth 0 (window-edges (next-window))))))))
 
 (defun shrink-window-if-larger-than-buffer (&optional window)
   "Shrink height of WINDOW if its buffer doesn't need so many lines.
@@ -1439,29 +1583,35 @@ Return non-nil if the window was shrunk, nil otherwise."
        (error nil)))))
 
 (defun quit-window (&optional kill window)
-  "Bury or kill (with KILL non-nil) the buffer displayed in WINDOW.
-KILL defaults to nil, WINDOW to the selected window.  If WINDOW
-is dedicated or a minibuffer window, delete it and, if it's the
-only window on its frame, delete its frame as well provided there
-are other frames left.  Otherwise, display some other buffer in
-the window."
-  (interactive)
-  (let* ((window (or window (selected-window)))
-        (buffer (window-buffer window)))
-    (if (or (window-minibuffer-p window) (window-dedicated-p window))
-       (if (eq window (frame-root-window (window-frame window)))
-           ;; If this is the only window on its frame, try to delete the
-           ;; frame (`delete-windows-on' knows how to do that).
-           (delete-windows-on buffer (selected-frame))
-         ;; Other windows are left, delete this window.  But don't
-         ;; throw an error if that fails for some reason.
-         (condition-case nil
-             (delete-window window)
-           (error nil)))
-      ;; The window is neither dedicated nor a minibuffer window,
-      ;; display another buffer in it.
-      (with-selected-window window
-       (switch-to-buffer (other-buffer))))
+  "Quit WINDOW and bury its buffer.
+With a prefix argument, kill the buffer instead.  WINDOW defaults
+to the selected window.
+
+If WINDOW is non-nil, dedicated, or a minibuffer window, delete
+it and, if it's alone on its frame, its frame too.  Otherwise, or
+if deleting WINDOW fails in any of the preceding cases, display
+another buffer in WINDOW using `switch-to-buffer'.
+
+Optional argument KILL non-nil means kill WINDOW's buffer.
+Otherwise, bury WINDOW's buffer, see `bury-buffer'."
+  (interactive "P")
+  (let ((buffer (window-buffer window)))
+    (if (or window
+           (window-minibuffer-p window)
+           (window-dedicated-p window))
+       ;; WINDOW is either non-nil, a minibuffer window, or dedicated;
+       ;; try to delete it.
+       (let* ((window (or window (selected-window)))
+              (frame (window-frame window)))
+         (if (eq window (frame-root-window frame))
+             ;; WINDOW is alone on its frame.  `delete-windows-on'
+             ;; knows how to handle that case.
+             (delete-windows-on buffer frame)
+           ;; There are other windows on its frame, delete WINDOW.
+           (delete-window window)))
+      ;; Otherwise, switch to another buffer in the selected window.
+      (switch-to-buffer nil))
+
     ;; Deal with the buffer.
     (if kill
        (kill-buffer buffer)
@@ -1469,40 +1619,95 @@ the window."
 
 (defvar recenter-last-op nil
   "Indicates the last recenter operation performed.
-Possible values: `top', `middle', `bottom'.")
+Possible values: `top', `middle', `bottom', integer or float numbers.")
+
+(defcustom recenter-positions '(middle top bottom)
+  "Cycling order for `recenter-top-bottom'.
+A list of elements with possible values `top', `middle', `bottom',
+integer or float numbers that define the cycling order for
+the command `recenter-top-bottom'.
+
+Top and bottom destinations are `scroll-margin' lines the from true
+window top and bottom.  Middle redraws the frame and centers point
+vertically within the window.  Integer number moves current line to
+the specified absolute window-line.  Float number between 0.0 and 1.0
+means the percentage of the screen space from the top.  The default
+cycling order is middle -> top -> bottom."
+  :type '(repeat (choice
+                 (const :tag "Top" top)
+                 (const :tag "Middle" middle)
+                 (const :tag "Bottom" bottom)
+                 (integer :tag "Line number")
+                 (float :tag "Percentage")))
+  :version "23.2"
+  :group 'windows)
 
 (defun recenter-top-bottom (&optional arg)
-  "Move current line to window center, top, and bottom, successively.
-With no prefix argument, the first call redraws the frame and
- centers point vertically within the window.  Successive calls
- scroll the window, placing point on the top, bottom, and middle
- consecutively.  The cycling order is middle -> top -> bottom.
+  "Move current buffer line to the specified window line.
+With no prefix argument, successive calls place point according
+to the cycling order defined by `recenter-positions'.
 
 A prefix argument is handled like `recenter':
  With numeric prefix ARG, move current line to window-line ARG.
- With plain `C-u', move current line to window center.
-
-Top and bottom destinations are actually `scroll-margin' lines
- the from true window top and bottom."
+ With plain `C-u', move current line to window center."
   (interactive "P")
   (cond
-   (arg (recenter arg))                 ; Always respect ARG.
-   ((or (not (eq this-command last-command))
-       (eq recenter-last-op 'bottom))
-    (setq recenter-last-op 'middle)
-    (recenter))
+   (arg (recenter arg))                        ; Always respect ARG.
    (t
+    (setq recenter-last-op
+         (if (eq this-command last-command)
+             (car (or (cdr (member recenter-last-op recenter-positions))
+                      recenter-positions))
+           (car recenter-positions)))
     (let ((this-scroll-margin
           (min (max 0 scroll-margin)
                (truncate (/ (window-body-height) 4.0)))))
       (cond ((eq recenter-last-op 'middle)
-            (setq recenter-last-op 'top)
-            (recenter this-scroll-margin))
+            (recenter))
            ((eq recenter-last-op 'top)
-            (setq recenter-last-op 'bottom)
-            (recenter (- -1 this-scroll-margin))))))))
+            (recenter this-scroll-margin))
+           ((eq recenter-last-op 'bottom)
+            (recenter (- -1 this-scroll-margin)))
+           ((integerp recenter-last-op)
+            (recenter recenter-last-op))
+           ((floatp recenter-last-op)
+            (recenter (round (* recenter-last-op (window-height))))))))))
 
 (define-key global-map [?\C-l] 'recenter-top-bottom)
+
+(defun move-to-window-line-top-bottom (&optional arg)
+  "Position point relative to window.
+
+With a prefix argument ARG, acts like `move-to-window-line'.
+
+With no argument, positions point at center of window.
+Successive calls position point at positions defined
+by `recenter-positions'."
+  (interactive "P")
+  (cond
+   (arg (move-to-window-line arg))     ; Always respect ARG.
+   (t
+    (setq recenter-last-op
+         (if (eq this-command last-command)
+             (car (or (cdr (member recenter-last-op recenter-positions))
+                      recenter-positions))
+           (car recenter-positions)))
+    (let ((this-scroll-margin
+          (min (max 0 scroll-margin)
+               (truncate (/ (window-body-height) 4.0)))))
+      (cond ((eq recenter-last-op 'middle)
+            (call-interactively 'move-to-window-line))
+           ((eq recenter-last-op 'top)
+            (move-to-window-line this-scroll-margin))
+           ((eq recenter-last-op 'bottom)
+            (move-to-window-line (- -1 this-scroll-margin)))
+           ((integerp recenter-last-op)
+            (move-to-window-line recenter-last-op))
+           ((floatp recenter-last-op)
+            (move-to-window-line (round (* recenter-last-op (window-height))))))))))
+
+(define-key global-map [?\M-r] 'move-to-window-line-top-bottom)
+
 \f
 (defvar mouse-autoselect-window-timer nil
   "Timer used by delayed window autoselection.")