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