* doc-view.el (doc-view-mode): Silence --without-x compilation.
[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
45fdb482 518(defun follow-comint-scroll-to-bottom (&optional _window)
782fbf2a
CY
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 574 (not (eq (selected-window)
12b4c0ea 575 (frame-first-window))))
e79016aa
KH
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."
45fdb482 886 (let (win 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)
45fdb482 893 (setq win (car 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 1084 (let* ((orig-buffer (current-buffer))
12b4c0ea 1085 (top (frame-first-window))
782fbf2a
CY
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.
2bede2ed
XF
1262
1263 ;; FIXME: Why not use `use-region-p' here?
782fbf2a
CY
1264 (when (region-active-p)
1265 (follow-maximize-region
1266 (selected-window) windows win-start-end)))
1267
1268 ;; Whether or not the buffer was in follow mode, update windows
1269 ;; displaying the tail so that Emacs won't recenter them.
1270 (follow-avoid-tail-recenter))))
e79016aa 1271
c93b886f 1272;;; The region
e79016aa
KH
1273
1274;; Tries to make the highlighted area representing the region look
cb02be17 1275;; good when spanning several windows.
e79016aa
KH
1276;;
1277;; Not perfect, as the point can't be placed at window end, only at
cb02be17 1278;; end-1. This will highlight a little bit in windows above
e79016aa
KH
1279;; the current.
1280
1281(defun follow-maximize-region (win windows win-start-end)
cb02be17 1282 "Make a highlighted region stretching multiple windows look good."
e79016aa
KH
1283 (let* ((all (follow-split-followers windows win))
1284 (pred (car all))
1285 (succ (cdr all))
1286 data)
1287 (while pred
1288 (setq data (assq (car pred) win-start-end))
1289 (set-window-point (car pred) (max (nth 1 data) (- (nth 2 data) 1)))
1290 (setq pred (cdr pred)))
1291 (while succ
1292 (set-window-point (car succ) (nth 1 (assq (car succ) win-start-end)))
1293 (setq succ (cdr succ)))))
1294
c93b886f 1295;;; Scroll bar
e79016aa
KH
1296
1297;;;; Scroll-bar support code.
1298
782fbf2a
CY
1299;; This handles the case where the user drags the scroll bar of a
1300;; non-selected window whose buffer is in Follow mode.
1301
1302(defun follow-scroll-bar-toolkit-scroll (event)
1303 (interactive "e")
1304 (scroll-bar-toolkit-scroll event)
1305 (follow-redraw-after-event event))
1306
1307(defun follow-scroll-bar-drag (event)
1308 (interactive "e")
1309 (scroll-bar-drag event)
1310 (follow-redraw-after-event event))
1311
1312(defun follow-scroll-bar-scroll-up (event)
1313 (interactive "e")
1314 (scroll-bar-scroll-up event)
1315 (follow-redraw-after-event event))
1316
1317(defun follow-scroll-bar-scroll-down (event)
1318 (interactive "e")
1319 (scroll-bar-scroll-down event)
1320 (follow-redraw-after-event event))
1321
87233a14
CY
1322(defun follow-mwheel-scroll (event)
1323 (interactive "e")
1324 (mwheel-scroll event)
1325 (follow-redraw-after-event event))
1326
782fbf2a 1327(defun follow-redraw-after-event (event)
87233a14
CY
1328 "Re-align the Follow mode windows affected by EVENT."
1329 (let* ((window (nth 0 (event-end event)))
1330 (buffer (window-buffer window))
1331 (orig-win (selected-window)))
1332 (when (and (buffer-local-value 'follow-mode buffer)
782fbf2a
CY
1333 ;; Ignore the case where we scroll the selected window;
1334 ;; that is handled by the post-command hook function.
1335 (not (eq window (selected-window))))
1336 (select-window window)
1337 (follow-redisplay)
87233a14
CY
1338 (unless (eq (window-buffer orig-win) buffer)
1339 (select-window orig-win)))))
e79016aa 1340
c93b886f 1341;;; Window size change
e79016aa 1342
782fbf2a
CY
1343;; The functions in `window-size-change-functions' are called every
1344;; time a window in a frame changes size, most notably after the frame
1345;; has been resized. We call `follow-post-command-hook' for every
1346;; Follow mode buffer visible in any window in the resized frame.
e79016aa 1347;;
782fbf2a
CY
1348;; Since `follow-window-size-change' can be called indirectly from
1349;; `follow-post-command-hook' we have a potential infinite loop. To
1350;; avoid this, we simply do not do anything in this situation. The
1351;; variable `follow-inside-post-command-hook' contains information
1352;; about whether the execution actually is inside the
e79016aa
KH
1353;; post-command-hook or not.
1354
e79016aa
KH
1355(defun follow-window-size-change (frame)
1356 "Redraw all windows in FRAME, when in Follow mode."
782fbf2a
CY
1357 ;; Below, we call `post-command-hook'. Avoid an infloop.
1358 (unless follow-inside-post-command-hook
e79016aa
KH
1359 (let ((buffers '())
1360 (orig-window (selected-window))
1361 (orig-buffer (current-buffer))
1362 (orig-frame (selected-frame))
1363 windows
1364 buf)
1365 (select-frame frame)
1366 (unwind-protect
cb02be17 1367 (walk-windows
782fbf2a
CY
1368 (lambda (win)
1369 (setq buf (window-buffer win))
1370 (unless (memq buf buffers)
1371 (set-buffer buf)
1372 (when follow-mode
1373 (setq windows (follow-all-followers win))
1374 (if (not (memq orig-window windows))
1375 (follow-redisplay windows win)
1376 ;; Make sure we're redrawing around the selected
1377 ;; window.
1378 (select-window orig-window)
1379 (follow-post-command-hook)
1380 (setq orig-window (selected-window)))
1381 (setq buffers (cons buf buffers)))))
1382 'no-minibuf)
e79016aa
KH
1383 (select-frame orig-frame)
1384 (set-buffer orig-buffer)
1385 (select-window orig-window)))))
1386
782fbf2a 1387(add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)
e79016aa 1388
c93b886f 1389;;; Profile support
e79016aa
KH
1390
1391;; The following (non-evaluated) section can be used to
1392;; profile this package using `elp'.
1393;;
1394;; Invalid indentation on purpose!
1395
782fbf2a
CY
1396;; (setq elp-function-list
1397;; '(window-end
1398;; vertical-motion
1399;; follow-mode
1400;; follow-all-followers
1401;; follow-split-followers
1402;; follow-redisplay
1403;; follow-estimate-first-window-start
1404;; follow-calculate-first-window-start-from-above
1405;; follow-calculate-first-window-start-from-below
1406;; follow-calc-win-end
1407;; follow-calc-win-start
1408;; follow-pos-visible
1409;; follow-windows-start-end
1410;; follow-cache-valid-p
1411;; follow-select-if-visible
1412;; follow-select-if-visible-from-first
1413;; follow-windows-aligned-p
1414;; follow-point-visible-all-windows-p
1415;; follow-avoid-tail-recenter
1416;; follow-update-window-start
1417;; follow-post-command-hook))
fc779383 1418
e79016aa
KH
1419(provide 'follow)
1420
e79016aa
KH
1421;; /------------------------------------------------------------------------\
1422;; | "I [..] am rarely happier then when spending an entire day programming |
1423;; | my computer to perform automatically a task that it would otherwise |
1424;; | take me a good ten seconds to do by hand. Ten seconds, I tell myself, |
1425;; | is ten seconds. Time is valuable and ten seconds' worth of it is well |
1426;; | worth the investment of a day's happy activity working out a way to |
1427;; | save it". -- Douglas Adams, "Last Chance to See" |
1428;; \------------------------------------------------------------------------/
1429
1430;;; follow.el ends here