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