* emacs-lisp/byte-run.el (defmacro): Use same argument parsing as
[bpt/emacs.git] / lisp / follow.el
CommitLineData
e8af40ee 1;;; follow.el --- synchronize windows showing the same buffer
acaf905b 2;; Copyright (C) 1995-1997, 1999, 2001-2012 Free Software Foundation, Inc.
e79016aa 3
c86b6fd2 4;; Author: Anders Lindgren <andersl@andersl.com>
ec8d89ec 5;; Maintainer: FSF (Anders' email bounces, Sep 2005)
c86b6fd2 6;; Created: 1995-05-25
f5f727f8 7;; Keywords: display, window, minor-mode, convenience
e79016aa 8
dcde1278
RS
9;; This file is part of GNU Emacs.
10
eb3fa2cf 11;; GNU Emacs is free software: you can redistribute it and/or modify
e79016aa 12;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
e79016aa 15
dcde1278 16;; GNU Emacs is distributed in the hope that it will be useful,
e79016aa
KH
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
eb3fa2cf 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
e79016aa
KH
23
24;;; Commentary:
25
ff753438 26;; `Follow mode' is a minor mode for Emacs and XEmacs that
e79016aa
KH
27;; combines windows into one tall virtual window.
28;;
29;; The feeling of a "virtual window" has been accomplished by the use
30;; of two major techniques:
31;;
1ece6cdb 32;; * The windows always display adjacent sections of the buffer.
e79016aa 33;; This means that whenever one window is moved, all the
17f6d002 34;; others will follow. (Hence the name Follow mode.)
e79016aa 35;;
cb02be17
KH
36;; * Should the point (cursor) end up outside a window, another
37;; window displaying that point is selected, if possible. This
38;; makes it possible to walk between windows using normal cursor
e79016aa
KH
39;; movement commands.
40;;
ff753438 41;; Follow mode comes to its prime when a large screen and two
08a1dbe6 42;; side-by-side window are used. The user can, with the help of Follow
1ece6cdb
EZ
43;; mode, use two full-height windows as though they are one.
44;; Imagine yourself editing a large function, or section of text,
c8d05b03 45;; and being able to use 144 lines instead of the normal 72... (your
e79016aa
KH
46;; mileage may vary).
47
e79016aa 48;; To test this package, make sure `follow' is loaded, or will be
08a1dbe6 49;; autoloaded when activated (see below). Then do the following:
e79016aa 50;;
ff753438 51;; * Find your favorite file (preferably a long one).
e79016aa 52;;
ff753438
RS
53;; * Resize Emacs so that it will be wide enough for two full size
54;; columns. Delete the other windows and split the window with
55;; the commands `C-x 1 C-x 3'.
e79016aa
KH
56;;
57;; * Give the command:
58;; M-x follow-mode <RETURN>
59;;
60;; * Now the display should look something like (assuming the text "71"
61;; is on line 71):
62;;
63;; +----------+----------+
64;; |1 |73 |
65;; |2 |74 |
66;; |3 |75 |
cb02be17 67;; ... ...
e79016aa
KH
68;; |71 |143 |
69;; |72 |144 |
70;; +----------+----------+
71;;
72;; As you can see, the right-hand window starts at line 73, the line
08a1dbe6 73;; immediately below the end of the left-hand window. As long as
1ece6cdb 74;; `follow-mode' is active, the two windows will follow each other!
e79016aa
KH
75;;
76;; * Play around and enjoy! Scroll one window and watch the other.
08a1dbe6
SM
77;; Jump to the beginning or end. Press `Cursor down' at the last
78;; line of the left-hand window. Enter new lines into the
79;; text. Enter long lines spanning several lines, or several
e79016aa
KH
80;; windows.
81;;
82;; * Should you find `Follow' mode annoying, just type
cb02be17 83;; M-x follow-mode <RETURN>
e79016aa
KH
84;; to turn it off.
85
86
53964682 87;; The command `follow-delete-other-windows-and-split' maximizes the
e79016aa
KH
88;; visible area of the current buffer.
89;;
90;; I recommend adding it, and `follow-mode', to hotkeys in the global
91;; key map. To do so, add the following lines (replacing `[f7]' and
92;; `[f8]' with your favorite keys) to the init file:
93;;
e79016aa 94;; (global-set-key [f8] 'follow-mode)
e79016aa
KH
95;; (global-set-key [f7] 'follow-delete-other-windows-and-split)
96
97
e4920bc9 98;; There exist two system variables that control the appearance of
1ece6cdb
EZ
99;; lines wider than the window containing them. The default is to
100;; truncate long lines whenever a window isn't as wide as the frame.
e79016aa
KH
101;;
102;; To make sure lines are never truncated, please place the following
103;; lines in your init file:
104;;
105;; (setq truncate-lines nil)
106;; (setq truncate-partial-width-windows nil)
107
108
da6062e6 109;; The correct way to configure Follow mode, or any other mode for
1ece6cdb
EZ
110;; that matter, is to create one or more functions that do
111;; whatever you would like to do. These functions are then added to
e79016aa
KH
112;; a hook.
113;;
e79016aa
KH
114;; The keymap `follow-key-map' contains key bindings activated by
115;; `follow-mode'.
116;;
117;; Example:
118;; (add-hook 'follow-mode-hook 'my-follow-mode-hook)
119;;
120;; (defun my-follow-mode-hook ()
121;; (define-key follow-mode-map "\C-ca" 'your-favorite-function)
122;; (define-key follow-mode-map "\C-cb" 'another-function))
123
124
125;; Usage:
126;;
1ece6cdb
EZ
127;; To activate, issue the command "M-x follow-mode"
128;; and press Return. To deactivate, do it again.
e79016aa 129;;
ff753438
RS
130;; The following is a list of commands useful when follow-mode is active.
131;;
e79016aa 132;; follow-scroll-up C-c . C-v
17f6d002 133;; Scroll text in a Follow mode window chain up.
e79016aa
KH
134;;
135;; follow-scroll-down C-c . v
136;; Like `follow-scroll-up', but in the other direction.
137;;
138;; follow-delete-other-windows-and-split C-c . 1
1ece6cdb 139;; Maximize the visible area of the current buffer,
17f6d002 140;; and enter Follow mode. This is a very convenient
fe7a3057 141;; way to start Follow mode, hence we recommend that
1ece6cdb 142;; this command be added to the global keymap.
e79016aa
KH
143;;
144;; follow-recenter C-c . C-l
145;; Place the point in the center of the middle window,
146;; or a specified number of lines from either top or bottom.
147;;
148;; follow-switch-to-buffer C-c . b
149;; Switch buffer in all windows displaying the current buffer
150;; in this frame.
151;;
152;; follow-switch-to-buffer-all C-c . C-b
1ece6cdb 153;; Switch buffer in all windows in the selected frame.
e79016aa
KH
154;;
155;; follow-switch-to-current-buffer-all
156;; Show the current buffer in all windows on the current
157;; frame and turn on `follow-mode'.
158;;
159;; follow-first-window C-c . <
160;; Select the first window in the frame showing the same buffer.
161;;
162;; follow-last-window C-c . >
163;; Select the last window in the frame showing the same buffer.
164;;
165;; follow-next-window C-c . n
166;; Select the next window in the frame showing the same buffer.
167;;
168;; follow-previous-window C-c . p
169;; Select the previous window showing the same buffer.
170
171
172;; Well, it seems ok, but what if I really want to look at two different
173;; positions in the text? Here are two simple methods to use:
174;;
175;; 1) Use multiple frames; `follow' mode only affects windows displayed
5eba16a3 176;; in the same frame. (My apologies to you who can't use frames.)
e79016aa
KH
177;;
178;; 2) Bind `follow-mode' to key so you can turn it off whenever
08a1dbe6 179;; you want to view two locations. Of course, `follow' mode can
e79016aa
KH
180;; be reactivated by hitting the same key again.
181;;
182;; Example from my ~/.emacs:
183;; (global-set-key [f8] 'follow-mode)
184
cb02be17 185;; Implementation:
e79016aa 186;;
782fbf2a
CY
187;; The main method by which Follow mode aligns windows is via the
188;; function `follow-post-command-hook', which is run after each
189;; command. This "fixes up" the alignment of other windows which are
190;; showing the same Follow mode buffer, on the same frame as the
191;; selected window. It does not try to deal with buffers other than
192;; the buffer of the selected frame, or windows on other frames.
e79016aa 193;;
782fbf2a
CY
194;; Comint mode specially calls `follow-comint-scroll-to-bottom' on
195;; Follow mode buffers. This function scrolls the bottom-most window
196;; in a window chain and aligns the other windows accordingly. Follow
197;; mode adds a function to `compilation-filter-hook' to align
198;; compilation buffers.
e79016aa 199
e79016aa
KH
200;;; Code:
201
9271083a
JB
202(require 'easymenu)
203
c93b886f 204;;; Variables
e79016aa 205
4bef9110
SE
206(defgroup follow nil
207 "Synchronize windows showing the same buffer."
f5f727f8
DN
208 :group 'windows
209 :group 'convenience)
4bef9110 210
4bef9110 211(defcustom follow-mode-hook nil
eceb3266 212 "Normal hook run by `follow-mode'."
4bef9110
SE
213 :type 'hook
214 :group 'follow)
e79016aa 215
c93b886f 216;;; Keymap/Menu
e79016aa 217
08a1dbe6 218;; Define keys for the follow-mode minor mode map and replace some
782fbf2a
CY
219;; functions in the global map. All Follow mode special functions can
220;; be found on the `C-c .' prefix key.
08a1dbe6 221;;
782fbf2a
CY
222;; To change the prefix, redefine `follow-mode-prefix' before `follow'
223;; is loaded, or see the section on `follow-mode-hook' above for an
224;; example of how to bind the keys the way you like.
e79016aa 225
4bef9110 226(defcustom follow-mode-prefix "\C-c."
08a1dbe6 227 "Prefix key to use for follow commands in Follow mode.
e79016aa 228The value of this variable is checked as part of loading Follow mode.
4bef9110
SE
229After that, changing the prefix key requires manipulating keymaps."
230 :type 'string
231 :group 'follow)
232
08a1dbe6
SM
233(defvar follow-mode-map
234 (let ((mainmap (make-sparse-keymap))
235 (map (make-sparse-keymap)))
e79016aa
KH
236 (define-key map "\C-v" 'follow-scroll-up)
237 (define-key map "\M-v" 'follow-scroll-down)
238 (define-key map "v" 'follow-scroll-down)
239 (define-key map "1" 'follow-delete-other-windows-and-split)
240 (define-key map "b" 'follow-switch-to-buffer)
241 (define-key map "\C-b" 'follow-switch-to-buffer-all)
242 (define-key map "\C-l" 'follow-recenter)
243 (define-key map "<" 'follow-first-window)
244 (define-key map ">" 'follow-last-window)
245 (define-key map "n" 'follow-next-window)
246 (define-key map "p" 'follow-previous-window)
247
08a1dbe6 248 (define-key mainmap follow-mode-prefix map)
e79016aa 249
17f6d002 250 ;; Replace the standard `end-of-buffer', when in Follow mode. (I
ff753438 251 ;; don't see the point in trying to replace every function that
e79016aa
KH
252 ;; could be enhanced in Follow mode. End-of-buffer is a special
253 ;; case since it is very simple to define and it greatly enhances
254 ;; the look and feel of Follow mode.)
08a1dbe6 255 (define-key mainmap [remap end-of-buffer] 'follow-end-of-buffer)
e79016aa 256
782fbf2a
CY
257 (define-key mainmap [remap scroll-bar-toolkit-scroll] 'follow-scroll-bar-toolkit-scroll)
258 (define-key mainmap [remap scroll-bar-drag] 'follow-scroll-bar-drag)
259 (define-key mainmap [remap scroll-bar-scroll-up] 'follow-scroll-bar-scroll-up)
260 (define-key mainmap [remap scroll-bar-scroll-down] 'follow-scroll-bar-scroll-down)
87233a14 261 (define-key mainmap [remap mwheel-scroll] 'follow-mwheel-scroll)
782fbf2a 262
08a1dbe6
SM
263 mainmap)
264 "Minor mode keymap for Follow mode.")
e79016aa 265
7dcef48d
SM
266;; When the mode is not activated, only one item is visible to activate
267;; the mode.
268(defun follow-menu-filter (menu)
17f6d002 269 (if (bound-and-true-p follow-mode)
7dcef48d 270 menu
17f6d002 271 '(["Follow mode" follow-mode
7dcef48d
SM
272 :style toggle :selected follow-mode])))
273
7dcef48d
SM
274(easy-menu-add-item nil '("Tools")
275 '("Follow"
7dcef48d
SM
276 :filter follow-menu-filter
277 ["Scroll Up" follow-scroll-up follow-mode]
278 ["Scroll Down" follow-scroll-down follow-mode]
279 "--"
280 ["Delete Other Windows and Split" follow-delete-other-windows-and-split follow-mode]
281 "--"
282 ["Switch To Buffer" follow-switch-to-buffer follow-mode]
283 ["Switch To Buffer (all windows)" follow-switch-to-buffer-all follow-mode]
284 "--"
285 ["First Window" follow-first-window follow-mode]
286 ["Last Window" follow-last-window follow-mode]
287 ["Next Window" follow-next-window follow-mode]
288 ["Previous Window" follow-previous-window follow-mode]
289 "--"
290 ["Recenter" follow-recenter follow-mode]
291 "--"
292 ["Follow mode" follow-mode :style toggle :selected follow-mode]))
293
08a1dbe6
SM
294(defcustom follow-mode-line-text " Follow"
295 "Text shown in the mode line when Follow mode is active.
296Defaults to \" Follow\". Examples of other values
297are \" Fw\", or simply \"\"."
298 :type 'string
299 :group 'follow)
300
301(defcustom follow-auto nil
302 "Non-nil activates Follow mode whenever a file is loaded."
303 :type 'boolean
782fbf2a
CY
304 :group 'follow
305 :set (lambda (symbol value)
306 (if value
307 (add-hook 'find-file-hook 'follow-find-file-hook t)
308 (remove-hook 'find-file-hook 'follow-find-file-hook))
309 (set-default symbol value)))
08a1dbe6
SM
310
311(defvar follow-cache-command-list
312 '(next-line previous-line forward-char backward-char)
313 "List of commands that don't require recalculation.
314
315In order to be able to use the cache, a command should not change the
316contents of the buffer, nor should it change selected window or current
317buffer.
318
319The commands in this list are checked at load time.
320
321To mark other commands as suitable for caching, set the symbol
322property `follow-mode-use-cache' to non-nil.")
323
78f3273a
CY
324(defcustom follow-debug nil
325 "If non-nil, emit Follow mode debugging messages."
326 :type 'boolean
327 :group 'follow)
08a1dbe6
SM
328
329;; Internal variables:
330
331(defvar follow-internal-force-redisplay nil
332 "True when Follow mode should redisplay the windows.")
333
08a1dbe6
SM
334(defvar follow-active-menu nil
335 "The menu visible when Follow mode is active.")
336
72b255c7
PE
337(defvar follow-inactive-menu nil
338 "The menu visible when Follow mode is inactive.")
08a1dbe6
SM
339
340(defvar follow-inside-post-command-hook nil
341 "Non-nil when inside Follow modes `post-command-hook'.
342Used by `follow-window-size-change'.")
343
344(defvar follow-windows-start-end-cache nil
345 "Cache used by `follow-window-start-end'.")
346
c93b886f 347;;; Debug messages
08a1dbe6
SM
348
349;; This inline function must be as small as possible!
350;; Maybe we should define a macro that expands to nil if
351;; the variable is not set.
352
353(defsubst follow-debug-message (&rest args)
5eba16a3 354 "Like `message', but only active when `follow-debug' is non-nil."
08a1dbe6
SM
355 (if (and (boundp 'follow-debug) follow-debug)
356 (apply 'message args)))
e79016aa 357
c93b886f 358;;; Cache
f3bfa025 359
08a1dbe6
SM
360(dolist (cmd follow-cache-command-list)
361 (put cmd 'follow-mode-use-cache t))
f3bfa025 362
c93b886f 363;;; The mode
e79016aa
KH
364
365;;;###autoload
366(defun turn-on-follow-mode ()
08a1dbe6 367 "Turn on Follow mode. Please see the function `follow-mode'."
e79016aa
KH
368 (follow-mode 1))
369
370
371;;;###autoload
372(defun turn-off-follow-mode ()
08a1dbe6 373 "Turn off Follow mode. Please see the function `follow-mode'."
e79016aa
KH
374 (follow-mode -1))
375
08a1dbe6 376(put 'follow-mode 'permanent-local t)
e79016aa 377;;;###autoload
08a1dbe6 378(define-minor-mode follow-mode
06e21633
CY
379 "Toggle Follow mode.
380With a prefix argument ARG, enable Follow mode if ARG is
381positive, and disable it otherwise. If called from Lisp, enable
382the mode if ARG is omitted or nil.
e79016aa 383
06e21633
CY
384Follow mode is a minor mode that combines windows into one tall
385virtual window. This is accomplished by two main techniques:
e79016aa 386
cb02be17 387* The windows always displays adjacent sections of the buffer.
e79016aa 388 This means that whenever one window is moved, all the
17f6d002 389 others will follow. (Hence the name Follow mode.)
e79016aa 390
cb02be17
KH
391* Should the point (cursor) end up outside a window, another
392 window displaying that point is selected, if possible. This
393 makes it possible to walk between windows using normal cursor
e79016aa
KH
394 movement commands.
395
396Follow mode comes to its prime when used on a large screen and two
3eaf40f7 397side-by-side windows are used. The user can, with the help of Follow
e79016aa 398mode, use two full-height windows as though they would have been
8cd3480b 399one. Imagine yourself editing a large function, or section of text,
c8d05b03 400and being able to use 144 lines instead of the normal 72... (your
e79016aa
KH
401mileage may vary).
402
403To split one large window into two side-by-side windows, the commands
2d197ffb 404`\\[split-window-right]' or \
e79016aa
KH
405`M-x follow-delete-other-windows-and-split' can be used.
406
8cd3480b 407Only windows displayed in the same frame follow each other.
e79016aa 408
eceb3266 409This command runs the normal hook `follow-mode-hook'.
e79016aa
KH
410
411Keys specific to Follow mode:
412\\{follow-mode-map}"
08a1dbe6 413 :keymap follow-mode-map
782fbf2a
CY
414 (if follow-mode
415 (progn
416 (add-hook 'compilation-filter-hook 'follow-align-compilation-windows t t)
417 (add-hook 'post-command-hook 'follow-post-command-hook t)
418 (add-hook 'window-size-change-functions 'follow-window-size-change t))
419 ;; Remove globally-installed hook functions only if there is no
420 ;; other Follow mode buffer.
421 (let ((buffers (buffer-list))
422 following)
423 (while (and (not following) buffers)
424 (setq following (buffer-local-value 'follow-mode (car buffers))
425 buffers (cdr buffers)))
426 (unless following
427 (remove-hook 'post-command-hook 'follow-post-command-hook)
428 (remove-hook 'window-size-change-functions 'follow-window-size-change)))
429 (remove-hook 'compilation-filter-hook 'follow-align-compilation-windows t)))
e79016aa
KH
430
431(defun follow-find-file-hook ()
17f6d002 432 "Find-file hook for Follow mode. See the variable `follow-auto'."
782fbf2a 433 (if follow-auto (follow-mode 1)))
e79016aa 434
c93b886f 435;;; User functions
e79016aa 436
c93b886f 437;;; Scroll
e79016aa 438
17f6d002 439;; `scroll-up' and `-down', but for windows in Follow mode.
e79016aa 440;;
4c36be58 441;; Almost like the real thing, except when the cursor ends up outside
e79016aa 442;; the top or bottom... In our case however, we end up outside the
40ba43b4 443;; window and hence we are recentered. Should we let `recenter' handle
e79016aa
KH
444;; the point position we would never leave the selected window. To do
445;; it ourselves we would need to do our own redisplay, which is easier
446;; said than done. (Why didn't I do a real display abstraction from
447;; the beginning?)
448;;
449;; We must sometimes set `follow-internal-force-redisplay', otherwise
450;; our post-command-hook will move our windows back into the old
451;; position... (This would also be corrected if we would have had a
452;; good redisplay abstraction.)
453
454(defun follow-scroll-up (&optional arg)
17f6d002 455 "Scroll text in a Follow mode window chain up.
e79016aa
KH
456
457If called with no ARG, the `next-screen-context-lines' last lines of
458the bottom window in the chain will be visible in the top window.
459
460If called with an argument, scroll ARG lines up.
461Negative ARG means scroll downward.
462
17f6d002 463Works like `scroll-up' when not in Follow mode."
e79016aa 464 (interactive "P")
782fbf2a 465 (cond ((not follow-mode)
e79016aa
KH
466 (scroll-up arg))
467 (arg
468 (save-excursion (scroll-up arg))
469 (setq follow-internal-force-redisplay t))
470 (t
471 (let* ((windows (follow-all-followers))
472 (end (window-end (car (reverse windows)))))
473 (if (eq end (point-max))
474 (signal 'end-of-buffer nil)
475 (select-window (car windows))
ff753438
RS
476 ;; `window-end' might return nil.
477 (if end
478 (goto-char end))
e79016aa
KH
479 (vertical-motion (- next-screen-context-lines))
480 (set-window-start (car windows) (point)))))))
481
482
483(defun follow-scroll-down (&optional arg)
17f6d002 484 "Scroll text in a Follow mode window chain down.
e79016aa
KH
485
486If called with no ARG, the `next-screen-context-lines' top lines of
487the top window in the chain will be visible in the bottom window.
488
489If called with an argument, scroll ARG lines down.
490Negative ARG means scroll upward.
491
17f6d002 492Works like `scroll-up' when not in Follow mode."
e79016aa 493 (interactive "P")
782fbf2a 494 (cond ((not follow-mode)
e79016aa
KH
495 (scroll-up arg))
496 (arg
497 (save-excursion (scroll-down arg)))
498 (t
499 (let* ((windows (follow-all-followers))
500 (win (car (reverse windows)))
501 (start (window-start (car windows))))
502 (if (eq start (point-min))
503 (signal 'beginning-of-buffer nil)
504 (select-window win)
505 (goto-char start)
cb02be17 506 (vertical-motion (- (- (window-height win)
99dfcc0d 507 (if header-line-format 2 1)
e79016aa
KH
508 next-screen-context-lines)))
509 (set-window-start win (point))
510 (goto-char start)
511 (vertical-motion (- next-screen-context-lines 1))
512 (setq follow-internal-force-redisplay t))))))
513
782fbf2a
CY
514(declare-function comint-adjust-point "comint" (window))
515(defvar comint-scroll-show-maximum-output)
516
517(defun follow-comint-scroll-to-bottom (&optional window)
518 "Scroll the bottom-most window in the current Follow chain.
519This is to be called by `comint-postoutput-scroll-to-bottom'."
520 (let* ((buffer (current-buffer))
521 (selected (selected-window))
522 (is-selected (eq (window-buffer) buffer))
523 some-window)
524 (when (or is-selected
525 (setq some-window (get-buffer-window)))
526 (let* ((pos (progn (comint-adjust-point nil) (point)))
527 (win (if is-selected
528 selected
529 (car (last (follow-all-followers some-window))))))
530 (select-window win)
531 (goto-char pos)
532 (setq follow-windows-start-end-cache nil)
533 (follow-adjust-window win pos)
534 (unless is-selected
535 (select-window selected)
536 (set-buffer buffer))))))
537
538(defun follow-align-compilation-windows ()
539 "Align the windows of the current Follow mode buffer.
540This is to be called from `compilation-filter-hook'."
541 (let ((buffer (current-buffer))
542 (win (get-buffer-window))
543 (selected (selected-window)))
544 (when (and follow-mode (waiting-for-user-input-p) win)
545 (let ((windows (follow-all-followers win)))
546 (unless (eq (window-buffer selected) buffer)
547 (setq win (car windows))
548 (select-window win))
549 (follow-redisplay windows win t)
550 (setq follow-windows-start-end-cache nil)
551 (unless (eq selected win)
552 (select-window selected)
553 (set-buffer buffer))))))
554
c93b886f 555;;; Buffer
e79016aa
KH
556
557;;;###autoload
558(defun follow-delete-other-windows-and-split (&optional arg)
17f6d002 559 "Create two side by side windows and enter Follow mode.
e79016aa
KH
560
561Execute this command to display as much as possible of the text
cb02be17 562in the selected window. All other windows, in the current
e79016aa 563frame, are deleted and the selected window is split in two
17f6d002 564side-by-side windows. Follow mode is activated, hence the
e79016aa
KH
565two windows always will display two successive pages.
566\(If one window is moved, the other one will follow.)
567
8cd3480b 568If ARG is positive, the leftmost window is selected. If negative,
e79016aa 569the rightmost is selected. If ARG is nil, the leftmost window is
782fbf2a 570selected if the original window is the first one in the frame."
e79016aa 571 (interactive "P")
cb02be17 572 (let ((other (or (and (null arg)
e79016aa
KH
573 (not (eq (selected-window)
574 (frame-first-window (selected-frame)))))
575 (and arg
576 (< (prefix-numeric-value arg) 0))))
577 (start (window-start)))
578 (delete-other-windows)
2d197ffb 579 (split-window-right)
cb02be17 580 (if other
e79016aa
KH
581 (progn
582 (other-window 1)
583 (set-window-start (selected-window) start)
584 (setq follow-internal-force-redisplay t)))
585 (follow-mode 1)))
586
587(defun follow-switch-to-buffer (buffer)
17f6d002 588 "Show BUFFER in all windows in the current Follow mode window chain."
e79016aa
KH
589 (interactive "BSwitch to Buffer: ")
590 (let ((orig-window (selected-window))
591 (windows (follow-all-followers)))
592 (while windows
593 (select-window (car windows))
594 (switch-to-buffer buffer)
595 (setq windows (cdr windows)))
596 (select-window orig-window)))
597
598
599(defun follow-switch-to-buffer-all (&optional buffer)
600 "Show BUFFER in all windows on this frame.
601Defaults to current buffer."
cb02be17 602 (interactive (list (read-buffer "Switch to Buffer: "
e79016aa
KH
603 (current-buffer))))
604 (or buffer (setq buffer (current-buffer)))
605 (let ((orig-window (selected-window)))
782fbf2a
CY
606 (walk-windows (lambda (win)
607 (select-window win)
608 (switch-to-buffer buffer))
609 'no-minibuf)
e79016aa
KH
610 (select-window orig-window)
611 (follow-redisplay)))
612
613
614(defun follow-switch-to-current-buffer-all ()
782fbf2a 615 "Show current buffer in all windows on this frame, and enter Follow mode."
e79016aa 616 (interactive)
782fbf2a
CY
617 (unless follow-mode
618 (follow-mode 1))
e79016aa
KH
619 (follow-switch-to-buffer-all))
620
c93b886f 621;;; Movement
e79016aa 622
fc779383 623;; Note, these functions are not very useful, at least not unless you
e79016aa
KH
624;; rebind the rather cumbersome key sequence `C-c . p'.
625
626(defun follow-next-window ()
627 "Select the next window showing the same buffer."
628 (interactive)
629 (let ((succ (cdr (follow-split-followers (follow-all-followers)))))
630 (if succ
631 (select-window (car succ))
632 (error "%s" "No more windows"))))
633
634
635(defun follow-previous-window ()
636 "Select the previous window showing the same buffer."
637 (interactive)
638 (let ((pred (car (follow-split-followers (follow-all-followers)))))
639 (if pred
640 (select-window (car pred))
641 (error "%s" "No more windows"))))
642
643
644(defun follow-first-window ()
645 "Select the first window in the frame showing the same buffer."
646 (interactive)
647 (select-window (car (follow-all-followers))))
648
649
650(defun follow-last-window ()
651 "Select the last window in the frame showing the same buffer."
652 (interactive)
653 (select-window (car (reverse (follow-all-followers)))))
654
c93b886f 655;;; Redraw
e79016aa
KH
656
657(defun follow-recenter (&optional arg)
cb02be17
KH
658 "Recenter the middle window around point.
659Rearrange all other windows around the middle window.
e79016aa
KH
660
661With a positive argument, place the current line ARG lines
8cd3480b
JB
662from the top. With a negative argument, place it -ARG lines
663from the bottom."
e79016aa
KH
664 (interactive "P")
665 (if arg
666 (let ((p (point))
667 (arg (prefix-numeric-value arg)))
668 (if (>= arg 0)
669 ;; Recenter relative to the top.
670 (progn
671 (follow-first-window)
672 (goto-char p)
673 (recenter arg))
674 ;; Recenter relative to the bottom.
675 (follow-last-window)
676 (goto-char p)
677 (recenter arg)
678 ;; Otherwise, our post-command-hook will move the window
679 ;; right back.
680 (setq follow-internal-force-redisplay t)))
681 ;; Recenter in the middle.
682 (let* ((dest (point))
683 (windows (follow-all-followers))
684 (win (nth (/ (- (length windows) 1) 2) windows)))
685 (select-window win)
686 (goto-char dest)
782fbf2a 687 (recenter))))
e79016aa
KH
688
689
690(defun follow-redraw ()
691 "Arrange windows displaying the same buffer in successor order.
692This function can be called even if the buffer is not in Follow mode.
693
694Hopefully, there should be no reason to call this function when in
695Follow mode since the windows should always be aligned."
696 (interactive)
697 (sit-for 0)
698 (follow-redisplay))
699
c93b886f 700;;; End of buffer
e79016aa
KH
701
702(defun follow-end-of-buffer (&optional arg)
17f6d002 703 "Move point to the end of the buffer, Follow mode style.
e79016aa
KH
704
705If the end is not visible, it will be displayed in the last possible
17f6d002 706window in the Follow mode window chain.
e79016aa 707
cb02be17 708The mark is left at the previous position. With arg N, put point N/10
e79016aa
KH
709of the way from the true end."
710 (interactive "P")
711 (let ((followers (follow-all-followers))
712 (pos (point)))
713 (cond (arg
714 (select-window (car (reverse followers))))
cb02be17 715 ((follow-select-if-end-visible
e79016aa
KH
716 (follow-windows-start-end followers)))
717 (t
718 (select-window (car (reverse followers)))))
719 (goto-char pos)
2a4b9211
RS
720 (with-no-warnings
721 (end-of-buffer arg))))
e79016aa 722
c93b886f 723;;; Display
e79016aa 724
782fbf2a
CY
725(defun follow--window-sorter (w1 w2)
726 "Sorting function for W1 and W2 based on their positions.
727Return non-nil if W1 is above W2; if their top-lines
728are at the same position, return non-nil if W1 is to the
729left of W2."
730 (let* ((edge-1 (window-pixel-edges w1))
731 (edge-2 (window-pixel-edges w2))
732 (y1 (nth 1 edge-1))
733 (y2 (nth 1 edge-2)))
734 (if (= y1 y2)
735 (< (car edge-1) (car edge-2))
736 (< y1 y2))))
737
738(defun follow-all-followers (&optional win)
739 "Return all windows displaying the same buffer as the WIN.
740The list is sorted with topmost and leftmost windows first, and
741contains only windows in the same frame as WIN. If WIN is nil,
742it defaults to the selected window."
743 (unless (window-live-p win)
744 (setq win (selected-window)))
745 (let ((buffer (window-buffer win))
746 windows)
747 (dolist (w (window-list (window-frame win) 'no-minibuf win))
748 (if (eq (window-buffer w) buffer)
749 (push w windows)))
750 (sort windows 'follow--window-sorter)))
e79016aa
KH
751
752(defun follow-split-followers (windows &optional win)
782fbf2a 753 "Split WINDOWS into two sets: predecessors and successors.
cb02be17 754Return `(PRED . SUCC)' where `PRED' and `SUCC' are ordered starting
e79016aa 755from the selected window."
cb02be17 756 (or win
e79016aa
KH
757 (setq win (selected-window)))
758 (let ((pred '()))
759 (while (not (eq (car windows) win))
760 (setq pred (cons (car windows) pred))
761 (setq windows (cdr windows)))
762 (cons pred (cdr windows))))
763
e79016aa 764(defun follow-calc-win-end (&optional win)
c93b886f
CY
765 "Calculate the end position for window WIN.
766Return (END-POS END-OF-BUFFER).
767
768Actually, the position returned is the start of the line after
769the last fully-visible line in WIN. If WIN is nil, the selected
770window is used."
771 (let* ((win (or win (selected-window)))
772 (edges (window-inside-pixel-edges win))
773 (ht (- (nth 3 edges) (nth 1 edges)))
774 (last-line-pos (posn-point (posn-at-x-y 0 (1- ht) win))))
775 (if (pos-visible-in-window-p last-line-pos win)
776 (let ((end (window-end win t)))
777 (list end (= end (point-max))))
778 (list last-line-pos nil))))
e79016aa 779
e79016aa 780(defun follow-calc-win-start (windows pos win)
782fbf2a
CY
781 "Determine the start of window WIN in a Follow mode window chain.
782WINDOWS is a list of chained windows, and POS is the starting
783position for the first window in the list. If WIN is nil, return
784the point below all windows."
785 (while (and windows (not (eq (car windows) win)))
786 (let ((old-start (window-start (car windows))))
787 ;; Can't use `save-window-excursion' since it triggers a redraw.
e79016aa 788 (set-window-start (car windows) pos 'noforce)
4484b97d 789 (setq pos (car (follow-calc-win-end (car windows))))
782fbf2a
CY
790 (set-window-start (car windows) old-start 'noforce)
791 (setq windows (cdr windows))))
792 pos)
e79016aa 793
f3bfa025
KH
794;; The result from `follow-windows-start-end' is cached when using
795;; a handful simple commands, like cursor movement commands.
796
797(defsubst follow-cache-valid-p (windows)
798 "Test if the cached value of `follow-windows-start-end' can be used.
799Note that this handles the case when the cache has been set to nil."
800 (let ((res t)
801 (cache follow-windows-start-end-cache))
802 (while (and res windows cache)
803 (setq res (and (eq (car windows)
804 (car (car cache)))
805 (eq (window-start (car windows))
806 (car (cdr (car cache))))))
807 (setq windows (cdr windows))
808 (setq cache (cdr cache)))
809 (and res (null windows) (null cache))))
810
e79016aa 811(defun follow-windows-start-end (windows)
782fbf2a 812 "Return a list of (WIN START END BUFFER-END-P) for window list WINDOWS."
f3bfa025
KH
813 (if (follow-cache-valid-p windows)
814 follow-windows-start-end-cache
30bf4750
CY
815 (let ((orig-win (selected-window))
816 win-start-end)
817 (dolist (w windows)
818 (select-window w)
819 (push (cons w (cons (window-start) (follow-calc-win-end)))
820 win-start-end))
f3bfa025 821 (select-window orig-win)
30bf4750 822 (setq follow-windows-start-end-cache (nreverse win-start-end)))))
f3bfa025 823
f3bfa025 824(defsubst follow-pos-visible (pos win win-start-end)
e79016aa
KH
825 "Non-nil when POS is visible in WIN."
826 (let ((wstart-wend-bend (cdr (assq win win-start-end))))
827 (and (>= pos (car wstart-wend-bend))
30bf4750 828 (or (< pos (cadr wstart-wend-bend))
e79016aa
KH
829 (nth 2 wstart-wend-bend)))))
830
831
30bf4750 832;; By `aligned' we mean that for all adjacent windows, the end of the
e79016aa
KH
833;; first is equal with the start of the successor. The first window
834;; should start at a full screen line.
835
f3bfa025 836(defsubst follow-windows-aligned-p (win-start-end)
782fbf2a
CY
837 "Non-nil if the follower windows are aligned.
838The argument, WIN-START-END, should be a list of the form
839returned by `follow-windows-start-end'."
840 (let ((result t))
841 (while (and win-start-end result)
842 (if (cdr win-start-end)
843 (setq result (eq (nth 2 (car win-start-end))
844 (nth 1 (cadr win-start-end)))))
e79016aa 845 (setq win-start-end (cdr win-start-end)))
782fbf2a 846 result))
e79016aa
KH
847
848;; Check if the point is visible in all windows. (So that
849;; no one will be recentered.)
850
851(defun follow-point-visible-all-windows-p (win-start-end)
8cd3480b 852 "Non-nil when the `window-point' is visible in all windows."
e79016aa
KH
853 (let ((res t))
854 (while (and res win-start-end)
f3bfa025
KH
855 (setq res (follow-pos-visible (window-point (car (car win-start-end)))
856 (car (car win-start-end))
857 win-start-end))
e79016aa
KH
858 (setq win-start-end (cdr win-start-end)))
859 res))
860
861
5eba16a3 862;; Make sure WIN always starts at the beginning of a whole screen
e79016aa
KH
863;; line. If WIN is not aligned the start is updated which probably
864;; will lead to a redisplay of the screen later on.
865;;
866;; This is used with the first window in a follow chain. The reason
867;; is that we want to detect that the point is outside the window.
868;; (Without the update, the start of the window will move as the
869;; user presses BackSpace, and the other window redisplay routines
870;; will move the start of the window in the wrong direction.)
871
872(defun follow-update-window-start (win)
873 "Make sure that the start of WIN starts at a full screen line."
874 (save-excursion
875 (goto-char (window-start win))
4484b97d 876 (unless (bolp)
e79016aa 877 (vertical-motion 0 win)
4484b97d 878 (unless (eq (point) (window-start win))
e79016aa
KH
879 (vertical-motion 1 win)
880 (set-window-start win (point) 'noforce)))))
881
e79016aa
KH
882(defun follow-select-if-visible (dest win-start-end)
883 "Select and return a window, if DEST is visible in it.
884Return the selected window."
782fbf2a 885 (let (win win-end wse)
e79016aa 886 (while (and (not win) win-start-end)
ff753438 887 ;; Don't select a window that was just moved. This makes it
782fbf2a
CY
888 ;; possible to later select the last window after a
889 ;; `end-of-buffer' command.
890 (setq wse (car win-start-end))
891 (when (follow-pos-visible dest (car wse) win-start-end)
892 (setq win (car wse)
893 win-end (nth 2 wse))
4484b97d 894 (select-window win))
e79016aa
KH
895 (setq win-start-end (cdr win-start-end)))
896 win))
897
5eba16a3
JB
898;; Lets select a window showing the end. Make sure we only select it if
899;; it wasn't just moved here. (I.e. M-> shall not unconditionally place
e79016aa
KH
900;; the point in the selected window.)
901;;
da6062e6 902;; (Compatibility kludge: in Emacs `window-end' is equal to `point-max';
e79016aa 903;; in XEmacs, it is equal to `point-max + 1'. Should I really bother
cb02be17 904;; checking `window-end' now when I check `end-of-buffer' explicitly?)
e79016aa
KH
905
906(defun follow-select-if-end-visible (win-start-end)
907 "Select and return a window, if end is visible in it."
908 (let ((win nil))
909 (while (and (not win) win-start-end)
ff753438 910 ;; Don't select a window that was just moved. This makes it
e79016aa
KH
911 ;; possible to later select the last window after a `end-of-buffer'
912 ;; command.
913 (if (and (eq (point-max) (nth 2 (car win-start-end)))
914 (nth 3 (car win-start-end))
ff753438
RS
915 ;; `window-end' might return nil.
916 (let ((end (window-end (car (car win-start-end)))))
917 (and end
918 (eq (point-max) (min (point-max) end)))))
e79016aa
KH
919 (progn
920 (setq win (car (car win-start-end)))
921 (select-window win)))
922 (setq win-start-end (cdr win-start-end)))
923 win))
924
925
ff753438 926;; Select a window that will display the point if the windows would
e79016aa
KH
927;; be redisplayed with the first window fixed. This is useful for
928;; example when the user has pressed return at the bottom of a window
929;; as the point is not visible in any window.
930
931(defun follow-select-if-visible-from-first (dest windows)
30bf4750
CY
932 "Try to select one of WINDOWS without repositioning the topmost window.
933If one of the windows in WINDOWS contains DEST, select it, call
934`follow-redisplay', move point to DEST, and return that window.
935Otherwise, return nil."
936 (let (win end-pos-end-p)
e79016aa
KH
937 (save-excursion
938 (goto-char (window-start (car windows)))
939 ;; Make sure the line start in the beginning of a real screen
940 ;; line.
cb02be17 941 (vertical-motion 0 (car windows))
30bf4750 942 (when (>= dest (point))
e79016aa
KH
943 ;; At or below the start. Check the windows.
944 (save-window-excursion
30bf4750
CY
945 (let ((windows windows))
946 (while (and (not win) windows)
947 (set-window-start (car windows) (point) 'noforce)
948 (setq end-pos-end-p (follow-calc-win-end (car windows)))
949 (goto-char (car end-pos-end-p))
c93b886f
CY
950 ;; Visible, if dest above end, or if eob is visible
951 ;; inside the window.
30bf4750
CY
952 (if (or (car (cdr end-pos-end-p))
953 (< dest (point)))
e79016aa 954 (setq win (car windows))
30bf4750
CY
955 (setq windows (cdr windows))))))))
956 (when win
957 (select-window win)
958 (follow-redisplay windows (car windows))
959 (goto-char dest))
e79016aa
KH
960 win))
961
c93b886f 962;;; Redisplay
e79016aa
KH
963
964;; Redraw all the windows on the screen, starting with the top window.
c80e3b4a 965;; The window used as as marker is WIN, or the selected window if WIN
4484b97d
CY
966;; is nil. Start every window directly after the end of the previous
967;; window, to make sure long lines are displayed correctly.
e79016aa 968
30bf4750 969(defun follow-redisplay (&optional windows win preserve-win)
e79016aa
KH
970 "Reposition the WINDOWS around WIN.
971Should the point be too close to the roof we redisplay everything
8cd3480b 972from the top. WINDOWS should contain a list of windows to
5eba16a3 973redisplay; it is assumed that WIN is a member of the list.
e79016aa
KH
974Should WINDOWS be nil, the windows displaying the
975same buffer as WIN, in the current frame, are used.
30bf4750
CY
976Should WIN be nil, the selected window is used.
977If PRESERVE-WIN is non-nil, keep WIN itself unchanged while
978repositioning the other windows."
4484b97d
CY
979 (or win (setq win (selected-window)))
980 (or windows (setq windows (follow-all-followers win)))
981 ;; Calculate the start of the first window.
982 (let* ((old-win-start (window-start win))
983 (try-first-start (follow-estimate-first-window-start
984 windows win old-win-start))
985 (try-win-start (follow-calc-win-start
986 windows try-first-start win))
987 (start (cond ((= try-win-start old-win-start)
988 (follow-debug-message "exact")
989 try-first-start)
990 ((< try-win-start old-win-start)
991 (follow-debug-message "above")
992 (follow-calculate-first-window-start-from-above
993 windows try-first-start win old-win-start))
994 (t
995 (follow-debug-message "below")
996 (follow-calculate-first-window-start-from-below
997 windows try-first-start win old-win-start)))))
998 (dolist (w windows)
30bf4750
CY
999 (unless (and preserve-win (eq w win))
1000 (set-window-start w start))
4484b97d 1001 (setq start (car (follow-calc-win-end w))))))
e79016aa 1002
e79016aa
KH
1003(defun follow-estimate-first-window-start (windows win start)
1004 "Estimate the position of the first window.
4484b97d
CY
1005The estimate is computed by assuming that the window WIN, which
1006should be a member of WINDOWS, starts at position START."
1007 (let ((windows-before (car (follow-split-followers windows win))))
e79016aa
KH
1008 (save-excursion
1009 (goto-char start)
e79016aa 1010 (vertical-motion 0 win)
4484b97d
CY
1011 (dolist (w windows-before)
1012 (vertical-motion (- 1 (window-text-height w)) w))
1013 (point))))
e79016aa
KH
1014
1015
1016;; Find the starting point, start at GUESS and search downward.
1017;; The returned point is always a point below GUESS.
1018
cb02be17 1019(defun follow-calculate-first-window-start-from-above
e79016aa
KH
1020 (windows guess win start)
1021 (save-excursion
1022 (let ((done nil)
1023 win-start
1024 res)
1025 (goto-char guess)
1026 (while (not done)
1027 (if (not (= (vertical-motion 1 (car windows)) 1))
1028 ;; Hit bottom! (Can we really do this?)
1029 ;; We'll keep it, since it ensures termination.
1030 (progn
1031 (setq done t)
1032 (setq res (point-max)))
1033 (setq win-start (follow-calc-win-start windows (point) win))
1034 (if (>= win-start start)
4484b97d 1035 (setq done t res (point)))))
e79016aa
KH
1036 res)))
1037
1038
1039;; Find the starting point, start at GUESS and search upward. Return
1040;; a point on the same line as GUESS, or above.
e79016aa
KH
1041
1042(defun follow-calculate-first-window-start-from-below
1043 (windows guess &optional win start)
1044 (setq win (or win (selected-window)))
1045 (setq start (or start (window-start win)))
1046 (save-excursion
13d5c73f 1047 (let (done win-start res opoint)
fc779383 1048 ;; Always calculate what happens when no line is displayed in the first
e79016aa
KH
1049 ;; window. (The `previous' res is needed below!)
1050 (goto-char guess)
1051 (vertical-motion 0 (car windows))
1052 (setq res (point))
1053 (while (not done)
13d5c73f 1054 (setq opoint (point))
e79016aa
KH
1055 (if (not (= (vertical-motion -1 (car windows)) -1))
1056 ;; Hit roof!
4484b97d 1057 (setq done t res (point-min))
e79016aa 1058 (setq win-start (follow-calc-win-start windows (point) win))
13d5c73f
CY
1059 (cond ((>= (point) opoint)
1060 ;; In some pathological cases, vertical-motion may
1061 ;; return -1 even though point has not decreased. In
1062 ;; that case, avoid looping forever.
1063 (setq done t res (point)))
1064 ((= win-start start) ; Perfect match, use this value
1065 (setq done t res (point)))
5eba16a3 1066 ((< win-start start) ; Walked to far, use previous result
e79016aa
KH
1067 (setq done t))
1068 (t ; Store result for next iteration
1069 (setq res (point))))))
1070 res)))
1071
c93b886f 1072;;; Avoid tail recenter
e79016aa 1073
782fbf2a
CY
1074;; This sets the window internal flag `force_start'. The effect is
1075;; that windows only displaying the tail aren't recentered.
e79016aa 1076;;
782fbf2a
CY
1077;; A window displaying only the tail, is a window whose window-start
1078;; position is equal to (point-max) of the buffer it displays.
e79016aa 1079
06b60517 1080(defun follow-avoid-tail-recenter (&rest _rest)
e79016aa 1081 "Make sure windows displaying the end of a buffer aren't recentered.
7194219d 1082This is done by reading and rewriting the start position of
17f6d002 1083non-first windows in Follow mode."
782fbf2a
CY
1084 (let* ((orig-buffer (current-buffer))
1085 (top (frame-first-window (selected-frame)))
1086 (win top)
1087 who) ; list of (buffer . frame)
1088 ;; If the only window in the frame is a minibuffer
1089 ;; window, `next-window' will never find it again...
1090 (unless (window-minibuffer-p top)
1091 (while ;; look, no body!
1092 (let ((start (window-start win))
1093 (pair (cons (window-buffer win) (window-frame win))))
1094 (set-buffer (window-buffer win))
1095 (cond ((null (member pair who))
1096 (setq who (cons pair who)))
1097 ((and follow-mode (eq (point-max) start))
1098 ;; Write the same window start back, but don't
1099 ;; set the NOFORCE flag.
1100 (set-window-start win start)))
1101 (setq win (next-window win 'not t))
1102 (not (eq win top)))) ;; Loop while this is true.
1103 (set-buffer orig-buffer))))
e79016aa 1104
c93b886f 1105;;; Post Command Hook
e79016aa 1106
08a1dbe6 1107;; The magic little box. This function is called after every command.
e79016aa
KH
1108
1109;; This is not as complicated as it seems. It is simply a list of common
1110;; display situations and the actions to take, plus commands for redrawing
1111;; the screen if it should be unaligned.
1112;;
1113;; We divide the check into two parts; whether we are at the end or not.
99dfcc0d 1114;; This is due to the fact that the end can actually be visible
e79016aa
KH
1115;; in several window even though they are aligned.
1116
1117(defun follow-post-command-hook ()
cb02be17 1118 "Ensure that the windows in Follow mode are adjacent after each command."
4484b97d
CY
1119 (unless (input-pending-p)
1120 (let ((follow-inside-post-command-hook t)
1121 (win (selected-window)))
e79016aa 1122 ;; Work in the selected window, not in the current buffer.
4484b97d
CY
1123 (with-current-buffer (window-buffer win)
1124 (unless (and (symbolp this-command)
1125 (get this-command 'follow-mode-use-cache))
782fbf2a
CY
1126 (setq follow-windows-start-end-cache nil)))
1127 (follow-adjust-window win (point)))))
1128
1129(defun follow-adjust-window (win dest)
1130 ;; Adjust the window WIN and its followers.
1131 (with-current-buffer (window-buffer win)
1132 (when (and follow-mode
1133 (not (window-minibuffer-p win)))
1134 (let* ((windows (follow-all-followers win))
1135 (win-start-end (progn
1136 (follow-update-window-start (car windows))
1137 (follow-windows-start-end windows)))
1138 (aligned (follow-windows-aligned-p win-start-end))
1139 (visible (follow-pos-visible dest win win-start-end))
1140 selected-window-up-to-date)
1141 (unless (and aligned visible)
1142 (setq follow-windows-start-end-cache nil))
1143
1144 ;; Select a window to display point.
1145 (unless follow-internal-force-redisplay
1146 (if (eq dest (point-max))
1147 ;; Be careful at point-max: the display can be aligned
1148 ;; while DEST can be visible in several windows.
1149 (cond
1150 ;; Select the current window, but only when the display
1151 ;; is correct. (When inserting characters in a tail
1152 ;; window, the display is not correct, as they are
1153 ;; shown twice.)
1154 ;;
1155 ;; Never stick to the current window after a deletion.
1156 ;; Otherwise, when typing `DEL' in a window showing
1157 ;; only the end of the file, a character would be
1158 ;; removed from the window above, which is very
1159 ;; unintuitive.
1160 ((and visible
1161 aligned
1162 (not (memq this-command
1163 '(backward-delete-char
1164 delete-backward-char
1165 backward-delete-char-untabify
1166 kill-region))))
1167 (follow-debug-message "Max: same"))
1168 ;; If the end is visible, and the window doesn't
1169 ;; seems like it just has been moved, select it.
1170 ((follow-select-if-end-visible win-start-end)
1171 (follow-debug-message "Max: end visible")
1172 (setq visible t aligned nil)
1173 (goto-char dest))
1174 ;; Just show the end...
1175 (t
1176 (follow-debug-message "Max: default")
1177 (select-window (car (last windows)))
1178 (goto-char dest)
1179 (setq visible nil aligned nil)))
1180
1181 ;; We're not at the end, here life is much simpler.
1182 (cond
1183 ;; This is the normal case!
1184 ;; It should be optimized for speed.
1185 ((and visible aligned)
1186 (follow-debug-message "same"))
1187 ;; Pick a position in any window. If the display is ok,
1188 ;; this picks the `correct' window.
1189 ((follow-select-if-visible dest win-start-end)
1190 (follow-debug-message "visible")
1191 (goto-char dest)
1192 ;; Perform redisplay, in case line is partially visible.
1193 (setq visible nil))
1194 ;; Not visible anywhere else, lets pick this one.
1195 (visible
1196 (follow-debug-message "visible in selected."))
1197 ;; If DEST is before the first window start, select the
1198 ;; first window.
1199 ((< dest (nth 1 (car win-start-end)))
1200 (follow-debug-message "before first")
1201 (select-window (car windows))
1202 (goto-char dest)
1203 (setq visible nil aligned nil))
1204 ;; If we can position the cursor without moving the first
1205 ;; window, do it. This is the case that catches `RET' at
1206 ;; the bottom of a window.
1207 ((follow-select-if-visible-from-first dest windows)
1208 (follow-debug-message "Below first")
1209 (setq visible t aligned t))
1210 ;; None of the above. Stick to the selected window.
1211 (t
1212 (follow-debug-message "None")
1213 (setq visible nil aligned nil))))
1214
1215 ;; If a new window was selected, make sure that the old is
1216 ;; not scrolled when the point is outside the window.
1217 (unless (eq win (selected-window))
1218 (let ((p (window-point win)))
1219 (set-window-start win (window-start win) nil)
1220 (set-window-point win p))))
1221
1222 (unless visible
1223 ;; If point may not be visible in the selected window,
1224 ;; perform a redisplay; this ensures scrolling.
1225 (let ((opoint (point)))
1226 (redisplay)
1227 ;; If this `redisplay' moved point, we got clobbered by a
1228 ;; previous call to `set-window-start'. Try again.
1229 (when (/= (point) opoint)
1230 (goto-char opoint)
1231 (redisplay)))
1232
1233 (setq selected-window-up-to-date t)
1234 (follow-avoid-tail-recenter)
1235 (setq win-start-end (follow-windows-start-end windows)
1236 follow-windows-start-end-cache nil
1237 aligned nil))
1238
1239 ;; Now redraw the windows around the selected window.
1240 (unless (and (not follow-internal-force-redisplay)
1241 (or aligned
1242 (follow-windows-aligned-p win-start-end))
1243 (follow-point-visible-all-windows-p win-start-end))
1244 (setq follow-internal-force-redisplay nil)
1245 (follow-redisplay windows (selected-window)
1246 selected-window-up-to-date)
1247 (setq win-start-end (follow-windows-start-end windows)
1248 follow-windows-start-end-cache nil)
1249 ;; The point can ends up in another window when DEST is at
1250 ;; the beginning of the buffer and the selected window is
1251 ;; not the first. It can also happen when long lines are
1252 ;; used and there is a big difference between the width of
1253 ;; the windows. (When scrolling one line in a wide window
1254 ;; which will cause a move larger that an entire small
1255 ;; window.)
1256 (unless (follow-pos-visible dest win win-start-end)
1257 (follow-select-if-visible dest win-start-end)
1258 (goto-char dest)))
1259
1260 ;; If the region is visible, make it look good when spanning
1261 ;; multiple windows.
1262 (when (region-active-p)
1263 (follow-maximize-region
1264 (selected-window) windows win-start-end)))
1265
1266 ;; Whether or not the buffer was in follow mode, update windows
1267 ;; displaying the tail so that Emacs won't recenter them.
1268 (follow-avoid-tail-recenter))))
e79016aa 1269
c93b886f 1270;;; The region
e79016aa
KH
1271
1272;; Tries to make the highlighted area representing the region look
cb02be17 1273;; good when spanning several windows.
e79016aa
KH
1274;;
1275;; Not perfect, as the point can't be placed at window end, only at
cb02be17 1276;; end-1. This will highlight a little bit in windows above
e79016aa
KH
1277;; the current.
1278
1279(defun follow-maximize-region (win windows win-start-end)
cb02be17 1280 "Make a highlighted region stretching multiple windows look good."
e79016aa
KH
1281 (let* ((all (follow-split-followers windows win))
1282 (pred (car all))
1283 (succ (cdr all))
1284 data)
1285 (while pred
1286 (setq data (assq (car pred) win-start-end))
1287 (set-window-point (car pred) (max (nth 1 data) (- (nth 2 data) 1)))
1288 (setq pred (cdr pred)))
1289 (while succ
1290 (set-window-point (car succ) (nth 1 (assq (car succ) win-start-end)))
1291 (setq succ (cdr succ)))))
1292
c93b886f 1293;;; Scroll bar
e79016aa
KH
1294
1295;;;; Scroll-bar support code.
1296
782fbf2a
CY
1297;; This handles the case where the user drags the scroll bar of a
1298;; non-selected window whose buffer is in Follow mode.
1299
1300(defun follow-scroll-bar-toolkit-scroll (event)
1301 (interactive "e")
1302 (scroll-bar-toolkit-scroll event)
1303 (follow-redraw-after-event event))
1304
1305(defun follow-scroll-bar-drag (event)
1306 (interactive "e")
1307 (scroll-bar-drag event)
1308 (follow-redraw-after-event event))
1309
1310(defun follow-scroll-bar-scroll-up (event)
1311 (interactive "e")
1312 (scroll-bar-scroll-up event)
1313 (follow-redraw-after-event event))
1314
1315(defun follow-scroll-bar-scroll-down (event)
1316 (interactive "e")
1317 (scroll-bar-scroll-down event)
1318 (follow-redraw-after-event event))
1319
87233a14
CY
1320(defun follow-mwheel-scroll (event)
1321 (interactive "e")
1322 (mwheel-scroll event)
1323 (follow-redraw-after-event event))
1324
782fbf2a 1325(defun follow-redraw-after-event (event)
87233a14
CY
1326 "Re-align the Follow mode windows affected by EVENT."
1327 (let* ((window (nth 0 (event-end event)))
1328 (buffer (window-buffer window))
1329 (orig-win (selected-window)))
1330 (when (and (buffer-local-value 'follow-mode buffer)
782fbf2a
CY
1331 ;; Ignore the case where we scroll the selected window;
1332 ;; that is handled by the post-command hook function.
1333 (not (eq window (selected-window))))
1334 (select-window window)
1335 (follow-redisplay)
87233a14
CY
1336 (unless (eq (window-buffer orig-win) buffer)
1337 (select-window orig-win)))))
e79016aa 1338
c93b886f 1339;;; Window size change
e79016aa 1340
782fbf2a
CY
1341;; The functions in `window-size-change-functions' are called every
1342;; time a window in a frame changes size, most notably after the frame
1343;; has been resized. We call `follow-post-command-hook' for every
1344;; Follow mode buffer visible in any window in the resized frame.
e79016aa 1345;;
782fbf2a
CY
1346;; Since `follow-window-size-change' can be called indirectly from
1347;; `follow-post-command-hook' we have a potential infinite loop. To
1348;; avoid this, we simply do not do anything in this situation. The
1349;; variable `follow-inside-post-command-hook' contains information
1350;; about whether the execution actually is inside the
e79016aa
KH
1351;; post-command-hook or not.
1352
e79016aa
KH
1353(defun follow-window-size-change (frame)
1354 "Redraw all windows in FRAME, when in Follow mode."
782fbf2a
CY
1355 ;; Below, we call `post-command-hook'. Avoid an infloop.
1356 (unless follow-inside-post-command-hook
e79016aa
KH
1357 (let ((buffers '())
1358 (orig-window (selected-window))
1359 (orig-buffer (current-buffer))
1360 (orig-frame (selected-frame))
1361 windows
1362 buf)
1363 (select-frame frame)
1364 (unwind-protect
cb02be17 1365 (walk-windows
782fbf2a
CY
1366 (lambda (win)
1367 (setq buf (window-buffer win))
1368 (unless (memq buf buffers)
1369 (set-buffer buf)
1370 (when follow-mode
1371 (setq windows (follow-all-followers win))
1372 (if (not (memq orig-window windows))
1373 (follow-redisplay windows win)
1374 ;; Make sure we're redrawing around the selected
1375 ;; window.
1376 (select-window orig-window)
1377 (follow-post-command-hook)
1378 (setq orig-window (selected-window)))
1379 (setq buffers (cons buf buffers)))))
1380 'no-minibuf)
e79016aa
KH
1381 (select-frame orig-frame)
1382 (set-buffer orig-buffer)
1383 (select-window orig-window)))))
1384
782fbf2a 1385(add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)
e79016aa 1386
c93b886f 1387;;; Profile support
e79016aa
KH
1388
1389;; The following (non-evaluated) section can be used to
1390;; profile this package using `elp'.
1391;;
1392;; Invalid indentation on purpose!
1393
782fbf2a
CY
1394;; (setq elp-function-list
1395;; '(window-end
1396;; vertical-motion
1397;; follow-mode
1398;; follow-all-followers
1399;; follow-split-followers
1400;; follow-redisplay
1401;; follow-estimate-first-window-start
1402;; follow-calculate-first-window-start-from-above
1403;; follow-calculate-first-window-start-from-below
1404;; follow-calc-win-end
1405;; follow-calc-win-start
1406;; follow-pos-visible
1407;; follow-windows-start-end
1408;; follow-cache-valid-p
1409;; follow-select-if-visible
1410;; follow-select-if-visible-from-first
1411;; follow-windows-aligned-p
1412;; follow-point-visible-all-windows-p
1413;; follow-avoid-tail-recenter
1414;; follow-update-window-start
1415;; follow-post-command-hook))
fc779383 1416
e79016aa
KH
1417(provide 'follow)
1418
e79016aa
KH
1419;; /------------------------------------------------------------------------\
1420;; | "I [..] am rarely happier then when spending an entire day programming |
1421;; | my computer to perform automatically a task that it would otherwise |
1422;; | take me a good ten seconds to do by hand. Ten seconds, I tell myself, |
1423;; | is ten seconds. Time is valuable and ten seconds' worth of it is well |
1424;; | worth the investment of a day's happy activity working out a way to |
1425;; | save it". -- Douglas Adams, "Last Chance to See" |
1426;; \------------------------------------------------------------------------/
1427
1428;;; follow.el ends here