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