Subject: Re: bug#13399: 24.3.50; Word-wrap can't wrap at zero-width space U-200B

From: martin rudalics <[email-masked]>
Date: Sat, 02 Feb 2013 17:48:42 +0100

> But why would we need that?  Most everything we need to know about
 > display is already tracked by the display iterator, so available even
 > without generating glyphs, and that's what the move_it_* functions do.
 > These function do their job by traversing only small portions of the
 > buffer, just large enough for the job at hand to be done.

I rewrote `fit-window-to-buffer' and `fit-frame-to-buffer' using the
display iterator.  Please have a look at the attached patch.

Thanks, martin
=== modified file 'lisp/window.el'
--- lisp/window.el	2013-01-02 16:13:04 +0000
+++ lisp/window.el	2013-02-02 14:58:22 +0000
@@ -6074,211 +6074,428 @@
 			     (eobp)
 			     window))))
 
-;;; Resizing buffers to fit their contents exactly.
+;;; Resizing windows and frames to fit their contents exactly.
+(defcustom fit-window-to-buffer-horizontally nil
+  "Non-nil means `fit-window-to-buffer' can resize windows horizontally.
+If this is nil, `fit-window-to-buffer' never resizes windows
+horizontally.  If this is `only', it can resize windows
+horizontally only.  Any other value means `fit-window-to-buffer'
+can resize windows in both dimensions."
+  :type 'boolean
+  :version "24.4"
+  :group 'help)
+
 (defcustom fit-frame-to-buffer nil
-  "Non-nil means `fit-window-to-buffer' can resize frames.
+  "Non-nil means `fit-frame-to-buffer' can resize frames.
 A frame can be resized if and only if its root window is a live
-window.  The height of the root window is subject to the values
-of `fit-frame-to-buffer-max-height' and `window-min-height'."
+window.  If this is `horizontally', frames can be resized
+horizontally only.  If this is `vertically', frames can be
+resized vertically only.  Any other non-nil value means frames
+can be resized in both dimensions.  See also
+`fit-frame-to-buffer-margins' and `fit-frame-to-buffer-sizes'."
   :type 'boolean
-  :version "24.3"
+  :version "24.4"
   :group 'help)
 
-(defcustom fit-frame-to-buffer-bottom-margin 4
-  "Bottom margin for the command `fit-frame-to-buffer'.
-This is the number of lines that function leaves free at the bottom of
-the display, in order to not obscure any system task bar or panel.
-If you do not have one (or if it is vertical) you might want to
-reduce this.  If it is thicker, you might want to increase this."
-  ;; If you set this too small, fit-frame-to-buffer can shift the
-  ;; frame up to avoid the panel.
-  :type 'integer
-  :version "24.3"
-  :group 'windows)
-
-(defun fit-frame-to-buffer (&optional frame max-height min-height)
+(defcustom fit-frame-to-buffer-margins '(0 0 0 72)
+  "Margins around frame for `fit-frame-to-buffer'.
+This list specifies the numbers of pixels to be left free on the
+left, above, the right, and below a frame that shall be fit to
+its buffer.  The value specified here can be overridden for a
+specific frame by that frame's `fit-frame-to-buffer-margins'
+parameter, if present.
+
+On some window systems the calculation of frame sizes can be
+incorrect.  Increasing the value of the third and/or fourth
+element of this variable can fix that.
+
+See also `fit-frame-to-buffer-sizes'."
+  :version "24.4"
+  :type '(list
+	  (integer :tag "Left" :size 5)
+	  (integer :tag " Above" :size 5)
+	  (integer :tag " Right" :size 5)
+	  (integer :tag " Below" :size 5))
+  :group 'windows)
+
+(defcustom fit-frame-to-buffer-sizes '(nil nil nil nil)
+  "Size boundaries of frame for `fit-frame-to-buffer'.
+This list specifies the total maximum and minimum lines and
+maximum and minimum columns of the root window of any frame that
+shall be fit to its buffer.  If any of these values is non-nil,
+it will override the value supplied by the respective arguments
+of `fit-frame-to-buffer'.
+
+On window systems where the menubar can wrap, fitting a frame to
+its buffer may swallow the last line(s).  Specifying an
+appropriate minimum width value here can avoid such wrapping.
+
+See also `fit-frame-to-buffer-margins'."
+  :version "24.4"
+  :type '(list
+	  (choice
+	   :tag "Maximum Height"
+	   :value nil
+	   :format "%[MaxHeight%] %v  "
+	   (const :tag "None" :format "%t" nil)
+	   (integer :tag "Lines" :size 5))
+	  (choice
+	   :tag "Minimum Height"
+	   :value nil
+	   :format "%[MinHeight%] %v  "
+	   (const :tag "None" :format "%t" nil)
+	   (integer :tag "Lines" :size 5))
+	  (choice
+	   :tag "Maximum Width"
+	   :value nil
+	   :format "%[MaxWidth%] %v  "
+	   (const :tag "None" :format "%t" nil)
+	   (integer :tag "Columns" :size 5))
+	  (choice
+	   :tag "Minimum Width"
+	   :value nil
+	   :format "%[MinWidth%] %v\n"
+	   (const :tag "None" :format "%t" nil)
+	   (integer :tag "Columns" :size 5)))
+  :group 'windows)
+
+(defun window--sanitize-margin (margin left right)
+  "Return MARGIN if it's a number between LEFT and RIGHT."
+  (if (and (numberp margin)
+	   (<= left (- right margin)) (<= margin right))
+      margin
+    0))
+
+(defun fit-frame-to-buffer (&optional frame max-height min-height max-width min-width)
   "Adjust height of FRAME to display its buffer contents exactly.
 FRAME can be any live frame and defaults to the selected one.
+MAX-HEIGHT, MIN-HEIGHT, MAX-WIDTH and MIN-WIDTH specify bounds on
+the new total size of FRAME's root window.
 
-Optional argument MAX-HEIGHT specifies the maximum height of FRAME.
-It defaults to the height of the display below the current
-top line of FRAME, minus `fit-frame-to-buffer-bottom-margin'.
-Optional argument MIN-HEIGHT specifies the minimum height of FRAME.
-The default corresponds to `window-min-height'."
+The option `fit-frame-to-buffer' controls whether this function
+has any effect.  New position and size of FRAME are additionally
+determined by the options `fit-frame-to-buffer-sizes' and
+`fit-frame-to-buffer-margins' or the corresponding parameters of
+FRAME.  This function can fail to fit the buffer's height when
+`word-wrap' is turned on in that buffer."
   (interactive)
   (setq frame (window-normalize-frame frame))
-  (let* ((root (frame-root-window frame))
-	 (frame-min-height
-	  (+ (- (frame-height frame) (window-total-size root))
-	     window-min-height))
-	 (frame-top (frame-parameter frame 'top))
-	 (top (if (consp frame-top)
-		  (funcall (car frame-top) (cadr frame-top))
-		frame-top))
-	 (frame-max-height
-	  (- (/ (- (x-display-pixel-height frame) top)
-		(frame-char-height frame))
-	     fit-frame-to-buffer-bottom-margin))
-	 (compensate 0)
-	 delta)
-    (when (and (window-live-p root) (not (window-size-fixed-p root)))
-      (with-selected-window root
-	(cond
-	 ((not max-height)
-	  (setq max-height frame-max-height))
-	 ((numberp max-height)
-	  (setq max-height (min max-height frame-max-height)))
-	 (t
-	  (error "%s is an invalid maximum height" max-height)))
-	(cond
-	 ((not min-height)
-	  (setq min-height frame-min-height))
-	 ((numberp min-height)
-	  (setq min-height (min min-height frame-min-height)))
-	 (t
-	  (error "%s is an invalid minimum height" min-height)))
-	;; When tool-bar-mode is enabled and we have just created a new
-	;; frame, reserve lines for toolbar resizing.  This is needed
-	;; because for reasons unknown to me Emacs (1) reserves one line
-	;; for the toolbar when making the initial frame and toolbars
-	;; are enabled, and (2) later adds the remaining lines needed.
-	;; Our code runs IN BETWEEN (1) and (2).  YMMV when you're on a
-	;; system that behaves differently.
-	(let ((quit-restore (window-parameter root 'quit-restore))
-	      (lines (tool-bar-lines-needed frame)))
-	  (when (and quit-restore (eq (car quit-restore) 'frame)
-		     (not (zerop lines)))
-	    (setq compensate (1- lines))))
-	(message "%s" compensate)
-	(setq delta
-	      ;; Always count a final newline - we don't do any
-	      ;; post-processing, so let's play safe.
-	      (+ (count-screen-lines nil nil t)
-		 (- (window-body-size))
-		 compensate)))
-      ;; Move away from final newline.
-      (when (and (eobp) (bolp) (not (bobp)))
-	(set-window-point root (line-beginning-position 0)))
-      (set-window-start root (point-min))
-      (set-window-vscroll root 0)
-      (condition-case nil
-	  (set-frame-height
-	   frame
-	   (min (max (+ (frame-height frame) delta)
-		     min-height)
-		max-height))
-	(error (setq delta nil))))
-    delta))
+  (when (and (window-live-p (frame-root-window frame))
+	     fit-frame-to-buffer
+	     (or (not window-size-fixed)
+		 (and (eq window-size-fixed 'height)
+		      (not (eq fit-frame-to-buffer 'vertically)))
+		 (and (eq window-size-fixed 'width)
+		      (not (eq fit-frame-to-buffer 'horizontally)))))
+    (with-selected-window (frame-root-window frame)
+      (let* ((window (frame-root-window frame))
+	     (char-width (frame-char-width))
+	     (char-height (frame-char-height))
+	     (display-width (display-pixel-width frame))
+	     (display-height (display-pixel-height frame))
+	     ;; Sanitize margins.
+	     (margins (or (frame-parameter frame 'fit-frame-to-buffer-margins)
+			  fit-frame-to-buffer-margins))
+	     (left-margin (window--sanitize-margin
+			   (nth 0 margins) 0 display-width))
+	     (top-margin (window--sanitize-margin
+			  (nth 1 margins) 0 display-height))
+	     (right-margin (window--sanitize-margin
+			    (nth 2 margins) left-margin display-width))
+	     (bottom-margin (window--sanitize-margin
+			     (nth 3 margins) top-margin display-height))
+	     ;; The pixel width of FRAME.
+	     (frame-width (frame-pixel-width))
+	     ;; The difference between FRAME's pixel and parameter
+	     ;; widths.
+	     (frame-extra-width
+	      (- frame-width (* (frame-width) char-width)))
+	     ;; The pixel height of FRAME's window.
+	     (window-body-width (* (window-body-width) char-width))
+	     ;; The difference in pixels between total and body width of
+	     ;; FRAME's window.
+	     (window-extra-width
+	      (- (* (window-total-width) char-width) window-body-width))
+	     ;; The difference in pixels between the frame's pixel width
+	     ;; and the window's body width.
+	     (extra-width
+	      (* char-width (- (frame-width) (window-body-width))))
+	     ;; The maximum width we can use for fitting.
+	     (fit-width
+	      (- display-width (- frame-width window-body-width)
+		 left-margin right-margin))
+	     ;; The pixel position of FRAME's left border.  We usually
+	     ;; try to leave this alone.
+	     (left
+	      (let ((left (frame-parameter nil 'left)))
+		(if (consp left)
+		    (funcall (car left) (cadr left))
+		  left)))
+	     ;; The pixel height of FRAME.
+	     (frame-height (frame-pixel-height))
+	     ;; The difference between FRAME's pixel and parameter
+	     ;; heights.
+	     (frame-extra-height
+	      (- frame-height (* (frame-height) char-height)))
+	     ;; When tool-bar-mode is enabled and we just created a new
+	     ;; frame, reserve lines for toolbar resizing.  Needed
+	     ;; because for reasons unknown to me Emacs (1) reserves one
+	     ;; line for the toolbar when making the initial frame and
+	     ;; toolbars are enabled, and (2) later adds the remaining
+	     ;; lines needed.  Our code runs IN BETWEEN (1) and (2).
+	     ;; YMMV when you're on a system that behaves differently.
+	     (toolbar-extra-height
+	      (let ((quit-restore (window-parameter window 'quit-restore))
+		    (lines (tool-bar-lines-needed frame)))
+		(* char-height
+		   (if (and quit-restore (eq (car quit-restore) 'frame)
+			    (not (zerop lines)))
+		       (1- lines)
+		     0))))
+	     ;; The pixel height of FRAME's window.
+	     (window-body-height (* (window-body-height) char-height))
+	     ;; The difference in pixels between total and body height
+	     ;; of FRAME's window.
+	     (window-extra-height
+	      (- (* (window-total-height) char-height) window-body-height))
+	     ;; The difference in pixels between the frame's pixel
+	     ;; height and the window's body height.
+	     (extra-height
+	      (* (- (frame-height) (window-body-height)) char-height))
+	     ;; The maximum height we can use for fitting.
+	     (fit-height
+	      (- display-height (- frame-height window-body-height)
+		 top-margin bottom-margin toolbar-extra-height))
+	     ;; The pixel position of FRAME's top border.  We usually
+	     ;; try to leave this alone.
+	     (top
+	      (let ((top (frame-parameter nil 'top)))
+		(if (consp top)
+		    (funcall (car top) (cadr top))
+		  top)))
+	     ;; Sanitize minimum and maximum sizes.
+	     (sizes (or (frame-parameter frame 'fit-frame-to-buffer-sizes)
+			fit-frame-to-buffer-sizes))
+	     (max-height
+	      (cond
+	       ((numberp (nth 0 sizes))
+		(- (* (nth 0 sizes) char-height) window-extra-height))
+	       ((numberp max-height)
+		(- (* max-height char-height) window-extra-height))))
+	     (min-height
+	      (cond
+	       ((numberp (nth 1 sizes))
+		(- (* (nth 1 sizes) char-height) window-extra-height))
+	       ((numberp min-height)
+		(- (* min-height char-height) window-extra-height))))
+	     (max-width
+	      (cond
+	       ((numberp (nth 2 sizes))
+		(- (* (nth 2 sizes) char-width) window-extra-width))
+	       ((numberp max-width)
+		(- (* max-width char-width) window-extra-width))))
+	     (min-width
+	      (cond
+	       ((numberp (nth 3 sizes))
+		(- (* (nth 3 sizes) char-width) window-extra-width))
+	       ((numberp min-width)
+		(- (* min-width char-width) window-extra-width))))
+	     (value (window-buffer-pixel-size
+		     nil (point-min) (point-max)
+		     display-width display-height))
+	     (width (car value))
+	     (height (cdr value))
+	     remainder)
+	;; Round sizes (hopefully we can drop these as soon as we can
+	;; resize pixelwise).  First add pixels to obtain full last
+	;; lines and columns.
+	(setq remainder (% width char-width))
+	(unless (zerop remainder)
+	  (setq width (+ width (- char-width remainder))))
+	(setq remainder (% height char-height))
+	(setq height (+ height (- char-height remainder)))
+	;; Now make sure that we don't get larger than our rounded
+	;; maximum lines and columns.
+	(when (> width fit-width)
+	  (setq width (- fit-width (% fit-width char-width))))
+	(when (> height fit-height)
+	  (setq height (- fit-height (% fit-height char-height))))
+	;; Don't change height or width when the window's size is fixed
+	;; in either direction.
+	(cond
+	 ((eq window-size-fixed 'height)
+	  (setq height nil))
+	 ((eq window-size-fixed 'width)
+	  (setq height nil)))
+	(when width
+	  ;; Fit to maximum and minimum widths.
+	  (when max-width
+	    (setq width (min width max-width)))
+	  (when min-width
+	    (setq width (max width min-width)))
+	  ;; Add extra width.
+	  (setq width (+ width extra-width))
+	  ;; Preserve right margin.
+	  (let ((right (+ left width frame-extra-width))
+		(max-right (- display-width right-margin)))
+	    (cond
+	     ((> right max-right)
+	      ;; Move FRAME to left.
+	      (setq left (max 0 (- left (- right max-right)))))
+	     ((< left left-margin)
+	      ;; Move frame to right.
+	      (setq left left-margin)))))
+	(when height
+	  ;; Fit to maximum and minimum heights.
+	  (when max-height
+	    (setq height (min height max-height)))
+	  (when min-height
+	    (setq height (max height min-height)))
+	  ;; Add extra height.
+	  (setq height (+ height extra-height))
+	  ;; Preserve bottom and top margins.
+	  (let ((bottom (+ top height frame-extra-height))
+		(max-bottom (- display-height bottom-margin)))
+	    (cond
+	     ((> bottom max-bottom)
+	      ;; Move FRAME to left.
+	      (setq top (max 0 (- top (- bottom max-bottom)))))
+	     ((< top top-margin)
+	      ;; Move frame down.
+	      (setq top top-margin)))))
+	;; Apply changes.
+	(set-frame-position frame left top)
+	(set-frame-size
+	 frame
+	 (if width (/ width char-width) (frame-width))
+	 (if height (/ height char-height) (frame-height)))))))
 
-(defun fit-window-to-buffer (&optional window max-height min-height)
-  "Adjust height of WINDOW to display its buffer's contents exactly.
+(defun fit-window-to-buffer (&optional window max-height min-height max-width min-width)
+  "Adjust size of WINDOW to display its buffer's contents exactly.
 WINDOW must be a live window and defaults to the selected one.
 
-Optional argument MAX-HEIGHT specifies the maximum height of
-WINDOW and defaults to the height of WINDOW's frame.  Optional
-argument MIN-HEIGHT specifies the minimum height of 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.
-
-If WINDOW is a full height window, then if the option
-`fit-frame-to-buffer' is non-nil, this calls the function
-`fit-frame-to-buffer' to adjust the frame height.
-
-Return the number of lines by which WINDOW was enlarged or
-shrunk.  If an error occurs during resizing, return nil but don't
-signal an error.
+If WINDOW is part of a vertical combination, adjust WINDOW's
+height.  The new height is calculated from the number of lines of
+the accessible portion of its buffer.  The optional argument
+MAX-HEIGHT specifies a maximum height and defaults to the height
+of WINDOW's frame.  The optional argument MIN-HEIGHT specifies a
+minimum height 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.
+
+If WINDOW is part of a horizontal combination and the value of
+the option `fit-window-to-buffer-horizontally' is non-nil, adjust
+WINDOW's height.  The new width of WINDOW is calculated from the
+maximum length of its buffer's lines that follow the current
+start position of WINDOW.  The optional argument MAX-WIDTH
+specifies a maximum width and defaults to the width of WINDOW's
+frame.  The optional argument MIN-WIDTH specifies a minimum width
+and defaults to `window-min-width'.  Both MAX-WIDTH and MIN-WIDTH
+are specified in columns and include fringes, margins and
+scrollbars, if any.
+
+If WINDOW is its frame's root window, then if the option
+`fit-frame-to-buffer' is non-nil, call `fit-frame-to-buffer' to
+adjust the frame's size.
 
 Note that even if this function makes WINDOW large enough to show
-_all_ lines of its buffer you might not see the first lines when
-WINDOW was scrolled."
+_all_ parts of its buffer you might not see the first part when
+WINDOW was scrolled.  If WINDOW is resized horizontally, you will
+not see the top of its buffer unless WINDOW starts at its minimum
+accessible position."
   (interactive)
   (setq window (window-normalize-window window t))
-  (cond
-   ((window-size-fixed-p window))
-   ((window-full-height-p window)
-    (when fit-frame-to-buffer
-      (fit-frame-to-buffer (window-frame window))))
-   (t
+  (if (eq window (frame-root-window window))
+      (when fit-frame-to-buffer
+	;; Fit WINDOW's frame to buffer.
+	(fit-frame-to-buffer
+	 (window-frame window) max-height min-height max-width min-width))
     (with-selected-window window
-      (let* ((height (window-total-size))
+      (let* ((frame (window-frame))
+	     (char-height (frame-char-height))
+	     (char-width (frame-char-width))
+	     (display-height (display-pixel-height))
+	     (total-height (window-total-height))
+	     (body-height (window-body-height))
+	     (body-width (window-body-width))
 	     (min-height
-	      ;; Adjust MIN-HEIGHT.
+	      ;; Sanitize MIN-HEIGHT.
 	      (if (numberp min-height)
 		  ;; Can't get smaller than `window-safe-min-height'.
 		  (max min-height window-safe-min-height)
 		;; Preserve header and mode line if present.
 		(window-min-size nil nil t)))
 	     (max-height
-	      ;; Adjust MAX-HEIGHT.
+	      ;; Sanitize MAX-HEIGHT.
 	      (if (numberp max-height)
-		  ;; Can't get larger than height of frame.
-		  (min max-height
-		       (window-total-size (frame-root-window window)))
-		;; Don't delete other windows.
-		(+ height (window-max-delta nil nil window))))
-	     ;; Make `desired-height' the height necessary to show
-	     ;; all of WINDOW's buffer, constrained by MIN-HEIGHT
-	     ;; and MAX-HEIGHT.
-	     (desired-height
-	      (max
-	       (min
-		(+ (count-screen-lines)
-		   ;; 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))
-		max-height)
-	       min-height))
-	     (desired-delta
-	      (- desired-height (window-total-size window)))
-	     (delta
-	      (if (> desired-delta 0)
-		  (min desired-delta
-		       (window-max-delta window nil window))
-		(max desired-delta
-		     (- (window-min-delta window nil window))))))
-	(condition-case nil
-	    (if (zerop delta)
-		;; Return zero if DELTA became zero in the process.
-		0
-	      ;; 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)))
-		;; It's silly to put `point' at the end of the previous
-		;; line and so maybe force horizontal scrolling.
-		(set-window-point window (line-beginning-position 0)))
-	      ;; Call `window-resize' with OVERRIDE argument equal WINDOW.
-	      (window-resize window delta nil 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)
-		;; This loop might in some rare pathological cases raise
-		;; an error - another reason for the `condition-case'.
-		(while (and (< desired-height max-height)
-			    (= desired-height (window-total-size))
-			    (not (pos-visible-in-window-p end)))
-		  (window-resize window 1 nil window)
-		  (setq desired-height (1+ desired-height)))))
-	  (error (setq delta nil)))
-	delta)))))
+		  (min (+ total-height (window-max-delta)) max-height)
+		(+ total-height (window-max-delta))))
+	     height)
+	(cond
+	 ;; If WINDOW is vertically combined, try to resize it
+	 ;; vertically.
+	 ((and (not (eq fit-window-to-buffer-horizontally 'only))
+	       (not (window-size-fixed-p window))
+	       (window-combined-p))
+	  ;; Vertically we always want to fit the entire buffer.
+	  ;; WINDOW'S height can't get larger than its frame's pixel
+	  ;; height.  Its width remains fixed.
+	  (setq height (cdr (window-buffer-pixel-size
+			     nil (point-min) (point-max)
+			     (* body-width char-width)
+			     (frame-pixel-height))))
+	  ;; Round height.
+	  (setq height (+ (/ height char-height)
+			  (if (zerop (% height char-height)) 0 1)))
+	  (unless (= height body-height)
+	    (window-resize-no-error
+	     window
+	     (- (max min-height
+		     (min max-height
+			  (+ total-height (- height body-height))))
+		total-height)
+	     nil window)))
+	 ;; If WINDOW is horizontally combined, try to resize it
+	 ;; horizontally.
+	 ((and fit-window-to-buffer-horizontally
+	       (not (window-size-fixed-p window t))
+	       (window-combined-p nil t))
+	  (let* ((display-width (display-pixel-width))
+		 (total-width (window-total-width))
+		 (min-width
+		  ;; Sanitize MIN-WIDTH.
+		  (if (numberp min-width)
+		      ;; Can't get smaller than `window-safe-min-width'.
+		      (max min-width window-safe-min-width)
+		    ;; Preserve fringes, margines, scrollbars if present.
+		    (window-min-size nil nil t)))
+		 (max-width
+		  ;; Sanitize MAX-WIDTH.
+		  (if (numberp max-width)
+		      (min (+ total-width (window-max-delta nil t)) max-width)
+		    (+ total-width (window-max-delta nil t))))
+		 ;; When fitting vertically, assume that WINDOW's start
+		 ;; position remains unaltered.  WINDOW can't get wider
+		 ;; than its frame's pixel width, its height remains
+		 ;; unaltered.
+		 (width (car (window-buffer-pixel-size
+			      nil (window-start) (point-max)
+			      (frame-pixel-width)
+			      ;; Add one char-height to assure that
+			      ;; we're on the safe side.  This
+			      ;; overshoots when the first line below
+			      ;; the bottom is wider than the window.
+			      (* body-height char-height)))))
+	    (setq width (+ (/ width char-width)
+			   (if (zerop (% width char-width)) 0 1)))
+	    (unless (= width body-width)
+	      (window-resize-no-error
+	       window
+	       (- (max min-width
+		       (min max-width
+			    (+ total-width (- width body-width))))
+		  total-width)
+	       t window)))))))))
 
 (defun window-safely-shrinkable-p (&optional window)
   "Return t if WINDOW can be shrunk without shrinking other windows.

=== modified file 'src/dispextern.h'
--- src/dispextern.h	2013-01-02 16:13:04 +0000
+++ src/dispextern.h	2013-02-02 14:56:54 +0000
@@ -2489,6 +2489,9 @@
      pixel_width with each call to produce_glyphs.  */
   int current_x;
 
+  /* Maximum x pixel position encountered within a display line.  */
+  int max_current_x;
+
   /* Accumulated width of continuation lines.  If > 0, this means we
      are currently in a continuation line.  This is initially zero and
      incremented/reset by display_line, move_it_to etc.  */

=== modified file 'src/xdisp.c'
--- src/xdisp.c	2013-01-05 21:18:01 +0000
+++ src/xdisp.c	2013-02-02 14:56:32 +0000
@@ -2996,7 +2996,7 @@
 
 	  it->current_y = first_y;
 	  it->vpos = 0;
-	  it->current_x = it->hpos = 0;
+	  it->current_x = it->max_current_x = it->hpos = 0;
 	}
     }
 }
@@ -8814,7 +8814,10 @@
 
 	  /* If TO_CHARPOS is reached or ZV, we don't have to do more.  */
 	  if (skip == MOVE_POS_MATCH_OR_ZV)
-	    reached = 5;
+	    {
+	      it->max_current_x = max (it->current_x, it->max_current_x);
+	      reached = 5;
+	    }
 	  else if (skip == MOVE_X_REACHED)
 	    {
 	      /* If TO_X was reached, we want to know whether TO_Y is
@@ -8883,6 +8886,8 @@
 		      skip = move_it_in_display_line_to
 			(it, -1, prev_x, MOVE_TO_X);
 		    }
+
+		  it->max_current_x = max (it->current_x, it->max_current_x);
 		  reached = 6;
 		}
 	    }
@@ -8908,15 +8913,18 @@
       switch (skip)
 	{
 	case MOVE_POS_MATCH_OR_ZV:
+	  it->max_current_x = max (it->current_x, it->max_current_x);
 	  reached = 8;
 	  goto out;
 
 	case MOVE_NEWLINE_OR_CR:
+	  it->max_current_x = max (it->current_x, it->max_current_x);
 	  set_iterator_to_next (it, 1);
 	  it->continuation_lines_width = 0;
 	  break;
 
 	case MOVE_LINE_TRUNCATED:
+	  it->max_current_x = it->last_visible_x;
 	  it->continuation_lines_width = 0;
 	  reseat_at_next_visible_line_start (it, 0);
 	  if ((op & MOVE_TO_POS) != 0
@@ -8928,6 +8936,7 @@
 	  break;
 
 	case MOVE_LINE_CONTINUED:
+	  it->max_current_x = it->last_visible_x;
 	  /* For continued lines ending in a tab, some of the glyphs
 	     associated with the tab are displayed on the current
 	     line.  Since it->current_x does not include these glyphs,
@@ -9326,6 +9335,93 @@
 	  && it->dpvec + it->current.dpvec_index != it->dpend);
 }
 
+DEFUN ("window-buffer-pixel-size", Fwindow_buffer_pixel_size, Swindow_buffer_pixel_size, 0, 5, 0,
+       doc: /* Return size of WINDOW's buffer in pixels.
+WINDOW must be a live window and defaults to the selected one.  The
+return value is a cons of the maximum pixel-width of any line and the
+maximum pixel-height of all lines.
+
+The optional argument X_LIMIT, if non-nil, specifies the maximum
+pixel-width that can be returned.  X_LIMIT nil or omitted, means to use
+the pixel-width of WINDOW's body; use this if you do not intend to
+change the width of WINDOW.  Use the maximum width WINDOW can be
+expanded to if you intend to change WINDOW's width.
+
+The optional argument Y_LIMIT, if non-nil, specifies the maximum
+pixel-height to scan.  Lines starting below Y_LIMIT are not scanned.
+Since calculating the pixel-height of a large buffer can take some time,
+it makes sense to specify this argument if the size of the buffer is
+unknown.  */)
+  (Lisp_Object window, Lisp_Object from, Lisp_Object to, Lisp_Object x_limit, Lisp_Object y_limit)
+{
+  struct window *w = decode_live_window (window);
+  Lisp_Object buf, value;
+  struct buffer *b;
+  struct it it;
+  struct buffer *old_buffer = NULL;
+  ptrdiff_t start, end;
+  struct text_pos startp, endp;
+  void *itdata = NULL;
+  int max_y = -1;
+
+  buf = w->buffer;
+  CHECK_BUFFER (buf);
+  b = XBUFFER (buf);
+
+  if (NILP (from))
+    start = BEGV;
+  else
+    {
+      CHECK_NUMBER_COERCE_MARKER (from);
+      start = min (max (XINT (from), BEGV), ZV);
+    }
+
+  if (NILP (to))
+    end = ZV;
+  else
+    {
+      CHECK_NUMBER_COERCE_MARKER (to);
+      end = max (start, min (XINT (to), ZV));
+    }
+
+  if (b != current_buffer)
+    {
+      old_buffer = current_buffer;
+      set_buffer_internal (b);
+    }
+
+  if (!NILP (y_limit))
+    {
+      CHECK_NUMBER (y_limit);
+      max_y = XINT (y_limit);
+    }
+
+  itdata = bidi_shelve_cache ();
+  SET_TEXT_POS (startp, start, CHAR_TO_BYTE (start));
+  start_display (&it, w, startp);
+
+  if (!NILP (x_limit))
+    {
+      CHECK_NUMBER (x_limit);
+      it.last_visible_x = XINT (x_limit);
+    }
+
+  /* Actually, we never want move_it_to stop at to_x.  But to make sure
+     that move_it_in_display_line_to always moves far enough, we set it
+     to MOST_POSITIVE_FIXNUM and specify MOVE_TO_X.  */
+  move_it_to (&it, end, MOST_POSITIVE_FIXNUM, max_y, -1,
+	      MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
+  last_height = 0;
+  value = Fcons (make_number (it.max_current_x),
+		 make_number (it.current_y));
+/** 		 make_number (line_bottom_y (&it))); **/
+  bidi_unshelve_cache (itdata, 0);
+
+  if (old_buffer)
+    set_buffer_internal (old_buffer);
+
+  return value;
+}
 
 /***********************************************************************
 			       Messages
@@ -28808,6 +28904,7 @@
   defsubr (&Sformat_mode_line);
   defsubr (&Sinvisible_p);
   defsubr (&Scurrent_bidi_paragraph_direction);
+  defsubr (&Swindow_buffer_pixel_size);
 
   DEFSYM (Qmenu_bar_update_hook, "menu-bar-update-hook");
   DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map");