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