Use whole file-name-history'.
[bpt/emacs.git] / lisp / follow.el
CommitLineData
e8af40ee 1;;; follow.el --- synchronize windows showing the same buffer
7e58af4b 2
ba318903
PE
3;; Copyright (C) 1995-1997, 1999, 2001-2014 Free Software Foundation,
4;; Inc.
e79016aa 5
c86b6fd2 6;; Author: Anders Lindgren <andersl@andersl.com>
ec8d89ec 7;; Maintainer: FSF (Anders' email bounces, Sep 2005)
c86b6fd2 8;; Created: 1995-05-25
f5f727f8 9;; Keywords: display, window, minor-mode, convenience
e79016aa 10
dcde1278
RS
11;; This file is part of GNU Emacs.
12
eb3fa2cf 13;; GNU Emacs is free software: you can redistribute it and/or modify
e79016aa 14;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
e79016aa 17
dcde1278 18;; GNU Emacs is distributed in the hope that it will be useful,
e79016aa
KH
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
eb3fa2cf 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
e79016aa
KH
25
26;;; Commentary:
27
ff753438 28;; `Follow mode' is a minor mode for Emacs and XEmacs that
e79016aa
KH
29;; combines windows into one tall virtual window.
30;;
31;; The feeling of a "virtual window" has been accomplished by the use
32;; of two major techniques:
33;;
1ece6cdb 34;; * The windows always display adjacent sections of the buffer.
e79016aa 35;; This means that whenever one window is moved, all the
17f6d002 36;; others will follow. (Hence the name Follow mode.)
e79016aa 37;;
cb02be17
KH
38;; * Should the point (cursor) end up outside a window, another
39;; window displaying that point is selected, if possible. This
40;; makes it possible to walk between windows using normal cursor
e79016aa
KH
41;; movement commands.
42;;
ff753438 43;; Follow mode comes to its prime when a large screen and two
08a1dbe6 44;; side-by-side window are used. The user can, with the help of Follow
1ece6cdb
EZ
45;; mode, use two full-height windows as though they are one.
46;; Imagine yourself editing a large function, or section of text,
c8d05b03 47;; and being able to use 144 lines instead of the normal 72... (your
e79016aa
KH
48;; mileage may vary).
49
e79016aa 50;; To test this package, make sure `follow' is loaded, or will be
08a1dbe6 51;; autoloaded when activated (see below). Then do the following:
e79016aa 52;;
ff753438 53;; * Find your favorite file (preferably a long one).
e79016aa 54;;
ff753438
RS
55;; * Resize Emacs so that it will be wide enough for two full size
56;; columns. Delete the other windows and split the window with
57;; the commands `C-x 1 C-x 3'.
e79016aa
KH
58;;
59;; * Give the command:
60;; M-x follow-mode <RETURN>
61;;
62;; * Now the display should look something like (assuming the text "71"
63;; is on line 71):
64;;
65;; +----------+----------+
66;; |1 |73 |
67;; |2 |74 |
68;; |3 |75 |
cb02be17 69;; ... ...
e79016aa
KH
70;; |71 |143 |
71;; |72 |144 |
72;; +----------+----------+
73;;
74;; As you can see, the right-hand window starts at line 73, the line
08a1dbe6 75;; immediately below the end of the left-hand window. As long as
1ece6cdb 76;; `follow-mode' is active, the two windows will follow each other!
e79016aa
KH
77;;
78;; * Play around and enjoy! Scroll one window and watch the other.
08a1dbe6
SM
79;; Jump to the beginning or end. Press `Cursor down' at the last
80;; line of the left-hand window. Enter new lines into the
81;; text. Enter long lines spanning several lines, or several
e79016aa
KH
82;; windows.
83;;
84;; * Should you find `Follow' mode annoying, just type
cb02be17 85;; M-x follow-mode <RETURN>
e79016aa
KH
86;; to turn it off.
87
88
53964682 89;; The command `follow-delete-other-windows-and-split' maximizes the
e79016aa
KH
90;; visible area of the current buffer.
91;;
92;; I recommend adding it, and `follow-mode', to hotkeys in the global
93;; key map. To do so, add the following lines (replacing `[f7]' and
94;; `[f8]' with your favorite keys) to the init file:
95;;
e79016aa 96;; (global-set-key [f8] 'follow-mode)
e79016aa
KH
97;; (global-set-key [f7] 'follow-delete-other-windows-and-split)
98
99
e4920bc9 100;; There exist two system variables that control the appearance of
1ece6cdb
EZ
101;; lines wider than the window containing them. The default is to
102;; truncate long lines whenever a window isn't as wide as the frame.
e79016aa
KH
103;;
104;; To make sure lines are never truncated, please place the following
105;; lines in your init file:
106;;
107;; (setq truncate-lines nil)
108;; (setq truncate-partial-width-windows nil)
109
110
da6062e6 111;; The correct way to configure Follow mode, or any other mode for
1ece6cdb
EZ
112;; that matter, is to create one or more functions that do
113;; whatever you would like to do. These functions are then added to
e79016aa
KH
114;; a hook.
115;;
e79016aa
KH
116;; The keymap `follow-key-map' contains key bindings activated by
117;; `follow-mode'.
118;;
119;; Example:
120;; (add-hook 'follow-mode-hook 'my-follow-mode-hook)
121;;
122;; (defun my-follow-mode-hook ()
123;; (define-key follow-mode-map "\C-ca" 'your-favorite-function)
124;; (define-key follow-mode-map "\C-cb" 'another-function))
125
126
127;; Usage:
128;;
1ece6cdb
EZ
129;; To activate, issue the command "M-x follow-mode"
130;; and press Return. To deactivate, do it again.
e79016aa 131;;
ff753438
RS
132;; The following is a list of commands useful when follow-mode is active.
133;;
e79016aa 134;; follow-scroll-up C-c . C-v
17f6d002 135;; Scroll text in a Follow mode window chain up.
e79016aa
KH
136;;
137;; follow-scroll-down C-c . v
138;; Like `follow-scroll-up', but in the other direction.
139;;
140;; follow-delete-other-windows-and-split C-c . 1
1ece6cdb 141;; Maximize the visible area of the current buffer,
17f6d002 142;; and enter Follow mode. This is a very convenient
fe7a3057 143;; way to start Follow mode, hence we recommend that
1ece6cdb 144;; this command be added to the global keymap.
e79016aa
KH
145;;
146;; follow-recenter C-c . C-l
147;; Place the point in the center of the middle window,
148;; or a specified number of lines from either top or bottom.
149;;
150;; follow-switch-to-buffer C-c . b
151;; Switch buffer in all windows displaying the current buffer
152;; in this frame.
153;;
154;; follow-switch-to-buffer-all C-c . C-b
1ece6cdb 155;; Switch buffer in all windows in the selected frame.
e79016aa
KH
156;;
157;; follow-switch-to-current-buffer-all
158;; Show the current buffer in all windows on the current
159;; frame and turn on `follow-mode'.
160;;
161;; follow-first-window C-c . <
162;; Select the first window in the frame showing the same buffer.
163;;
164;; follow-last-window C-c . >
165;; Select the last window in the frame showing the same buffer.
166;;
167;; follow-next-window C-c . n
168;; Select the next window in the frame showing the same buffer.
169;;
170;; follow-previous-window C-c . p
171;; Select the previous window showing the same buffer.
172
173
174;; Well, it seems ok, but what if I really want to look at two different
175;; positions in the text? Here are two simple methods to use:
176;;
177;; 1) Use multiple frames; `follow' mode only affects windows displayed
5eba16a3 178;; in the same frame. (My apologies to you who can't use frames.)
e79016aa
KH
179;;
180;; 2) Bind `follow-mode' to key so you can turn it off whenever
08a1dbe6 181;; you want to view two locations. Of course, `follow' mode can
e79016aa
KH
182;; be reactivated by hitting the same key again.
183;;
184;; Example from my ~/.emacs:
185;; (global-set-key [f8] 'follow-mode)
186
cb02be17 187;; Implementation:
e79016aa 188;;
782fbf2a
CY
189;; The main method by which Follow mode aligns windows is via the
190;; function `follow-post-command-hook', which is run after each
191;; command. This "fixes up" the alignment of other windows which are
192;; showing the same Follow mode buffer, on the same frame as the
193;; selected window. It does not try to deal with buffers other than
194;; the buffer of the selected frame, or windows on other frames.
e79016aa 195;;
782fbf2a
CY
196;; Comint mode specially calls `follow-comint-scroll-to-bottom' on
197;; Follow mode buffers. This function scrolls the bottom-most window
198;; in a window chain and aligns the other windows accordingly. Follow
199;; mode adds a function to `compilation-filter-hook' to align
200;; compilation buffers.
e79016aa 201
e79016aa
KH
202;;; Code:
203
9271083a
JB
204(require 'easymenu)
205
c93b886f 206;;; Variables
e79016aa 207
4bef9110
SE
208(defgroup follow nil
209 "Synchronize windows showing the same buffer."
f5f727f8
DN
210 :group 'windows
211 :group 'convenience)
4bef9110 212
4bef9110 213(defcustom follow-mode-hook nil
eceb3266 214 "Normal hook run by `follow-mode'."
4bef9110
SE
215 :type 'hook
216 :group 'follow)
e79016aa 217
c93b886f 218;;; Keymap/Menu
e79016aa 219
08a1dbe6 220;; Define keys for the follow-mode minor mode map and replace some
782fbf2a
CY
221;; functions in the global map. All Follow mode special functions can
222;; be found on the `C-c .' prefix key.
08a1dbe6 223;;
782fbf2a
CY
224;; To change the prefix, redefine `follow-mode-prefix' before `follow'
225;; is loaded, or see the section on `follow-mode-hook' above for an
226;; example of how to bind the keys the way you like.
e79016aa 227
4bef9110 228(defcustom follow-mode-prefix "\C-c."
08a1dbe6 229 "Prefix key to use for follow commands in Follow mode.
e79016aa 230The value of this variable is checked as part of loading Follow mode.
4bef9110
SE
231After that, changing the prefix key requires manipulating keymaps."
232 :type 'string
233 :group 'follow)
234
08a1dbe6
SM
235(defvar follow-mode-map
236 (let ((mainmap (make-sparse-keymap))
237 (map (make-sparse-keymap)))
e79016aa
KH
238 (define-key map "\C-v" 'follow-scroll-up)
239 (define-key map "\M-v" 'follow-scroll-down)
240 (define-key map "v" 'follow-scroll-down)
241 (define-key map "1" 'follow-delete-other-windows-and-split)
242 (define-key map "b" 'follow-switch-to-buffer)
243 (define-key map "\C-b" 'follow-switch-to-buffer-all)
244 (define-key map "\C-l" 'follow-recenter)
245 (define-key map "<" 'follow-first-window)
246 (define-key map ">" 'follow-last-window)
247 (define-key map "n" 'follow-next-window)
248 (define-key map "p" 'follow-previous-window)
249
08a1dbe6 250 (define-key mainmap follow-mode-prefix map)
e79016aa 251
17f6d002 252 ;; Replace the standard `end-of-buffer', when in Follow mode. (I
ff753438 253 ;; don't see the point in trying to replace every function that
e79016aa
KH
254 ;; could be enhanced in Follow mode. End-of-buffer is a special
255 ;; case since it is very simple to define and it greatly enhances
256 ;; the look and feel of Follow mode.)
08a1dbe6 257 (define-key mainmap [remap end-of-buffer] 'follow-end-of-buffer)
e79016aa 258
782fbf2a
CY
259 (define-key mainmap [remap scroll-bar-toolkit-scroll] 'follow-scroll-bar-toolkit-scroll)
260 (define-key mainmap [remap scroll-bar-drag] 'follow-scroll-bar-drag)
261 (define-key mainmap [remap scroll-bar-scroll-up] 'follow-scroll-bar-scroll-up)
262 (define-key mainmap [remap scroll-bar-scroll-down] 'follow-scroll-bar-scroll-down)
87233a14 263 (define-key mainmap [remap mwheel-scroll] 'follow-mwheel-scroll)
782fbf2a 264
08a1dbe6
SM
265 mainmap)
266 "Minor mode keymap for Follow mode.")
e79016aa 267
7dcef48d
SM
268;; When the mode is not activated, only one item is visible to activate
269;; the mode.
270(defun follow-menu-filter (menu)
17f6d002 271 (if (bound-and-true-p follow-mode)
7dcef48d 272 menu
17f6d002 273 '(["Follow mode" follow-mode
7dcef48d
SM
274 :style toggle :selected follow-mode])))
275
7dcef48d
SM
276(easy-menu-add-item nil '("Tools")
277 '("Follow"
7dcef48d
SM
278 :filter follow-menu-filter
279 ["Scroll Up" follow-scroll-up follow-mode]
280 ["Scroll Down" follow-scroll-down follow-mode]
281 "--"
282 ["Delete Other Windows and Split" follow-delete-other-windows-and-split follow-mode]
283 "--"
284 ["Switch To Buffer" follow-switch-to-buffer follow-mode]
285 ["Switch To Buffer (all windows)" follow-switch-to-buffer-all follow-mode]
286 "--"
287 ["First Window" follow-first-window follow-mode]
288 ["Last Window" follow-last-window follow-mode]
289 ["Next Window" follow-next-window follow-mode]
290 ["Previous Window" follow-previous-window follow-mode]
291 "--"
292 ["Recenter" follow-recenter follow-mode]
293 "--"
294 ["Follow mode" follow-mode :style toggle :selected follow-mode]))
295
08a1dbe6
SM
296(defcustom follow-mode-line-text " Follow"
297 "Text shown in the mode line when Follow mode is active.
298Defaults to \" Follow\". Examples of other values
299are \" Fw\", or simply \"\"."
300 :type 'string
301 :group 'follow)
302
303(defcustom follow-auto nil
304 "Non-nil activates Follow mode whenever a file is loaded."
305 :type 'boolean
782fbf2a
CY
306 :group 'follow
307 :set (lambda (symbol value)
308 (if value
309 (add-hook 'find-file-hook 'follow-find-file-hook t)
310 (remove-hook 'find-file-hook 'follow-find-file-hook))
311 (set-default symbol value)))
08a1dbe6
SM
312
313(defvar follow-cache-command-list
d9d836b5 314 '(next-line previous-line forward-char backward-char right-char left-char)
08a1dbe6
SM
315 "List of commands that don't require recalculation.
316
317In order to be able to use the cache, a command should not change the
318contents of the buffer, nor should it change selected window or current
319buffer.
320
321The commands in this list are checked at load time.
322
323To mark other commands as suitable for caching, set the symbol
324property `follow-mode-use-cache' to non-nil.")
325
78f3273a
CY
326(defcustom follow-debug nil
327 "If non-nil, emit Follow mode debugging messages."
328 :type 'boolean
329 :group 'follow)
08a1dbe6
SM
330
331;; Internal variables:
332
333(defvar follow-internal-force-redisplay nil
334 "True when Follow mode should redisplay the windows.")
335
08a1dbe6
SM
336(defvar follow-active-menu nil
337 "The menu visible when Follow mode is active.")
338
72b255c7
PE
339(defvar follow-inactive-menu nil
340 "The menu visible when Follow mode is inactive.")
08a1dbe6
SM
341
342(defvar follow-inside-post-command-hook nil
343 "Non-nil when inside Follow modes `post-command-hook'.
344Used by `follow-window-size-change'.")
345
346(defvar follow-windows-start-end-cache nil
347 "Cache used by `follow-window-start-end'.")
348
c93b886f 349;;; Debug messages
08a1dbe6
SM
350
351;; This inline function must be as small as possible!
352;; Maybe we should define a macro that expands to nil if
353;; the variable is not set.
354
355(defsubst follow-debug-message (&rest args)
5eba16a3 356 "Like `message', but only active when `follow-debug' is non-nil."
08a1dbe6
SM
357 (if (and (boundp 'follow-debug) follow-debug)
358 (apply 'message args)))
e79016aa 359
c93b886f 360;;; Cache
f3bfa025 361
08a1dbe6
SM
362(dolist (cmd follow-cache-command-list)
363 (put cmd 'follow-mode-use-cache t))
f3bfa025 364
c93b886f 365;;; The mode
e79016aa
KH
366
367;;;###autoload
368(defun turn-on-follow-mode ()
08a1dbe6 369 "Turn on Follow mode. Please see the function `follow-mode'."
e79016aa
KH
370 (follow-mode 1))
371
372
373;;;###autoload
374(defun turn-off-follow-mode ()
08a1dbe6 375 "Turn off Follow mode. Please see the function `follow-mode'."
e79016aa
KH
376 (follow-mode -1))
377
08a1dbe6 378(put 'follow-mode 'permanent-local t)
e79016aa 379;;;###autoload
08a1dbe6 380(define-minor-mode follow-mode
06e21633
CY
381 "Toggle Follow mode.
382With a prefix argument ARG, enable Follow mode if ARG is
383positive, and disable it otherwise. If called from Lisp, enable
384the mode if ARG is omitted or nil.
e79016aa 385
06e21633
CY
386Follow mode is a minor mode that combines windows into one tall
387virtual window. This is accomplished by two main techniques:
e79016aa 388
cb02be17 389* The windows always displays adjacent sections of the buffer.
e79016aa 390 This means that whenever one window is moved, all the
17f6d002 391 others will follow. (Hence the name Follow mode.)
e79016aa 392
cb02be17
KH
393* Should the point (cursor) end up outside a window, another
394 window displaying that point is selected, if possible. This
395 makes it possible to walk between windows using normal cursor
e79016aa
KH
396 movement commands.
397
398Follow mode comes to its prime when used on a large screen and two
3eaf40f7 399side-by-side windows are used. The user can, with the help of Follow
e79016aa 400mode, use two full-height windows as though they would have been
8cd3480b 401one. Imagine yourself editing a large function, or section of text,
c8d05b03 402and being able to use 144 lines instead of the normal 72... (your
e79016aa
KH
403mileage may vary).
404
405To split one large window into two side-by-side windows, the commands
2d197ffb 406`\\[split-window-right]' or \
e79016aa
KH
407`M-x follow-delete-other-windows-and-split' can be used.
408
8cd3480b 409Only windows displayed in the same frame follow each other.
e79016aa 410
eceb3266 411This command runs the normal hook `follow-mode-hook'.
e79016aa
KH
412
413Keys specific to Follow mode:
414\\{follow-mode-map}"
08a1dbe6 415 :keymap follow-mode-map
782fbf2a
CY
416 (if follow-mode
417 (progn
418 (add-hook 'compilation-filter-hook 'follow-align-compilation-windows t t)
419 (add-hook 'post-command-hook 'follow-post-command-hook t)
420 (add-hook 'window-size-change-functions 'follow-window-size-change t))
421 ;; Remove globally-installed hook functions only if there is no
422 ;; other Follow mode buffer.
423 (let ((buffers (buffer-list))
424 following)
425 (while (and (not following) buffers)
426 (setq following (buffer-local-value 'follow-mode (car buffers))
427 buffers (cdr buffers)))
428 (unless following
429 (remove-hook 'post-command-hook 'follow-post-command-hook)
430 (remove-hook 'window-size-change-functions 'follow-window-size-change)))
431 (remove-hook 'compilation-filter-hook 'follow-align-compilation-windows t)))
e79016aa
KH
432
433(defun follow-find-file-hook ()
17f6d002 434 "Find-file hook for Follow mode. See the variable `follow-auto'."
782fbf2a 435 (if follow-auto (follow-mode 1)))
e79016aa 436
c93b886f 437;;; User functions
e79016aa 438
c93b886f 439;;; Scroll
e79016aa 440
17f6d002 441;; `scroll-up' and `-down', but for windows in Follow mode.
e79016aa 442;;
4c36be58 443;; Almost like the real thing, except when the cursor ends up outside
e79016aa 444;; the top or bottom... In our case however, we end up outside the
40ba43b4 445;; window and hence we are recentered. Should we let `recenter' handle
e79016aa
KH
446;; the point position we would never leave the selected window. To do
447;; it ourselves we would need to do our own redisplay, which is easier
448;; said than done. (Why didn't I do a real display abstraction from
449;; the beginning?)
450;;
451;; We must sometimes set `follow-internal-force-redisplay', otherwise
452;; our post-command-hook will move our windows back into the old
453;; position... (This would also be corrected if we would have had a
454;; good redisplay abstraction.)
455
456(defun follow-scroll-up (&optional arg)
17f6d002 457 "Scroll text in a Follow mode window chain up.
e79016aa
KH
458
459If called with no ARG, the `next-screen-context-lines' last lines of
460the bottom window in the chain will be visible in the top window.
461
462If called with an argument, scroll ARG lines up.
463Negative ARG means scroll downward.
464
17f6d002 465Works like `scroll-up' when not in Follow mode."
e79016aa 466 (interactive "P")
782fbf2a 467 (cond ((not follow-mode)
e79016aa
KH
468 (scroll-up arg))
469 (arg
470 (save-excursion (scroll-up arg))
471 (setq follow-internal-force-redisplay t))
472 (t
473 (let* ((windows (follow-all-followers))
474 (end (window-end (car (reverse windows)))))
475 (if (eq end (point-max))
476 (signal 'end-of-buffer nil)
477 (select-window (car windows))
ff753438
RS
478 ;; `window-end' might return nil.
479 (if end
480 (goto-char end))
e79016aa
KH
481 (vertical-motion (- next-screen-context-lines))
482 (set-window-start (car windows) (point)))))))
483
484
485(defun follow-scroll-down (&optional arg)
17f6d002 486 "Scroll text in a Follow mode window chain down.
e79016aa
KH
487
488If called with no ARG, the `next-screen-context-lines' top lines of
489the top window in the chain will be visible in the bottom window.
490
491If called with an argument, scroll ARG lines down.
492Negative ARG means scroll upward.
493
17f6d002 494Works like `scroll-up' when not in Follow mode."
e79016aa 495 (interactive "P")
782fbf2a 496 (cond ((not follow-mode)
e79016aa
KH
497 (scroll-up arg))
498 (arg
499 (save-excursion (scroll-down arg)))
500 (t
501 (let* ((windows (follow-all-followers))
502 (win (car (reverse windows)))
503 (start (window-start (car windows))))
504 (if (eq start (point-min))
505 (signal 'beginning-of-buffer nil)
506 (select-window win)
507 (goto-char start)
cb02be17 508 (vertical-motion (- (- (window-height win)
99dfcc0d 509 (if header-line-format 2 1)
e79016aa
KH
510 next-screen-context-lines)))
511 (set-window-start win (point))
512 (goto-char start)
513 (vertical-motion (- next-screen-context-lines 1))
514 (setq follow-internal-force-redisplay t))))))
515
782fbf2a
CY
516(declare-function comint-adjust-point "comint" (window))
517(defvar comint-scroll-show-maximum-output)
518
45fdb482 519(defun follow-comint-scroll-to-bottom (&optional _window)
782fbf2a
CY
520 "Scroll the bottom-most window in the current Follow chain.
521This is to be called by `comint-postoutput-scroll-to-bottom'."
522 (let* ((buffer (current-buffer))
523 (selected (selected-window))
524 (is-selected (eq (window-buffer) buffer))
525 some-window)
526 (when (or is-selected
527 (setq some-window (get-buffer-window)))
528 (let* ((pos (progn (comint-adjust-point nil) (point)))
529 (win (if is-selected
530 selected
531 (car (last (follow-all-followers some-window))))))
532 (select-window win)
533 (goto-char pos)
534 (setq follow-windows-start-end-cache nil)
535 (follow-adjust-window win pos)
536 (unless is-selected
537 (select-window selected)
538 (set-buffer buffer))))))
539
540(defun follow-align-compilation-windows ()
541 "Align the windows of the current Follow mode buffer.
542This is to be called from `compilation-filter-hook'."
543 (let ((buffer (current-buffer))
544 (win (get-buffer-window))
545 (selected (selected-window)))
546 (when (and follow-mode (waiting-for-user-input-p) win)
547 (let ((windows (follow-all-followers win)))
548 (unless (eq (window-buffer selected) buffer)
549 (setq win (car windows))
550 (select-window win))
551 (follow-redisplay windows win t)
552 (setq follow-windows-start-end-cache nil)
553 (unless (eq selected win)
554 (select-window selected)
555 (set-buffer buffer))))))
556
c93b886f 557;;; Buffer
e79016aa
KH
558
559;;;###autoload
560(defun follow-delete-other-windows-and-split (&optional arg)
17f6d002 561 "Create two side by side windows and enter Follow mode.
e79016aa
KH
562
563Execute this command to display as much as possible of the text
cb02be17 564in the selected window. All other windows, in the current
e79016aa 565frame, are deleted and the selected window is split in two
17f6d002 566side-by-side windows. Follow mode is activated, hence the
e79016aa
KH
567two windows always will display two successive pages.
568\(If one window is moved, the other one will follow.)
569
8cd3480b 570If ARG is positive, the leftmost window is selected. If negative,
e79016aa 571the rightmost is selected. If ARG is nil, the leftmost window is
782fbf2a 572selected if the original window is the first one in the frame."
e79016aa 573 (interactive "P")
cb02be17 574 (let ((other (or (and (null arg)
e79016aa 575 (not (eq (selected-window)
12b4c0ea 576 (frame-first-window))))
e79016aa
KH
577 (and arg
578 (< (prefix-numeric-value arg) 0))))
579 (start (window-start)))
580 (delete-other-windows)
2d197ffb 581 (split-window-right)
cb02be17 582 (if other
e79016aa
KH
583 (progn
584 (other-window 1)
585 (set-window-start (selected-window) start)
586 (setq follow-internal-force-redisplay t)))
587 (follow-mode 1)))
588
589(defun follow-switch-to-buffer (buffer)
17f6d002 590 "Show BUFFER in all windows in the current Follow mode window chain."
e79016aa
KH
591 (interactive "BSwitch to Buffer: ")
592 (let ((orig-window (selected-window))
593 (windows (follow-all-followers)))
594 (while windows
595 (select-window (car windows))
596 (switch-to-buffer buffer)
597 (setq windows (cdr windows)))
598 (select-window orig-window)))
599
600
601(defun follow-switch-to-buffer-all (&optional buffer)
602 "Show BUFFER in all windows on this frame.
603Defaults to current buffer."
cb02be17 604 (interactive (list (read-buffer "Switch to Buffer: "
e79016aa
KH
605 (current-buffer))))
606 (or buffer (setq buffer (current-buffer)))
607 (let ((orig-window (selected-window)))
782fbf2a
CY
608 (walk-windows (lambda (win)
609 (select-window win)
610 (switch-to-buffer buffer))
611 'no-minibuf)
e79016aa
KH
612 (select-window orig-window)
613 (follow-redisplay)))
614
615
616(defun follow-switch-to-current-buffer-all ()
782fbf2a 617 "Show current buffer in all windows on this frame, and enter Follow mode."
e79016aa 618 (interactive)
782fbf2a
CY
619 (unless follow-mode
620 (follow-mode 1))
e79016aa
KH
621 (follow-switch-to-buffer-all))
622
c93b886f 623;;; Movement
e79016aa 624
fc779383 625;; Note, these functions are not very useful, at least not unless you
e79016aa
KH
626;; rebind the rather cumbersome key sequence `C-c . p'.
627
628(defun follow-next-window ()
629 "Select the next window showing the same buffer."
630 (interactive)
631 (let ((succ (cdr (follow-split-followers (follow-all-followers)))))
632 (if succ
633 (select-window (car succ))
634 (error "%s" "No more windows"))))
635
636
637(defun follow-previous-window ()
638 "Select the previous window showing the same buffer."
639 (interactive)
640 (let ((pred (car (follow-split-followers (follow-all-followers)))))
641 (if pred
642 (select-window (car pred))
643 (error "%s" "No more windows"))))
644
645
646(defun follow-first-window ()
647 "Select the first window in the frame showing the same buffer."
648 (interactive)
649 (select-window (car (follow-all-followers))))
650
651
652(defun follow-last-window ()
653 "Select the last window in the frame showing the same buffer."
654 (interactive)
655 (select-window (car (reverse (follow-all-followers)))))
656
c93b886f 657;;; Redraw
e79016aa
KH
658
659(defun follow-recenter (&optional arg)
cb02be17
KH
660 "Recenter the middle window around point.
661Rearrange all other windows around the middle window.
e79016aa
KH
662
663With a positive argument, place the current line ARG lines
8cd3480b
JB
664from the top. With a negative argument, place it -ARG lines
665from the bottom."
e79016aa
KH
666 (interactive "P")
667 (if arg
668 (let ((p (point))
669 (arg (prefix-numeric-value arg)))
670 (if (>= arg 0)
671 ;; Recenter relative to the top.
672 (progn
673 (follow-first-window)
674 (goto-char p)
675 (recenter arg))
676 ;; Recenter relative to the bottom.
677 (follow-last-window)
678 (goto-char p)
679 (recenter arg)
680 ;; Otherwise, our post-command-hook will move the window
681 ;; right back.
682 (setq follow-internal-force-redisplay t)))
683 ;; Recenter in the middle.
684 (let* ((dest (point))
685 (windows (follow-all-followers))
686 (win (nth (/ (- (length windows) 1) 2) windows)))
687 (select-window win)
688 (goto-char dest)
782fbf2a 689 (recenter))))
e79016aa
KH
690
691
692(defun follow-redraw ()
693 "Arrange windows displaying the same buffer in successor order.
694This function can be called even if the buffer is not in Follow mode.
695
696Hopefully, there should be no reason to call this function when in
697Follow mode since the windows should always be aligned."
698 (interactive)
699 (sit-for 0)
700 (follow-redisplay))
701
c93b886f 702;;; End of buffer
e79016aa
KH
703
704(defun follow-end-of-buffer (&optional arg)
17f6d002 705 "Move point to the end of the buffer, Follow mode style.
e79016aa
KH
706
707If the end is not visible, it will be displayed in the last possible
17f6d002 708window in the Follow mode window chain.
e79016aa 709
cb02be17 710The mark is left at the previous position. With arg N, put point N/10
e79016aa
KH
711of the way from the true end."
712 (interactive "P")
713 (let ((followers (follow-all-followers))
714 (pos (point)))
715 (cond (arg
716 (select-window (car (reverse followers))))
cb02be17 717 ((follow-select-if-end-visible
e79016aa
KH
718 (follow-windows-start-end followers)))
719 (t
720 (select-window (car (reverse followers)))))
721 (goto-char pos)
2a4b9211
RS
722 (with-no-warnings
723 (end-of-buffer arg))))
e79016aa 724
c93b886f 725;;; Display
e79016aa 726
782fbf2a
CY
727(defun follow--window-sorter (w1 w2)
728 "Sorting function for W1 and W2 based on their positions.
729Return non-nil if W1 is above W2; if their top-lines
730are at the same position, return non-nil if W1 is to the
731left of W2."
732 (let* ((edge-1 (window-pixel-edges w1))
733 (edge-2 (window-pixel-edges w2))
734 (y1 (nth 1 edge-1))
735 (y2 (nth 1 edge-2)))
736 (if (= y1 y2)
737 (< (car edge-1) (car edge-2))
738 (< y1 y2))))
739
740(defun follow-all-followers (&optional win)
741 "Return all windows displaying the same buffer as the WIN.
742The list is sorted with topmost and leftmost windows first, and
743contains only windows in the same frame as WIN. If WIN is nil,
744it defaults to the selected window."
745 (unless (window-live-p win)
746 (setq win (selected-window)))
747 (let ((buffer (window-buffer win))
748 windows)
749 (dolist (w (window-list (window-frame win) 'no-minibuf win))
750 (if (eq (window-buffer w) buffer)
751 (push w windows)))
752 (sort windows 'follow--window-sorter)))
e79016aa
KH
753
754(defun follow-split-followers (windows &optional win)
782fbf2a 755 "Split WINDOWS into two sets: predecessors and successors.
cb02be17 756Return `(PRED . SUCC)' where `PRED' and `SUCC' are ordered starting
e79016aa 757from the selected window."
cb02be17 758 (or win
e79016aa
KH
759 (setq win (selected-window)))
760 (let ((pred '()))
761 (while (not (eq (car windows) win))
762 (setq pred (cons (car windows) pred))
763 (setq windows (cdr windows)))
764 (cons pred (cdr windows))))
765
e79016aa 766(defun follow-calc-win-end (&optional win)
c93b886f
CY
767 "Calculate the end position for window WIN.
768Return (END-POS END-OF-BUFFER).
769
770Actually, the position returned is the start of the line after
771the last fully-visible line in WIN. If WIN is nil, the selected
772window is used."
773 (let* ((win (or win (selected-window)))
774 (edges (window-inside-pixel-edges win))
775 (ht (- (nth 3 edges) (nth 1 edges)))
776 (last-line-pos (posn-point (posn-at-x-y 0 (1- ht) win))))
777 (if (pos-visible-in-window-p last-line-pos win)
778 (let ((end (window-end win t)))
779 (list end (= end (point-max))))
780 (list last-line-pos nil))))
e79016aa 781
e79016aa 782(defun follow-calc-win-start (windows pos win)
782fbf2a
CY
783 "Determine the start of window WIN in a Follow mode window chain.
784WINDOWS is a list of chained windows, and POS is the starting
785position for the first window in the list. If WIN is nil, return
786the point below all windows."
787 (while (and windows (not (eq (car windows) win)))
788 (let ((old-start (window-start (car windows))))
789 ;; Can't use `save-window-excursion' since it triggers a redraw.
e79016aa 790 (set-window-start (car windows) pos 'noforce)
4484b97d 791 (setq pos (car (follow-calc-win-end (car windows))))
782fbf2a
CY
792 (set-window-start (car windows) old-start 'noforce)
793 (setq windows (cdr windows))))
794 pos)
e79016aa 795
f3bfa025
KH
796;; The result from `follow-windows-start-end' is cached when using
797;; a handful simple commands, like cursor movement commands.
798
799(defsubst follow-cache-valid-p (windows)
800 "Test if the cached value of `follow-windows-start-end' can be used.
801Note that this handles the case when the cache has been set to nil."
802 (let ((res t)
803 (cache follow-windows-start-end-cache))
804 (while (and res windows cache)
805 (setq res (and (eq (car windows)
806 (car (car cache)))
807 (eq (window-start (car windows))
808 (car (cdr (car cache))))))
809 (setq windows (cdr windows))
810 (setq cache (cdr cache)))
811 (and res (null windows) (null cache))))
812
e79016aa 813(defun follow-windows-start-end (windows)
782fbf2a 814 "Return a list of (WIN START END BUFFER-END-P) for window list WINDOWS."
f3bfa025
KH
815 (if (follow-cache-valid-p windows)
816 follow-windows-start-end-cache
30bf4750
CY
817 (let ((orig-win (selected-window))
818 win-start-end)
819 (dolist (w windows)
820 (select-window w)
821 (push (cons w (cons (window-start) (follow-calc-win-end)))
822 win-start-end))
f3bfa025 823 (select-window orig-win)
30bf4750 824 (setq follow-windows-start-end-cache (nreverse win-start-end)))))
f3bfa025 825
f3bfa025 826(defsubst follow-pos-visible (pos win win-start-end)
e79016aa
KH
827 "Non-nil when POS is visible in WIN."
828 (let ((wstart-wend-bend (cdr (assq win win-start-end))))
829 (and (>= pos (car wstart-wend-bend))
30bf4750 830 (or (< pos (cadr wstart-wend-bend))
e79016aa
KH
831 (nth 2 wstart-wend-bend)))))
832
833
30bf4750 834;; By `aligned' we mean that for all adjacent windows, the end of the
e79016aa
KH
835;; first is equal with the start of the successor. The first window
836;; should start at a full screen line.
837
f3bfa025 838(defsubst follow-windows-aligned-p (win-start-end)
782fbf2a
CY
839 "Non-nil if the follower windows are aligned.
840The argument, WIN-START-END, should be a list of the form
841returned by `follow-windows-start-end'."
842 (let ((result t))
843 (while (and win-start-end result)
844 (if (cdr win-start-end)
845 (setq result (eq (nth 2 (car win-start-end))
846 (nth 1 (cadr win-start-end)))))
e79016aa 847 (setq win-start-end (cdr win-start-end)))
782fbf2a 848 result))
e79016aa
KH
849
850;; Check if the point is visible in all windows. (So that
851;; no one will be recentered.)
852
853(defun follow-point-visible-all-windows-p (win-start-end)
8cd3480b 854 "Non-nil when the `window-point' is visible in all windows."
e79016aa
KH
855 (let ((res t))
856 (while (and res win-start-end)
f3bfa025
KH
857 (setq res (follow-pos-visible (window-point (car (car win-start-end)))
858 (car (car win-start-end))
859 win-start-end))
e79016aa
KH
860 (setq win-start-end (cdr win-start-end)))
861 res))
862
863
5eba16a3 864;; Make sure WIN always starts at the beginning of a whole screen
e79016aa
KH
865;; line. If WIN is not aligned the start is updated which probably
866;; will lead to a redisplay of the screen later on.
867;;
868;; This is used with the first window in a follow chain. The reason
869;; is that we want to detect that the point is outside the window.
870;; (Without the update, the start of the window will move as the
871;; user presses BackSpace, and the other window redisplay routines
872;; will move the start of the window in the wrong direction.)
873
874(defun follow-update-window-start (win)
875 "Make sure that the start of WIN starts at a full screen line."
876 (save-excursion
877 (goto-char (window-start win))
4484b97d 878 (unless (bolp)
e79016aa 879 (vertical-motion 0 win)
4484b97d 880 (unless (eq (point) (window-start win))
e79016aa
KH
881 (vertical-motion 1 win)
882 (set-window-start win (point) 'noforce)))))
883
e79016aa
KH
884(defun follow-select-if-visible (dest win-start-end)
885 "Select and return a window, if DEST is visible in it.
886Return the selected window."
45fdb482 887 (let (win wse)
e79016aa 888 (while (and (not win) win-start-end)
ff753438 889 ;; Don't select a window that was just moved. This makes it
782fbf2a
CY
890 ;; possible to later select the last window after a
891 ;; `end-of-buffer' command.
892 (setq wse (car win-start-end))
893 (when (follow-pos-visible dest (car wse) win-start-end)
45fdb482 894 (setq win (car 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 1085 (let* ((orig-buffer (current-buffer))
12b4c0ea 1086 (top (frame-first-window))
782fbf2a
CY
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.
2bede2ed
XF
1263
1264 ;; FIXME: Why not use `use-region-p' here?
782fbf2a
CY
1265 (when (region-active-p)
1266 (follow-maximize-region
1267 (selected-window) windows win-start-end)))
1268
1269 ;; Whether or not the buffer was in follow mode, update windows
1270 ;; displaying the tail so that Emacs won't recenter them.
1271 (follow-avoid-tail-recenter))))
e79016aa 1272
c93b886f 1273;;; The region
e79016aa
KH
1274
1275;; Tries to make the highlighted area representing the region look
cb02be17 1276;; good when spanning several windows.
e79016aa
KH
1277;;
1278;; Not perfect, as the point can't be placed at window end, only at
cb02be17 1279;; end-1. This will highlight a little bit in windows above
e79016aa
KH
1280;; the current.
1281
1282(defun follow-maximize-region (win windows win-start-end)
cb02be17 1283 "Make a highlighted region stretching multiple windows look good."
e79016aa
KH
1284 (let* ((all (follow-split-followers windows win))
1285 (pred (car all))
1286 (succ (cdr all))
1287 data)
1288 (while pred
1289 (setq data (assq (car pred) win-start-end))
1290 (set-window-point (car pred) (max (nth 1 data) (- (nth 2 data) 1)))
1291 (setq pred (cdr pred)))
1292 (while succ
1293 (set-window-point (car succ) (nth 1 (assq (car succ) win-start-end)))
1294 (setq succ (cdr succ)))))
1295
c93b886f 1296;;; Scroll bar
e79016aa
KH
1297
1298;;;; Scroll-bar support code.
1299
782fbf2a
CY
1300;; This handles the case where the user drags the scroll bar of a
1301;; non-selected window whose buffer is in Follow mode.
1302
7e58af4b
GM
1303(declare-function scroll-bar-toolkit-scroll "scroll-bar" (event))
1304(declare-function scroll-bar-drag "scroll-bar" (event))
1305(declare-function scroll-bar-scroll-up "scroll-bar" (event))
1306(declare-function scroll-bar-scroll-down "scroll-bar" (event))
1307(declare-function mwheel-scroll "mwheel" (event))
1308
782fbf2a
CY
1309(defun follow-scroll-bar-toolkit-scroll (event)
1310 (interactive "e")
1311 (scroll-bar-toolkit-scroll event)
1312 (follow-redraw-after-event event))
1313
1314(defun follow-scroll-bar-drag (event)
1315 (interactive "e")
1316 (scroll-bar-drag event)
1317 (follow-redraw-after-event event))
1318
1319(defun follow-scroll-bar-scroll-up (event)
1320 (interactive "e")
1321 (scroll-bar-scroll-up event)
1322 (follow-redraw-after-event event))
1323
1324(defun follow-scroll-bar-scroll-down (event)
1325 (interactive "e")
1326 (scroll-bar-scroll-down event)
1327 (follow-redraw-after-event event))
1328
87233a14
CY
1329(defun follow-mwheel-scroll (event)
1330 (interactive "e")
1331 (mwheel-scroll event)
1332 (follow-redraw-after-event event))
1333
782fbf2a 1334(defun follow-redraw-after-event (event)
87233a14
CY
1335 "Re-align the Follow mode windows affected by EVENT."
1336 (let* ((window (nth 0 (event-end event)))
1337 (buffer (window-buffer window))
1338 (orig-win (selected-window)))
1339 (when (and (buffer-local-value 'follow-mode buffer)
782fbf2a
CY
1340 ;; Ignore the case where we scroll the selected window;
1341 ;; that is handled by the post-command hook function.
1342 (not (eq window (selected-window))))
1343 (select-window window)
1344 (follow-redisplay)
87233a14
CY
1345 (unless (eq (window-buffer orig-win) buffer)
1346 (select-window orig-win)))))
e79016aa 1347
c93b886f 1348;;; Window size change
e79016aa 1349
782fbf2a
CY
1350;; The functions in `window-size-change-functions' are called every
1351;; time a window in a frame changes size, most notably after the frame
1352;; has been resized. We call `follow-post-command-hook' for every
1353;; Follow mode buffer visible in any window in the resized frame.
e79016aa 1354;;
782fbf2a
CY
1355;; Since `follow-window-size-change' can be called indirectly from
1356;; `follow-post-command-hook' we have a potential infinite loop. To
1357;; avoid this, we simply do not do anything in this situation. The
1358;; variable `follow-inside-post-command-hook' contains information
1359;; about whether the execution actually is inside the
e79016aa
KH
1360;; post-command-hook or not.
1361
e79016aa
KH
1362(defun follow-window-size-change (frame)
1363 "Redraw all windows in FRAME, when in Follow mode."
782fbf2a
CY
1364 ;; Below, we call `post-command-hook'. Avoid an infloop.
1365 (unless follow-inside-post-command-hook
e79016aa
KH
1366 (let ((buffers '())
1367 (orig-window (selected-window))
1368 (orig-buffer (current-buffer))
1369 (orig-frame (selected-frame))
1370 windows
1371 buf)
1372 (select-frame frame)
1373 (unwind-protect
cb02be17 1374 (walk-windows
782fbf2a
CY
1375 (lambda (win)
1376 (setq buf (window-buffer win))
1377 (unless (memq buf buffers)
1378 (set-buffer buf)
1379 (when follow-mode
1380 (setq windows (follow-all-followers win))
1381 (if (not (memq orig-window windows))
1382 (follow-redisplay windows win)
1383 ;; Make sure we're redrawing around the selected
1384 ;; window.
1385 (select-window orig-window)
1386 (follow-post-command-hook)
1387 (setq orig-window (selected-window)))
1388 (setq buffers (cons buf buffers)))))
1389 'no-minibuf)
e79016aa
KH
1390 (select-frame orig-frame)
1391 (set-buffer orig-buffer)
1392 (select-window orig-window)))))
1393
782fbf2a 1394(add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)
e79016aa 1395
c93b886f 1396;;; Profile support
e79016aa
KH
1397
1398;; The following (non-evaluated) section can be used to
1399;; profile this package using `elp'.
1400;;
1401;; Invalid indentation on purpose!
1402
782fbf2a
CY
1403;; (setq elp-function-list
1404;; '(window-end
1405;; vertical-motion
1406;; follow-mode
1407;; follow-all-followers
1408;; follow-split-followers
1409;; follow-redisplay
1410;; follow-estimate-first-window-start
1411;; follow-calculate-first-window-start-from-above
1412;; follow-calculate-first-window-start-from-below
1413;; follow-calc-win-end
1414;; follow-calc-win-start
1415;; follow-pos-visible
1416;; follow-windows-start-end
1417;; follow-cache-valid-p
1418;; follow-select-if-visible
1419;; follow-select-if-visible-from-first
1420;; follow-windows-aligned-p
1421;; follow-point-visible-all-windows-p
1422;; follow-avoid-tail-recenter
1423;; follow-update-window-start
1424;; follow-post-command-hook))
fc779383 1425
e79016aa
KH
1426(provide 'follow)
1427
e79016aa
KH
1428;; /------------------------------------------------------------------------\
1429;; | "I [..] am rarely happier then when spending an entire day programming |
1430;; | my computer to perform automatically a task that it would otherwise |
1431;; | take me a good ten seconds to do by hand. Ten seconds, I tell myself, |
1432;; | is ten seconds. Time is valuable and ten seconds' worth of it is well |
1433;; | worth the investment of a day's happy activity working out a way to |
1434;; | save it". -- Douglas Adams, "Last Chance to See" |
1435;; \------------------------------------------------------------------------/
1436
1437;;; follow.el ends here