]> code.delx.au - gnu-emacs/blobdiff - lisp/follow.el
* cl-generic.el (cl-defmethod): Make docstring dynamic
[gnu-emacs] / lisp / follow.el
index a74862cb5d0dc88cf4a2d81de6b85b1703a73eda..c510e5a848beeed12a9004cc135ebea7c4bd2d26 100644 (file)
@@ -1,8 +1,10 @@
 ;;; follow.el --- synchronize windows showing the same buffer
 ;;; follow.el --- synchronize windows showing the same buffer
-;; Copyright (C) 1995-1997, 1999, 2001-2012 Free Software Foundation, Inc.
 
 
-;; Author: Anders Lindgren <andersl@andersl.com>
-;; Maintainer: FSF (Anders' email bounces, Sep 2005)
+;; Copyright (C) 1995-1997, 1999, 2001-2016 Free Software Foundation,
+;; Inc.
+
+;; Author: Anders Lindgren
+;; Maintainer: emacs-devel@gnu.org
 ;; Created: 1995-05-25
 ;; Keywords: display, window, minor-mode, convenience
 
 ;; Created: 1995-05-25
 ;; Keywords: display, window, minor-mode, convenience
 
@@ -33,7 +35,7 @@
 ;;   This means that whenever one window is moved, all the
 ;;   others will follow.  (Hence the name Follow mode.)
 ;;
 ;;   This means that whenever one window is moved, all the
 ;;   others will follow.  (Hence the name Follow mode.)
 ;;
-;; * Should the point (cursor) end up outside a window, another
+;; * Should point (cursor) end up outside a window, another
 ;;   window displaying that point is selected, if possible.  This
 ;;   makes it possible to walk between windows using normal cursor
 ;;   movement commands.
 ;;   window displaying that point is selected, if possible.  This
 ;;   makes it possible to walk between windows using normal cursor
 ;;   movement commands.
 ;;             this command be added to the global keymap.
 ;;
 ;;     follow-recenter                          C-c . C-l
 ;;             this command be added to the global keymap.
 ;;
 ;;     follow-recenter                          C-c . C-l
-;;             Place the point in the center of the middle window,
+;;             Place point in the center of the middle window,
 ;;             or a specified number of lines from either top or bottom.
 ;;
 ;;     follow-switch-to-buffer                  C-c . b
 ;;             or a specified number of lines from either top or bottom.
 ;;
 ;;     follow-switch-to-buffer                  C-c . b
 ;;; Code:
 
 (require 'easymenu)
 ;;; Code:
 
 (require 'easymenu)
+(eval-when-compile (require 'cl-lib))
 
 ;;; Variables
 
 
 ;;; Variables
 
@@ -309,7 +312,7 @@ are \" Fw\", or simply \"\"."
         (set-default symbol value)))
 
 (defvar follow-cache-command-list
         (set-default symbol value)))
 
 (defvar follow-cache-command-list
-  '(next-line previous-line forward-char backward-char)
+  '(next-line previous-line forward-char backward-char right-char left-char)
   "List of commands that don't require recalculation.
 
 In order to be able to use the cache, a command should not change the
   "List of commands that don't require recalculation.
 
 In order to be able to use the cache, a command should not change the
@@ -344,6 +347,9 @@ Used by `follow-window-size-change'.")
 (defvar follow-windows-start-end-cache nil
   "Cache used by `follow-window-start-end'.")
 
 (defvar follow-windows-start-end-cache nil
   "Cache used by `follow-window-start-end'.")
 
+(defvar follow-fixed-window nil
+  "If non-nil, the current window must not be scrolled.
+This is typically set by explicit scrolling commands.")
 ;;; Debug messages
 
 ;; This inline function must be as small as possible!
 ;;; Debug messages
 
 ;; This inline function must be as small as possible!
@@ -388,21 +394,21 @@ virtual window.  This is accomplished by two main techniques:
   This means that whenever one window is moved, all the
   others will follow.  (Hence the name Follow mode.)
 
   This means that whenever one window is moved, all the
   others will follow.  (Hence the name Follow mode.)
 
-* Should the point (cursor) end up outside a window, another
+* Should point (cursor) end up outside a window, another
   window displaying that point is selected, if possible.  This
   makes it possible to walk between windows using normal cursor
   movement commands.
 
   window displaying that point is selected, if possible.  This
   makes it possible to walk between windows using normal cursor
   movement commands.
 
-Follow mode comes to its prime when used on a large screen and two
-side-by-side windows are used.  The user can, with the help of Follow
-mode, use two full-height windows as though they would have been
-one.  Imagine yourself editing a large function, or section of text,
-and being able to use 144 lines instead of the normal 72... (your
+Follow mode comes to its prime when used on a large screen and two or
+more side-by-side windows are used.  The user can, with the help of
+Follow mode, use these full-height windows as though they were one.
+Imagine yourself editing a large function, or section of text, and
+being able to use 144 or 216 lines instead of the normal 72... (your
 mileage may vary).
 
 To split one large window into two side-by-side windows, the commands
 `\\[split-window-right]' or \
 mileage may vary).
 
 To split one large window into two side-by-side windows, the commands
 `\\[split-window-right]' or \
-`M-x follow-delete-other-windows-and-split' can be used.
+`\\[follow-delete-other-windows-and-split]' can be used.
 
 Only windows displayed in the same frame follow each other.
 
 
 Only windows displayed in the same frame follow each other.
 
@@ -415,7 +421,21 @@ Keys specific to Follow mode:
       (progn
        (add-hook 'compilation-filter-hook 'follow-align-compilation-windows t t)
        (add-hook 'post-command-hook 'follow-post-command-hook t)
       (progn
        (add-hook 'compilation-filter-hook 'follow-align-compilation-windows t t)
        (add-hook 'post-command-hook 'follow-post-command-hook t)
-       (add-hook 'window-size-change-functions 'follow-window-size-change t))
+       (add-hook 'window-size-change-functions 'follow-window-size-change t)
+        (add-hook 'after-change-functions 'follow-after-change nil t)
+        (add-hook 'isearch-update-post-hook 'follow-post-command-hook nil t)
+        (add-hook 'replace-update-post-hook 'follow-post-command-hook nil t)
+        (add-hook 'ispell-update-post-hook 'follow-post-command-hook nil t)
+
+        (setq window-group-start-function 'follow-window-start)
+        (setq window-group-end-function 'follow-window-end)
+        (setq set-window-group-start-function 'follow-set-window-start)
+        (setq recenter-window-group-function 'follow-recenter)
+        (setq pos-visible-in-window-group-p-function
+              'follow-pos-visible-in-window-p)
+        (setq selected-window-group-function 'follow-all-followers)
+        (setq move-to-window-group-line-function 'follow-move-to-window-line))
+
     ;; Remove globally-installed hook functions only if there is no
     ;; other Follow mode buffer.
     (let ((buffers (buffer-list))
     ;; Remove globally-installed hook functions only if there is no
     ;; other Follow mode buffer.
     (let ((buffers (buffer-list))
@@ -426,6 +446,19 @@ Keys specific to Follow mode:
       (unless following
        (remove-hook 'post-command-hook 'follow-post-command-hook)
        (remove-hook 'window-size-change-functions 'follow-window-size-change)))
       (unless following
        (remove-hook 'post-command-hook 'follow-post-command-hook)
        (remove-hook 'window-size-change-functions 'follow-window-size-change)))
+
+    (kill-local-variable 'move-to-window-group-line-function)
+    (kill-local-variable 'selected-window-group-function)
+    (kill-local-variable 'pos-visible-in-window-group-p-function)
+    (kill-local-variable 'recenter-window-group-function)
+    (kill-local-variable 'set-window-group-start-function)
+    (kill-local-variable 'window-group-end-function)
+    (kill-local-variable 'window-group-start-function)
+
+    (remove-hook 'ispell-update-post-hook 'follow-post-command-hook t)
+    (remove-hook 'replace-update-post-hook 'follow-post-command-hook t)
+    (remove-hook 'isearch-update-post-hook 'follow-post-command-hook t)
+    (remove-hook 'after-change-functions 'follow-after-change t)
     (remove-hook 'compilation-filter-hook 'follow-align-compilation-windows t)))
 
 (defun follow-find-file-hook ()
     (remove-hook 'compilation-filter-hook 'follow-align-compilation-windows t)))
 
 (defun follow-find-file-hook ()
@@ -436,6 +469,54 @@ Keys specific to Follow mode:
 
 ;;; Scroll
 
 
 ;;; Scroll
 
+(defun follow-get-scrolled-point (dest windows)
+  "Calculate the correct value for point after a scrolling operation.
+
+DEST is our default position, typically where point was before the scroll.
+If `scroll-preserve-screen-position' is non-nil and active, DEST will be
+in the same screen position as before the scroll.  WINDOWS is the list of
+windows in the follow chain.
+
+This function attempts to duplicate the point placing from
+`window_scroll_line_based' in the Emacs core source window.c.
+
+Return the new position."
+  (if (and scroll-preserve-screen-position
+          (get this-command 'scroll-command))
+      dest
+    (let ((dest-column
+          (save-excursion
+            (goto-char dest)
+            (- (current-column)
+               (progn (vertical-motion 0) (current-column)))))
+         (limit0
+          (with-selected-window (car windows)
+            (save-excursion
+              (goto-char (window-start))
+              (vertical-motion 0)
+              (point))))
+         (limitn
+          (with-selected-window (car (reverse windows))
+            (save-excursion
+              (goto-char (window-end nil t))
+              (if (pos-visible-in-window-p)
+                  (point)              ; i.e. (point-max)
+                (1- (point)))))))
+      (cond
+       ((< dest limit0)
+       (with-selected-window (car windows)
+         (save-excursion
+           (goto-char limit0)
+           (vertical-motion (cons dest-column 0))
+           (point))))
+       ((> dest limitn)
+       (with-selected-window (car (reverse windows))
+         (save-excursion
+           (goto-char limitn)
+           (vertical-motion (cons dest-column 0))
+           (point))))
+       (t dest)))))
+
 ;; `scroll-up' and `-down', but for windows in Follow mode.
 ;;
 ;; Almost like the real thing, except when the cursor ends up outside
 ;; `scroll-up' and `-down', but for windows in Follow mode.
 ;;
 ;; Almost like the real thing, except when the cursor ends up outside
@@ -451,6 +532,81 @@ Keys specific to Follow mode:
 ;; position...  (This would also be corrected if we would have had a
 ;; good redisplay abstraction.)
 
 ;; position...  (This would also be corrected if we would have had a
 ;; good redisplay abstraction.)
 
+(defun follow-scroll-up-arg (arg)
+  "Scroll the text in a follow mode window chain up by ARG lines.
+If ARG is nil, scroll the size of the current window.
+
+This is an internal function for `follow-scroll-up' and
+`follow-scroll-up-window'."
+  (let ((opoint (point))  (owin (selected-window)))
+    (while
+        ;; If we are too near EOB, try scrolling the previous window.
+        (condition-case nil (progn (scroll-up arg) nil)
+          (end-of-buffer
+           (condition-case nil (progn (follow-previous-window) t)
+             (error
+              (select-window owin)
+              (goto-char opoint)
+              (signal 'end-of-buffer nil))))))
+    (unless (and scroll-preserve-screen-position
+                 (get this-command 'scroll-command))
+      (goto-char opoint))
+    (setq follow-fixed-window t)))
+
+(defun follow-scroll-down-arg (arg)
+  "Scroll the text in a follow mode window chain down by ARG lines.
+If ARG is nil, scroll the size of the current window.
+
+This is an internal function for `follow-scroll-down' and
+`follow-scroll-down-window'."
+  (let ((opoint (point)))
+    (scroll-down arg)
+    (unless (and scroll-preserve-screen-position
+                 (get this-command 'scroll-command))
+      (goto-char opoint))
+    (setq follow-fixed-window t)))
+
+;;;###autoload
+(defun follow-scroll-up-window (&optional arg)
+  "Scroll text in a Follow mode window up by that window's size.
+The other windows in the window chain will scroll synchronously.
+
+If called with no ARG, the `next-screen-context-lines' last lines of
+the window will be visible after the scroll.
+
+If called with an argument, scroll ARG lines up.
+Negative ARG means scroll downward.
+
+Works like `scroll-up' when not in Follow mode."
+  (interactive "P")
+  (cond ((not follow-mode)
+        (scroll-up arg))
+       ((eq arg '-)
+        (follow-scroll-down-window))
+       (t (follow-scroll-up-arg arg))))
+(put 'follow-scroll-up-window 'scroll-command t)
+
+;;;###autoload
+(defun follow-scroll-down-window (&optional arg)
+  "Scroll text in a Follow mode window down by that window's size.
+The other windows in the window chain will scroll synchronously.
+
+If called with no ARG, the `next-screen-context-lines' top lines of
+the window in the chain will be visible after the scroll.
+
+If called with an argument, scroll ARG lines down.
+Negative ARG means scroll upward.
+
+Works like `scroll-down' when not in Follow mode."
+  (interactive "P")
+  (cond ((not follow-mode)
+        (scroll-down arg))
+       ((eq arg '-)
+        (follow-scroll-up-window))
+       (t (follow-scroll-down-arg arg))))
+(put 'follow-scroll-down-window 'scroll-command t)
+
+;;;###autoload
 (defun follow-scroll-up (&optional arg)
   "Scroll text in a Follow mode window chain up.
 
 (defun follow-scroll-up (&optional arg)
   "Scroll text in a Follow mode window chain up.
 
@@ -464,10 +620,8 @@ Works like `scroll-up' when not in Follow mode."
   (interactive "P")
   (cond ((not follow-mode)
         (scroll-up arg))
   (interactive "P")
   (cond ((not follow-mode)
         (scroll-up arg))
-       (arg
-        (save-excursion (scroll-up arg))
-        (setq follow-internal-force-redisplay t))
-       (t
+       (arg (follow-scroll-up-arg arg))
+        (t
         (let* ((windows (follow-all-followers))
                (end (window-end (car (reverse windows)))))
           (if (eq end (point-max))
         (let* ((windows (follow-all-followers))
                (end (window-end (car (reverse windows)))))
           (if (eq end (point-max))
@@ -478,8 +632,9 @@ Works like `scroll-up' when not in Follow mode."
                 (goto-char end))
             (vertical-motion (- next-screen-context-lines))
             (set-window-start (car windows) (point)))))))
                 (goto-char end))
             (vertical-motion (- next-screen-context-lines))
             (set-window-start (car windows) (point)))))))
+(put 'follow-scroll-up 'scroll-command t)
 
 
-
+;;;###autoload
 (defun follow-scroll-down (&optional arg)
   "Scroll text in a Follow mode window chain down.
 
 (defun follow-scroll-down (&optional arg)
   "Scroll text in a Follow mode window chain down.
 
@@ -489,13 +644,12 @@ the top window in the chain will be visible in the bottom window.
 If called with an argument, scroll ARG lines down.
 Negative ARG means scroll upward.
 
 If called with an argument, scroll ARG lines down.
 Negative ARG means scroll upward.
 
-Works like `scroll-up' when not in Follow mode."
+Works like `scroll-down' when not in Follow mode."
   (interactive "P")
   (cond ((not follow-mode)
   (interactive "P")
   (cond ((not follow-mode)
-        (scroll-up arg))
-       (arg
-        (save-excursion (scroll-down arg)))
-       (t
+        (scroll-down arg))
+       (arg (follow-scroll-down-arg arg))
+        (t
         (let* ((windows (follow-all-followers))
                (win (car (reverse windows)))
                (start (window-start (car windows))))
         (let* ((windows (follow-all-followers))
                (win (car (reverse windows)))
                (start (window-start (car windows))))
@@ -510,11 +664,12 @@ Works like `scroll-up' when not in Follow mode."
             (goto-char start)
             (vertical-motion (- next-screen-context-lines 1))
             (setq follow-internal-force-redisplay t))))))
             (goto-char start)
             (vertical-motion (- next-screen-context-lines 1))
             (setq follow-internal-force-redisplay t))))))
+(put 'follow-scroll-down 'scroll-command t)
 
 (declare-function comint-adjust-point "comint" (window))
 (defvar comint-scroll-show-maximum-output)
 
 
 (declare-function comint-adjust-point "comint" (window))
 (defvar comint-scroll-show-maximum-output)
 
-(defun follow-comint-scroll-to-bottom (&optional window)
+(defun follow-comint-scroll-to-bottom (&optional _window)
   "Scroll the bottom-most window in the current Follow chain.
 This is to be called by `comint-postoutput-scroll-to-bottom'."
   (let* ((buffer (current-buffer))
   "Scroll the bottom-most window in the current Follow chain.
 This is to be called by `comint-postoutput-scroll-to-bottom'."
   (let* ((buffer (current-buffer))
@@ -530,7 +685,7 @@ This is to be called by `comint-postoutput-scroll-to-bottom'."
        (select-window win)
        (goto-char pos)
        (setq follow-windows-start-end-cache nil)
        (select-window win)
        (goto-char pos)
        (setq follow-windows-start-end-cache nil)
-       (follow-adjust-window win pos)
+       (follow-adjust-window win)
        (unless is-selected
          (select-window selected)
          (set-buffer buffer))))))
        (unless is-selected
          (select-window selected)
          (set-buffer buffer))))))
@@ -571,7 +726,7 @@ selected if the original window is the first one in the frame."
   (interactive "P")
   (let ((other (or (and (null arg)
                        (not (eq (selected-window)
   (interactive "P")
   (let ((other (or (and (null arg)
                        (not (eq (selected-window)
-                                (frame-first-window (selected-frame)))))
+                                (frame-first-window))))
                   (and arg
                        (< (prefix-numeric-value arg) 0))))
        (start (window-start)))
                   (and arg
                        (< (prefix-numeric-value arg) 0))))
        (start (window-start)))
@@ -742,12 +897,9 @@ contains only windows in the same frame as WIN.  If WIN is nil,
 it defaults to the selected window."
   (unless (window-live-p win)
     (setq win (selected-window)))
 it defaults to the selected window."
   (unless (window-live-p win)
     (setq win (selected-window)))
-  (let ((buffer (window-buffer win))
-       windows)
-    (dolist (w (window-list (window-frame win) 'no-minibuf win))
-      (if (eq (window-buffer w) buffer)
-         (push w windows)))
-    (sort windows 'follow--window-sorter)))
+  (let ((windows (get-buffer-window-list
+                  (window-buffer win) 'no-minibuf (window-frame win))))
+    (sort windows #'follow--window-sorter)))
 
 (defun follow-split-followers (windows &optional win)
   "Split WINDOWS into two sets: predecessors and successors.
 
 (defun follow-split-followers (windows &optional win)
   "Split WINDOWS into two sets: predecessors and successors.
@@ -766,15 +918,16 @@ from the selected window."
 Return (END-POS END-OF-BUFFER).
 
 Actually, the position returned is the start of the line after
 Return (END-POS END-OF-BUFFER).
 
 Actually, the position returned is the start of the line after
-the last fully-visible line in WIN.  If WIN is nil, the selected
-window is used."
+the last fully-visible line in WIN.  END-OF-BUFFER is t when EOB
+is fully-visible in WIN.  If WIN is nil, the selected window is
+used."
   (let* ((win (or win (selected-window)))
         (edges (window-inside-pixel-edges win))
         (ht (- (nth 3 edges) (nth 1 edges)))
         (last-line-pos (posn-point (posn-at-x-y 0 (1- ht) win))))
     (if (pos-visible-in-window-p last-line-pos win)
        (let ((end (window-end win t)))
   (let* ((win (or win (selected-window)))
         (edges (window-inside-pixel-edges win))
         (ht (- (nth 3 edges) (nth 1 edges)))
         (last-line-pos (posn-point (posn-at-x-y 0 (1- ht) win))))
     (if (pos-visible-in-window-p last-line-pos win)
        (let ((end (window-end win t)))
-         (list end (= end (point-max))))
+         (list end (pos-visible-in-window-p (point-max) win)))
       (list last-line-pos nil))))
 
 (defun follow-calc-win-start (windows pos win)
       (list last-line-pos nil))))
 
 (defun follow-calc-win-start (windows pos win)
@@ -815,10 +968,10 @@ Note that this handles the case when the cache has been set to nil."
     (let ((orig-win (selected-window))
          win-start-end)
       (dolist (w windows)
     (let ((orig-win (selected-window))
          win-start-end)
       (dolist (w windows)
-       (select-window w)
+       (select-window w 'norecord)
        (push (cons w (cons (window-start) (follow-calc-win-end)))
              win-start-end))
        (push (cons w (cons (window-start) (follow-calc-win-end)))
              win-start-end))
-      (select-window orig-win)
+      (select-window orig-win 'norecord)
       (setq follow-windows-start-end-cache (nreverse win-start-end)))))
 
 (defsubst follow-pos-visible (pos win win-start-end)
       (setq follow-windows-start-end-cache (nreverse win-start-end)))))
 
 (defsubst follow-pos-visible (pos win win-start-end)
@@ -845,7 +998,7 @@ returned by `follow-windows-start-end'."
       (setq win-start-end (cdr win-start-end)))
     result))
 
       (setq win-start-end (cdr win-start-end)))
     result))
 
-;; Check if the point is visible in all windows. (So that
+;; Check if point is visible in all windows. (So that
 ;; no one will be recentered.)
 
 (defun follow-point-visible-all-windows-p (win-start-end)
 ;; no one will be recentered.)
 
 (defun follow-point-visible-all-windows-p (win-start-end)
@@ -864,7 +1017,7 @@ returned by `follow-windows-start-end'."
 ;; will lead to a redisplay of the screen later on.
 ;;
 ;; This is used with the first window in a follow chain.  The reason
 ;; will lead to a redisplay of the screen later on.
 ;;
 ;; This is used with the first window in a follow chain.  The reason
-;; is that we want to detect that the point is outside the window.
+;; is that we want to detect that point is outside the window.
 ;; (Without the update, the start of the window will move as the
 ;; user presses BackSpace, and the other window redisplay routines
 ;; will move the start of the window in the wrong direction.)
 ;; (Without the update, the start of the window will move as the
 ;; user presses BackSpace, and the other window redisplay routines
 ;; will move the start of the window in the wrong direction.)
@@ -882,22 +1035,21 @@ returned by `follow-windows-start-end'."
 (defun follow-select-if-visible (dest win-start-end)
   "Select and return a window, if DEST is visible in it.
 Return the selected window."
 (defun follow-select-if-visible (dest win-start-end)
   "Select and return a window, if DEST is visible in it.
 Return the selected window."
-  (let (win win-end wse)
+  (let (win wse)
     (while (and (not win) win-start-end)
       ;; Don't select a window that was just moved. This makes it
       ;; possible to later select the last window after a
       ;; `end-of-buffer' command.
       (setq wse (car win-start-end))
       (when (follow-pos-visible dest (car wse) win-start-end)
     (while (and (not win) win-start-end)
       ;; Don't select a window that was just moved. This makes it
       ;; possible to later select the last window after a
       ;; `end-of-buffer' command.
       (setq wse (car win-start-end))
       (when (follow-pos-visible dest (car wse) win-start-end)
-       (setq win (car wse)
-             win-end (nth 2 wse))
+       (setq win (car wse))
        (select-window win))
       (setq win-start-end (cdr win-start-end)))
     win))
 
 ;; Lets select a window showing the end. Make sure we only select it if
 ;; it wasn't just moved here. (I.e. M-> shall not unconditionally place
        (select-window win))
       (setq win-start-end (cdr win-start-end)))
     win))
 
 ;; Lets select a window showing the end. Make sure we only select it if
 ;; it wasn't just moved here. (I.e. M-> shall not unconditionally place
-;; the point in the selected window.)
+;; point in the selected window.)
 ;;
 ;; (Compatibility kludge: in Emacs `window-end' is equal to `point-max';
 ;; in XEmacs, it is equal to `point-max + 1'. Should I really bother
 ;;
 ;; (Compatibility kludge: in Emacs `window-end' is equal to `point-max';
 ;; in XEmacs, it is equal to `point-max + 1'. Should I really bother
@@ -923,10 +1075,10 @@ Return the selected window."
     win))
 
 
     win))
 
 
-;; Select a window that will display the point if the windows would
+;; Select a window that will display point if the windows would
 ;; be redisplayed with the first window fixed. This is useful for
 ;; example when the user has pressed return at the bottom of a window
 ;; be redisplayed with the first window fixed. This is useful for
 ;; example when the user has pressed return at the bottom of a window
-;; as the point is not visible in any window.
+;; as point is not visible in any window.
 
 (defun follow-select-if-visible-from-first (dest windows)
   "Try to select one of WINDOWS without repositioning the topmost window.
 
 (defun follow-select-if-visible-from-first (dest windows)
   "Try to select one of WINDOWS without repositioning the topmost window.
@@ -966,9 +1118,13 @@ Otherwise, return nil."
 ;; is nil.  Start every window directly after the end of the previous
 ;; window, to make sure long lines are displayed correctly.
 
 ;; is nil.  Start every window directly after the end of the previous
 ;; window, to make sure long lines are displayed correctly.
 
+(defvar follow-start-end-invalid t
+  "When non-nil, indicates `follow-windows-start-end-cache' is invalid.")
+(make-variable-buffer-local 'follow-start-end-invalid)
+
 (defun follow-redisplay (&optional windows win preserve-win)
   "Reposition the WINDOWS around WIN.
 (defun follow-redisplay (&optional windows win preserve-win)
   "Reposition the WINDOWS around WIN.
-Should the point be too close to the roof we redisplay everything
+Should point be too close to the roof we redisplay everything
 from the top.  WINDOWS should contain a list of windows to
 redisplay; it is assumed that WIN is a member of the list.
 Should WINDOWS be nil, the windows displaying the
 from the top.  WINDOWS should contain a list of windows to
 redisplay; it is assumed that WIN is a member of the list.
 Should WINDOWS be nil, the windows displaying the
@@ -998,7 +1154,8 @@ repositioning the other windows."
     (dolist (w windows)
       (unless (and preserve-win (eq w win))
        (set-window-start w start))
     (dolist (w windows)
       (unless (and preserve-win (eq w win))
        (set-window-start w start))
-      (setq start (car (follow-calc-win-end w))))))
+      (setq start (car (follow-calc-win-end w))))
+    (setq follow-start-end-invalid nil)))
 
 (defun follow-estimate-first-window-start (windows win start)
   "Estimate the position of the first window.
 
 (defun follow-estimate-first-window-start (windows win start)
   "Estimate the position of the first window.
@@ -1009,7 +1166,7 @@ should be a member of WINDOWS, starts at position START."
       (goto-char start)
       (vertical-motion 0 win)
       (dolist (w windows-before)
       (goto-char start)
       (vertical-motion 0 win)
       (dolist (w windows-before)
-       (vertical-motion (- (window-text-height w)) w))
+       (vertical-motion (- (window-text-height w)) w))
       (point))))
 
 
       (point))))
 
 
@@ -1082,7 +1239,7 @@ should be a member of WINDOWS, starts at position START."
 This is done by reading and rewriting the start position of
 non-first windows in Follow mode."
   (let* ((orig-buffer (current-buffer))
 This is done by reading and rewriting the start position of
 non-first windows in Follow mode."
   (let* ((orig-buffer (current-buffer))
-        (top (frame-first-window (selected-frame)))
+        (top (frame-first-window))
         (win top)
         who) ; list of (buffer . frame)
     ;; If the only window in the frame is a minibuffer
         (win top)
         who) ; list of (buffer . frame)
     ;; If the only window in the frame is a minibuffer
@@ -1123,15 +1280,22 @@ non-first windows in Follow mode."
       (with-current-buffer (window-buffer win)
        (unless (and (symbolp this-command)
                     (get this-command 'follow-mode-use-cache))
       (with-current-buffer (window-buffer win)
        (unless (and (symbolp this-command)
                     (get this-command 'follow-mode-use-cache))
-         (setq follow-windows-start-end-cache nil)))
-      (follow-adjust-window win (point)))))
+         (setq follow-windows-start-end-cache nil))
+        (follow-adjust-window win)))))
 
 
-(defun follow-adjust-window (win dest)
+(defun follow-adjust-window (win)
   ;; Adjust the window WIN and its followers.
   ;; Adjust the window WIN and its followers.
-  (with-current-buffer (window-buffer win)
-    (when (and follow-mode
-              (not (window-minibuffer-p win)))
-      (let* ((windows (follow-all-followers win))
+  (cl-assert (eq (window-buffer win) (current-buffer)))
+  (when (and follow-mode
+             (not (window-minibuffer-p win)))
+    (let ((windows (follow-all-followers win)))
+      ;; If we've explicitly scrolled, align the windows first.
+      (when follow-fixed-window
+       (follow-debug-message "fixed")
+       (follow-redisplay windows win)
+       (goto-char (follow-get-scrolled-point (point) windows))
+       (setq follow-fixed-window nil))
+      (let* ((dest (point))
             (win-start-end (progn
                              (follow-update-window-start (car windows))
                              (follow-windows-start-end windows)))
             (win-start-end (progn
                              (follow-update-window-start (car windows))
                              (follow-windows-start-end windows)))
@@ -1213,7 +1377,7 @@ non-first windows in Follow mode."
              (setq visible nil aligned nil))))
 
          ;; If a new window was selected, make sure that the old is
              (setq visible nil aligned nil))))
 
          ;; If a new window was selected, make sure that the old is
-         ;; not scrolled when the point is outside the window.
+         ;; not scrolled when point is outside the window.
          (unless (eq win (selected-window))
            (let ((p (window-point win)))
              (set-window-start win (window-start win) nil)
          (unless (eq win (selected-window))
            (let ((p (window-point win)))
              (set-window-start win (window-start win) nil)
@@ -1246,7 +1410,7 @@ non-first windows in Follow mode."
                            selected-window-up-to-date)
          (setq win-start-end (follow-windows-start-end windows)
                follow-windows-start-end-cache nil)
                            selected-window-up-to-date)
          (setq win-start-end (follow-windows-start-end windows)
                follow-windows-start-end-cache nil)
-         ;; The point can ends up in another window when DEST is at
+         ;; Point can end up in another window when DEST is at
          ;; the beginning of the buffer and the selected window is
          ;; not the first.  It can also happen when long lines are
          ;; used and there is a big difference between the width of
          ;; the beginning of the buffer and the selected window is
          ;; not the first.  It can also happen when long lines are
          ;; used and there is a big difference between the width of
@@ -1261,18 +1425,18 @@ non-first windows in Follow mode."
        ;; multiple windows.
        (when (region-active-p)
          (follow-maximize-region
        ;; multiple windows.
        (when (region-active-p)
          (follow-maximize-region
-          (selected-window) windows win-start-end)))
+          (selected-window) windows win-start-end))))
 
 
-      ;; Whether or not the buffer was in follow mode, update windows
-      ;; displaying the tail so that Emacs won't recenter them.
-      (follow-avoid-tail-recenter))))
+    ;; Whether or not the buffer was in follow mode, update windows
+    ;; displaying the tail so that Emacs won't recenter them.
+    (follow-avoid-tail-recenter)))
 
 ;;; The region
 
 ;; Tries to make the highlighted area representing the region look
 ;; good when spanning several windows.
 ;;
 
 ;;; The region
 
 ;; Tries to make the highlighted area representing the region look
 ;; good when spanning several windows.
 ;;
-;; Not perfect, as the point can't be placed at window end, only at
+;; Not perfect, as point can't be placed at window end, only at
 ;; end-1.  This will highlight a little bit in windows above
 ;; the current.
 
 ;; end-1.  This will highlight a little bit in windows above
 ;; the current.
 
@@ -1297,6 +1461,12 @@ non-first windows in Follow mode."
 ;; This handles the case where the user drags the scroll bar of a
 ;; non-selected window whose buffer is in Follow mode.
 
 ;; This handles the case where the user drags the scroll bar of a
 ;; non-selected window whose buffer is in Follow mode.
 
+(declare-function scroll-bar-toolkit-scroll "scroll-bar" (event))
+(declare-function scroll-bar-drag "scroll-bar" (event))
+(declare-function scroll-bar-scroll-up "scroll-bar" (event))
+(declare-function scroll-bar-scroll-down "scroll-bar" (event))
+(declare-function mwheel-scroll "mwheel" (event))
+
 (defun follow-scroll-bar-toolkit-scroll (event)
   (interactive "e")
   (scroll-bar-toolkit-scroll event)
 (defun follow-scroll-bar-toolkit-scroll (event)
   (interactive "e")
   (scroll-bar-toolkit-scroll event)
@@ -1354,36 +1524,173 @@ non-first windows in Follow mode."
   "Redraw all windows in FRAME, when in Follow mode."
   ;; Below, we call `post-command-hook'.  Avoid an infloop.
   (unless follow-inside-post-command-hook
   "Redraw all windows in FRAME, when in Follow mode."
   ;; Below, we call `post-command-hook'.  Avoid an infloop.
   (unless follow-inside-post-command-hook
-    (let ((buffers '())
-         (orig-window (selected-window))
-         (orig-buffer (current-buffer))
-         (orig-frame (selected-frame))
-         windows
-         buf)
-      (select-frame frame)
-      (unwind-protect
-         (walk-windows
-          (lambda (win)
-            (setq buf (window-buffer win))
-            (unless (memq buf buffers)
-              (set-buffer buf)
-              (when follow-mode
-                (setq windows (follow-all-followers win))
-                (if (not (memq orig-window windows))
-                    (follow-redisplay windows win)
-                  ;; Make sure we're redrawing around the selected
-                  ;; window.
-                  (select-window orig-window)
-                  (follow-post-command-hook)
-                  (setq orig-window (selected-window)))
-                (setq buffers (cons buf buffers)))))
-          'no-minibuf)
-       (select-frame orig-frame)
-       (set-buffer orig-buffer)
-       (select-window orig-window)))))
+    (save-current-buffer
+      (let ((orig-frame (selected-frame)))
+        (select-frame frame)
+        (let ((picked-window (selected-window))   ; Note: May change below.
+              (seen-buffers '()))
+          (unwind-protect
+              (walk-windows
+               (lambda (win)
+                 (let ((buf (window-buffer win)))
+                   (unless (memq buf seen-buffers)
+                     (set-buffer buf)
+                     (when follow-mode
+                       (let ((windows (follow-all-followers win)))
+                         (if (not (memq picked-window windows))
+                             (follow-redisplay windows win)
+                           ;; Make sure we're redrawing around the selected
+                           ;; window.
+                           (select-window picked-window 'norecord)
+                           (follow-post-command-hook)
+                           (setq picked-window (selected-window))))
+                       (push buf seen-buffers)))))
+               'no-minibuf)
+            (select-window picked-window 'norecord)))
+        (select-frame orig-frame)))))
 
 (add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)
 
 
 (add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)
 
+;;; Low level window start and end.
+
+;; These routines are the Follow Mode versions of the low level
+;; functions described on page "Window Start and End" of the elisp
+;; manual, e.g. `window-group-start'.  The aim is to be able to handle
+;; Follow Mode windows by replacing `window-start' by
+;; `window-group-start', etc.
+
+(defun follow-after-change (_beg _end _old-len)
+  "After change function: set `follow-start-end-invalid'."
+  (setq follow-start-end-invalid t))
+
+(defun follow-window-start (&optional window)
+  "Return position at which display currently starts in the
+Follow Mode group of windows which includes WINDOW.
+
+WINDOW must be a live window and defaults to the selected one.
+This is updated by redisplay or by calling
+`follow-set-window-start'."
+  (let ((windows (follow-all-followers window)))
+    (window-start (car windows))))
+
+(defun follow-window-end (&optional window update)
+  "Return position at which display currently ends in the Follow
+  Mode group of windows which includes WINDOW.
+
+  WINDOW must be a live window and defaults to the selected one.
+  This is updated by redisplay, when it runs to completion.
+  Simply changing the buffer text or setting `window-start' does
+  not update this value.
+
+  Return nil if there is no recorded value.  (This can happen if
+  the last redisplay of WINDOW was preempted, and did not
+  finish.)  If UPDATE is non-nil, compute the up-to-date position
+  if it isn't already recorded."
+  (let* ((windows (follow-all-followers window))
+         (last (car (last windows))))
+    (when (and update follow-start-end-invalid)
+      (follow-redisplay windows (car windows)))
+    (window-end last update)))
+
+(defun follow-set-window-start (window pos &optional noforce)
+  "Make display in the Follow Mode group of windows which includes
+WINDOW start at position POS in WINDOW's buffer.
+
+WINDOW must be a live window and defaults to the selected one.  Return
+POS.  Optional third arg NOFORCE non-nil inhibits next redisplay from
+overriding motion of point in order to display at this exact start."
+  (let ((windows (follow-all-followers window)))
+    (setq follow-start-end-invalid t)
+    (set-window-start (car windows) pos noforce)))
+
+(defun follow-pos-visible-in-window-p (&optional pos window partially)
+  "Return non-nil if position POS is currently on the frame in one of
+  the windows in the Follow Mode group which includes WINDOW.
+
+WINDOW must be a live window and defaults to the selected one.
+
+Return nil if that position is scrolled vertically out of view.  If a
+character is only partially visible, nil is returned, unless the
+optional argument PARTIALLY is non-nil.  If POS is only out of view
+because of horizontal scrolling, return non-nil.  If POS is t, it
+specifies the position of the last visible glyph in WINDOW.  POS
+defaults to point in WINDOW; WINDOW defaults to the selected window.
+
+If POS is visible, return t if PARTIALLY is nil; if PARTIALLY is non-nil,
+the return value is a list of 2 or 6 elements (X Y [RTOP RBOT ROWH VPOS]),
+where X and Y are the pixel coordinates relative to the top left corner
+of the actual window containing it.  The remaining elements are
+omitted if the character after POS is fully visible; otherwise, RTOP
+and RBOT are the number of pixels off-window at the top and bottom of
+the screen line (\"row\") containing POS, ROWH is the visible height
+of that row, and VPOS is the row number \(zero-based)."
+  (let* ((windows (follow-all-followers window))
+         (last (car (last windows))))
+    (when follow-start-end-invalid
+      (follow-redisplay windows (car windows)))
+    (let* ((cache (follow-windows-start-end windows))
+           (last-elt (car (last cache)))
+           our-pos pertinent-elt)
+      (setq pertinent-elt
+            (if (eq pos t)
+                last-elt
+              (setq our-pos (or pos (point)))
+              (catch 'element
+                (while cache
+                  (when (< our-pos (nth 2 (car cache)))
+                    (throw 'element (car cache)))
+                  (setq cache (cdr cache)))
+                last-elt)))
+      (pos-visible-in-window-p our-pos (car pertinent-elt) partially))))
+
+(defun follow-move-to-window-line (arg)
+  "Position point relative to the Follow mode group containing the selected window.
+ARG nil means position point at center of the window group.
+Else, ARG specifies vertical position within the window group;
+zero means top of the first window in the group, negative means
+  relative to bottom of the last window in the group."
+  (let* ((windows (follow-all-followers))
+         (start-end (follow-windows-start-end windows))
+         (rev-start-end (reverse start-end))
+         (lines 0)
+         middle-window elt count)
+    (select-window
+     (cond
+      ((null arg)
+       (setq rev-start-end (nthcdr (/ (length windows) 2) rev-start-end))
+       (prog1 (car (car rev-start-end))
+         (while (setq rev-start-end (cdr rev-start-end))
+           (setq elt (car rev-start-end)
+                 count (count-screen-lines (cadr elt) (nth 2 elt)
+                                           nil (car elt))
+                 lines (+ lines count)))))
+      ((>= arg 0)
+       (while (and (cdr start-end)
+                   (progn
+                     (setq elt (car start-end)
+                           count (count-screen-lines (cadr elt) (nth 2 elt)
+                                                     nil (car elt)))
+                     (>= arg count)))
+         (setq arg (- arg count)
+               lines (+ lines count)
+               start-end (cdr start-end)))
+       (car (car start-end)))
+      (t                                ; (< arg 0)
+       (while (and (cadr rev-start-end)
+                   (progn
+                     (setq elt (car rev-start-end)
+                           count (count-lines (cadr elt) (nth 2 elt)))
+                     (<= arg (- count))))
+         (setq arg (+ arg count)
+               rev-start-end (cdr rev-start-end)))
+       (prog1 (car (car rev-start-end))
+         (while (setq rev-start-end (cdr rev-start-end))
+           (setq elt (car rev-start-end)
+                 count (count-screen-lines (cadr elt) (nth 2 elt)
+                                           nil (car elt))
+                 lines (+ lines count)))))))
+    (+ lines (move-to-window-line arg))))
+
 ;;; Profile support
 
 ;; The following (non-evaluated) section can be used to
 ;;; Profile support
 
 ;; The following (non-evaluated) section can be used to