Mercurial > emacs
changeset 14578:55d804f97ffc
Initial revision
author | Karl Heuer <kwzh@gnu.org> |
---|---|
date | Fri, 16 Feb 1996 01:01:16 +0000 |
parents | 39a431eafb34 |
children | a81dd4e9603a |
files | lisp/follow.el |
diffstat | 1 files changed, 2349 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/follow.el Fri Feb 16 01:01:16 1996 +0000 @@ -0,0 +1,2349 @@ +;;; follow.el --- Minor mode, Synchronize windows showing the same buffer. + +;; Copyright (C) 1995, 1996 Free Software Foundation, Inc. + +;; Author: Anders Lindgren <andersl@csd.uu.se> +;; Maintainer: Anders Lindgren <andersl@csd.uu.se> +;; Created: 25 May 1995 +;; Version: 1.5 +;; Keywords: display, window, minor-mode +;; Date: 22 Jan 1996 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: + +;;{{{ Documentation + +;; `Follow mode' is a minor mode for Emacs 19 and XEmacs which +;; combines windows into one tall virtual window. +;; +;; The feeling of a "virtual window" has been accomplished by the use +;; of two major techniques: +;; +;; * The windows always displays adjacent sections of the buffer. +;; This means that whenever one window is moved, all the +;; others will follow. (Hence the name Follow Mode.) +;; +;; * Should the point (cursor) end up outside a window, another +;; window displaying that point is selected, if possible. This +;; makes it possible to walk between windows using normal cursor +;; movement commands. +;; +;; Follow mode comes to its prime when used on a large screen and two +;; side-by-side window are used. The user can, with the help of Follow +;; mode, use two full-height windows as though they would have been +;; one. Imagine yourself editing a large function, or section of text, +;; and beeing able to use 144 lines instead of the normal 72... (your +;; mileage may vary). + +;; The latest version, and a demonstration, are avaiable at: +;; +;; ftp://ftp.csd.uu.se/pub/users/andersl/emacs/follow.el +;; http://www.csd.uu.se/~andersl/follow.shtml + +;; `Follow mode' can be used together with Emacs 19 and XEmacs. +;; It has been tested together with Emacs 19.27, 19.28, 19.29, +;; 19.30, XEmacs 19.12, and 19.13. + + +;; To test this package, make sure `follow' is loaded, or will be +;; autoloaded when activated (see below). Then do the following: +;; +;; * Find your favorite file (preferably a long one.) +;; +;; * Resize Emacs so that it will be wide enough for two full sized +;; columns. Delete the other windows and split with the commands +;; `C-x 1 C-x 3'. +;; +;; * Give the command: +;; M-x follow-mode <RETURN> +;; +;; * Now the display should look something like (assuming the text "71" +;; is on line 71): +;; +;; +----------+----------+ +;; |1 |73 | +;; |2 |74 | +;; |3 |75 | +;; ... ... +;; |71 |143 | +;; |72 |144 | +;; +----------+----------+ +;; +;; As you can see, the right-hand window starts at line 73, the line +;; immediately below the end of the left-hand window. As long as +;; `follow-mode' is active, the two windows will follow eachother! +;; +;; * Play around and enjoy! Scroll one window and watch the other. +;; Jump to the beginning or end. Press `Cursor down' at the last +;; line of the left-hand window. Enter new lines into the +;; text. Enter long lines spanning several lines, or several +;; windows. +;; +;; * Should you find `Follow' mode annoying, just type +;; M-x follow-mode <RETURN> +;; to turn it off. + + +;; Installation: +;; +;; To fully install this, add this file to your Emacs Lisp directory and +;; compile it with M-x byte-compile-file. Then add the following to the +;; appropriate init file (normally your `~/.emacs' file): +;; +;; (autoload 'follow-mode "follow" +;; "Synchronize windows showing the same buffer, minor mode." t) + + +;; The command `follow-delete-other-windows-and-split' maximises the +;; visible area of the current buffer. +;; +;; I recommend adding it, and `follow-mode', to hotkeys in the global +;; key map. To do so, add the following lines (replacing `[f7]' and +;; `[f8]' with your favorite keys) to the init file: +;; +;; (autoload 'follow-mode "follow" +;; "Synchronize windows showing the same buffer, minor mode." t) +;; (global-set-key [f8] 'follow-mode) +;; +;; (autoload 'follow-delete-other-windows-and-split "follow" +;; "Delete other windows, split the frame in two, and enter Follow Mode." t) +;; (global-set-key [f7] 'follow-delete-other-windows-and-split) + + +;; There exists two system variables which controls the appearence of +;; lines which are wider than the window containing them. The default +;; is to truncate long lines if a window isn't as wide as the frame. +;; +;; To make sure lines are never truncated, please place the following +;; lines in your init file: +;; +;; (setq truncate-lines nil) +;; (setq truncate-partial-width-windows nil) + + +;; The correct way to cofigurate Follow mode, or any other mode for +;; that matter, is to create one (or more) function which does +;; whatever you would like to do. The function is then added to +;; a hook. +;; +;; When `Follow' mode is activated, functions stored in the hook +;; `follow-mode-hook' are called. When it is deactivated +;; `follow-mode-off-hook' is runed. +;; +;; The keymap `follow-key-map' contains key bindings activated by +;; `follow-mode'. +;; +;; Example: +;; (add-hook 'follow-mode-hook 'my-follow-mode-hook) +;; +;; (defun my-follow-mode-hook () +;; (define-key follow-mode-map "\C-ca" 'your-favorite-function) +;; (define-key follow-mode-map "\C-cb" 'another-function)) + + +;; Usage: +;; +;; To activate give the command: M-x follow-mode +;; and press return. To deactivate, do it again. +;; +;; Some special commands have been developed to make life even easier: +;; follow-scroll-up C-c . C-v +;; Scroll text in a Follow Mode window chain up. +;; +;; follow-scroll-down C-c . v +;; Like `follow-scroll-up', but in the other direction. +;; +;; follow-delete-other-windows-and-split C-c . 1 +;; Maximise the visible area of the current buffer, +;; and enter Follow Mode. This is a very convenient +;; way to start Follow Mode, hence it is recomended +;; that this command is added to the global keymap. +;; +;; follow-recenter C-c . C-l +;; Place the point in the center of the middle window, +;; or a specified number of lines from either top or bottom. +;; +;; follow-switch-to-buffer C-c . b +;; Switch buffer in all windows displaying the current buffer +;; in this frame. +;; +;; follow-switch-to-buffer-all C-c . C-b +;; Switch buffer in all windows in the active frame. +;; +;; follow-switch-to-current-buffer-all +;; Show the current buffer in all windows on the current +;; frame and turn on `follow-mode'. +;; +;; follow-first-window C-c . < +;; Select the first window in the frame showing the same buffer. +;; +;; follow-last-window C-c . > +;; Select the last window in the frame showing the same buffer. +;; +;; follow-next-window C-c . n +;; Select the next window in the frame showing the same buffer. +;; +;; follow-previous-window C-c . p +;; Select the previous window showing the same buffer. + + +;; Well, it seems ok, but what if I really want to look at two different +;; positions in the text? Here are two simple methods to use: +;; +;; 1) Use multiple frames; `follow' mode only affects windows displayed +;; in the same frame. (My apoligies to you who can't use frames.) +;; +;; 2) Bind `follow-mode' to key so you can turn it off whenever +;; you want to view two locations. Of course, `follow' mode can +;; be reactivated by hitting the same key again. +;; +;; Example from my ~/.emacs: +;; (global-set-key [f8] 'follow-mode) + + +;; Implementation: +;; +;; In an ideal world, follow mode would have been implemented in the +;; kernal of the display routines, making sure that the windows (in +;; follow mode) ALWAYS are aligned. On planet earth, however, we must +;; accept a solution where we ALMOST ALWAYS can make sure that the +;; windows are aligned. +;; +;; Follow mode does this in three places: +;; 1) After each user command. +;; 2) After a process output has been perfomed. +;; 3) When a scrollbar has been moved. +;; +;; This will cover most situations. (Let me know if there are other +;; situations which should be covered.) +;; +;; However, only the selected window is checked, for the reason of +;; efficiency and code complexity. (i.e. it is possible to make a +;; non-selected windows unaligned. It will, however, pop right back +;; when it is selected.) + +;;}}} +;;{{{ Change Log + +;;; Change log: +;; 25-May-95 andersl * File created. +;; 26-May-95 andersl * It works! +;; 27-May-95 andersl * Avoids hitting the head in the roof. +;; * follow-scroll-up, -scroll-down, and -recenter. +;; * V0.1 Sent to Ohio. +;; 28-May-95 andersl * Scroll-bar support added. +;; 30-May-95 andersl * Code adopted to standard style. +;; * Minor mode keymap. +;; 2-Jun-95 andersl * Processor output. +;; 3-Jun-95 andersl * V0.4 +;; 5-Jun-95 andersl * V0.5. Copyright notice corrected. +;; (The old one stated that I had copyright, but +;; that Emacs could be freely distributed ;-) ) +;; 6-Jun-95 andersl * Lucid support added. (no longer valid.) +;; 7-Jun-95 andersl * Menu bar added. +;; * Bug fix, (at-window 0 0) => (frame-first-window) +;; 15-Jun-95 andersl * 0.8 Major rework. looong lines and outline mode. +;; 18-Jun-95 andersl * 0.9 Allow a tail window to be selected, but pick +;; a better one when edited. +;; 26-Jun-95 andersl * Inlineing. +;; 02-Jul-95 andersl * compute-motion imitated with a ugly workaround, +;; Works with XEmacs again! +;; 15-Jul-95 andersl * find-file hook. +;; * submit-feedback. +;; * Survives major mode changes. +;; * Region spanning multiple windows looks +;; resonabely good. +;; 19-Jul-95 andersl * New process-filter handling. +;; 1-Aug-95 andersl * XEmacs scrollbar support. +;; * Emacs 19 `window-size-change' support. +;; * `save-window-excursion' removed, it triggered +;; a redraw! +;; 5-Aug-95 andersl * `follow-switch-to-current-buffer-all' added. +;; 16-Nov-95 andersl * V1.0 released! +;; 17-Nov-95 andersl * Byte compiler silencer for XEmacs broken. +;; * fkey-end-of-buffer treated the same way +;; end-of-buffer is. +;; * follow-mode-off-hook added. +;; (Suggested by David Hughes, thanks!) +;; 20-Nov-95 andersl * Bug in menu code corrected. +;; (Reported by Robert E. Brown, thanks!) +;; 5-Dec-95 andersl * `follow-avoid-tail-recenter' added to the +;; post-command-idle-hook to avoid recentering +;; caused by `paren' et. al. +;; 7-Dec-95 andersl * `follow-avoid-tail-recenter' called by +;; `window-scroll-functions'. +;; 18-Dec-95 andersl * All processes intercepted. +;; 20-Dec-95 andersl * `follow-recenter' accepts arguments. +;; * `move-overlay' advices, drag-region works. +;; 2-Jan-96 andersl * XEmacs: isearch fixed. +;; * `follow-calc-win-end' created. +;; 8-Jan-96 andersl * XEmacs: `window-end' with `guarantee' +;; argument used in `follow-calc-win-end'. +;; 9-Jan-96 andersl * `follow-end-of-buffer' added. +;; Code in post hook removed. +;; * XEmacs: Post hook is always executed +;; after a mouse button event. +;; 22-Jan-95 andersl * 1.5 released. +;; + +;;}}} +;;{{{ LCD Entry + +;;; LCD Archive Entry: +;; follow|Anders Lindgren|andersl@csd.uu.se| +;; Combines windows into tall virtual window, minor mode. +;; 22-Jan-1996|1.5|~/modes/follow.el.Z| + +;;}}} + +;;; Code: + +;;{{{ Preliminaries + +;; Make the compiler shut up! +;; There are two strategies: +;; 1) Shut warnings off completely. +;; 2) Handle each warning separately. +;; +;; Since I would like to see real errors, I've selected the latter +;; method. +;; +;; The problem with undefined variables and functions has been solved +;; by using `set', `symbol-value' and `symbol-function' rather than +;; `setq' and direct references to variables and functions. +;; +;; For example: +;; (if (boundp 'foo) ... (symbol-value 'foo) ) +;; (set 'foo ...) <-- XEmacs doesn't fall for this one. +;; (funcall (symbol-function 'set) 'bar ...) +;; +;; Note: When this file is interpreted, `eval-when-compile' is +;; evaluted (really smart...) Since it doesn't hurt to evaluate it, +;; but it is a bit annoying, we test if the byte-compiler has been +;; loaded. This can, of course, lead to some occasional unintended +;; evaluation... +;; +;; Should someone come up with a better solution, please let me +;; know. + +(eval-when-compile + (if (or (featurep 'bytecomp) + (featurep 'byte-compile)) + (cond ((string-match "XEmacs" emacs-version) + ;; Make XEmacs shut up! I'm using standard Emacs + ;; functions, they are NOT obsolete! + (if (eq (get 'force-mode-line-update 'byte-compile) + 'byte-compile-obsolete) + (put 'force-mode-line-update 'byte-compile 'nil)) + (if (eq (get 'frame-first-window 'byte-compile) + 'byte-compile-obsolete) + (put 'frame-first-window 'byte-compile 'nil)))))) + +;;}}} +;;{{{ Variables + +(defvar follow-mode nil + "Variable indicating if Follow mode is active.") + +(defvar follow-mode-hook nil + "*Hooks to run when follow-mode is turned on.") + +(defvar follow-mode-off-hook nil + "*Hooks to run when follow-mode is turned off.") + +(defvar follow-mode-version "follow.el (Release 1.5)" + "The current version of Follow mode.") + +(defvar follow-mode-map nil + "Minor mode keymap for Follow mode.") + +(defvar follow-mode-line-text " Follow" + "*Text shown in the mode line when Follow mode is active. +Defaults to \" Follow\". Examples of other values +are \" Fw\", or simply \"\".") + +(defvar follow-auto nil + "*Non-nil activates Follow mode whenever a file is loaded.") + +(defvar follow-mode-prefix "\C-c." + "*Prefix key to use for follow commands in Follow mode. +The value of this variable is checked as part of loading Follow mode. +After that, changing the prefix key requires manipulating keymaps.") + +(defvar follow-intercept-processes t + "*When non-nil, Follow Mode will monitor process output.") + +(defvar follow-emacs-version-xemacs-p + (string-match "XEmacs" emacs-version) + "Non-nil when running under XEmacs.") + +(defvar follow-avoid-tail-recenter-p + (not follow-emacs-version-xemacs-p) + "*When non-nil, patch emacs so that tail windows won't be recentered. + +A \"tail window\" is a window which displays only the end of +the buffer. Normally it is practical for the user that empty +windows are recentered automatically. However, when using +Follow Mode it breaks the display when the end is displayed +in a window \"above\" the last window. This is for +example the case when displaying short files. + +Must be set before Follow Mode is loaded. + +Please note that it is not possible to fully prevent Emacs from +recentering empty windows. Please report if you find a repeatable +situation in which Emacs recenters empty windows. + +XEmacs, as of 19.12, does not recenter windows, good!") + +(defvar follow-debug nil + "*Non-nil when debugging Follow mode.") + + +;;; Internal variables + +(defvar follow-internal-force-redisplay nil + "True when Follow mode should redisplay the windows.") + +(defvar follow-process-filter-alist '() + "The original filters for processes intercepted by Follow mode.") + +(defvar follow-active-menu nil + "The menu visible when Follow mode is active.") + +(defvar follow-deactive-menu nil + "The menu visible when Follow mode is deactivated.") + +(defvar follow-inside-post-command-hook nil + "Non-nil when inside Follow modes `post-command-hook'. +Used by `follow-window-size-change'.") + +;;}}} +;;{{{ Bug report + +(eval-when-compile (require 'reporter)) + +(defun follow-submit-feedback () + "Sumbit feedback on Follow mode to the author: andersl@csd.uu.se" + (interactive) + (require 'reporter) + (and (y-or-n-p "Do you really want to submit a report on Follow mode? ") + (reporter-submit-bug-report + "Anders Lindgren <andersl@csd.uu.se>" + follow-mode-version + '(post-command-hook + post-command-idle-hook + pre-command-hook + window-size-change-functions + window-scroll-functions + follow-mode-hook + follow-mode-off-hook + follow-auto + follow-intercept-processes + follow-avoid-tail-recenter-p + follow-process-filter-alist) + nil + nil + (concat + "Hi Anders!\n\n" + "(I have read the section on how to report bugs in the " + "Emacs manual.)\n\n" + "Even though I know you are busy, I thought you might " + "want to know...\n\n")))) + +;;}}} +;;{{{ Debug messages + +;; This inline function must be as small as possible! +;; Maybe we should define a macro which expands to nil if +;; the varible is not set. + +(defsubst follow-debug-message (&rest args) + "Like message, but only active when `follow-debug' is non-nil." + (if (and (boundp 'follow-debug) follow-debug) + (apply 'message args))) + +;;}}} + +;;{{{ Keymap/Menu + +;;; Define keys for the follow-mode minor mode map and replace some +;;; functions in the global map. All `follow' mode special functions +;;; can be found on (the somewhat cumbersome) "C-c . <key>" +;;; (Control-C dot <key>). (As of Emacs 19.29 the keys +;;; C-c <punctuation character> are reserved for minor modes.) +;;; +;;; To change the prefix, redefine `follow-mode-prefix' before +;;; `follow' is loaded, or see the section on `follow-mode-hook' +;;; above for an example of how to bind the keys the way you like. +;;; +;;; Please note that the keymap is defined the first time this file is +;;; loaded. Also note that the only legal way to manipulate the +;;; keymap is to use `define-key'. Don't change it using `setq' or +;;; similar! + + +(if follow-mode-map + nil + (setq follow-mode-map (make-sparse-keymap)) + (let ((map (make-sparse-keymap))) + (define-key map "\C-v" 'follow-scroll-up) + (define-key map "\M-v" 'follow-scroll-down) + (define-key map "v" 'follow-scroll-down) + (define-key map "1" 'follow-delete-other-windows-and-split) + (define-key map "b" 'follow-switch-to-buffer) + (define-key map "\C-b" 'follow-switch-to-buffer-all) + (define-key map "\C-l" 'follow-recenter) + (define-key map "<" 'follow-first-window) + (define-key map ">" 'follow-last-window) + (define-key map "n" 'follow-next-window) + (define-key map "p" 'follow-previous-window) + + (define-key follow-mode-map follow-mode-prefix map) + + ;; Replace the standard `end-of-buffer', when in Follow Mode. (I + ;; don't see the point in trying to replace every function which + ;; could be enhanced in Follow mode. End-of-buffer is a special + ;; case since it is very simple to define and it greatly enhances + ;; the look and feel of Follow mode.) + ;; + ;; (The function `substitute-key-definition' does not work + ;; in all versions of Emacs.) + (mapcar + (function + (lambda (pair) + (let ((old (car pair)) + (new (cdr pair))) + (mapcar (function (lambda (key) + (define-key follow-mode-map key new))) + (where-is-internal old global-map))))) + '((end-of-buffer . follow-end-of-buffer) + (fkey-end-of-buffer . follow-end-of-buffer))) + + ;;; + ;;; The menu. + ;;; + + (if (not follow-emacs-version-xemacs-p) + + ;; + ;; Emacs 19 + ;; + (let ((menumap (funcall (symbol-function 'make-sparse-keymap) + "Follow")) + (count 0) + id) + (mapcar + (function + (lambda (item) + (setq id + (or (cdr item) + (progn + (setq count (+ count 1)) + (intern (format "separator-%d" count))))) + (define-key menumap (vector id) item) + (or (eq id 'follow-mode) + (put id 'menu-enable 'follow-mode)))) + ;; In reverse order: + '(("Toggle Follow mode" . follow-mode) + ("--") + ("Recenter" . follow-recenter) + ("--") + ("Previous Window" . follow-previous-window) + ("Next Windows" . follow-next-window) + ("Last Window" . follow-last-window) + ("First Window" . follow-first-window) + ("--") + ("Switch To Buffer (all windows)" + . follow-switch-to-buffer-all) + ("Switch To Buffer" . follow-switch-to-buffer) + ("--") + ("Delete Other Windows and Split" + . follow-delete-other-windows-and-split) + ("--") + ("Scroll Down" . follow-scroll-down) + ("Scroll Up" . follow-scroll-up))) + + ;; If there is a `tools' meny, we use it. However, we can't add a + ;; minor-mode specific item to it (it's broken), so we make the + ;; contents ghosted when not in use, and add ourselves to the + ;; global map. If no `tools' menu is present, just make a + ;; top-level menu visible when the mode is activated. + + (let ((tools-map (lookup-key (current-global-map) [menu-bar tools])) + (last nil)) + (if (sequencep tools-map) + (progn + ;; Find the last entry in the menu and store it in `last'. + (mapcar (function + (lambda (x) + (setq last (or (cdr-safe + (cdr-safe + (cdr-safe x))) + last)))) + tools-map) + (if last + (progn + (funcall (symbol-function 'define-key-after) + tools-map [separator-follow] '("--") last) + (funcall (symbol-function 'define-key-after) + tools-map [follow] (cons "Follow" menumap) + 'separator-follow)) + ;; Didn't find the last item, Adding to the top of + ;; tools. (This will probably never happend...) + (define-key (current-global-map) [menu-bar tools follow] + (cons "Follow" menumap)))) + ;; No tools menu, add "Follow" to the menubar. + (define-key follow-mode-map [menu-bar follow] + (cons "Follow" menumap))))) + + ;; + ;; XEmacs. + ;; + + ;; place the menu in the `Tools' menu. + (let ((menu '("Follow" + :filter follow-menu-filter + ["Scroll Up" follow-scroll-up t] + ["Scroll Down" follow-scroll-down t] + ["Delete Other Windows and Split" + follow-delete-other-windows-and-split t] + ["Switch To Buffer" follow-switch-to-buffer t] + ["Switch To Buffer (all windows)" + follow-switch-to-buffer-all t] + ["First Window" follow-first-window t] + ["Last Window" follow-last-window t] + ["Next Windows" follow-next-window t] + ["Previous Window" follow-previous-window t] + ["Recenter" follow-recenter t] + ["Deactivate" follow-mode t]))) + + ;; Why not just `(set-buffer-menubar current-menubar)'? The + ;; question is a very good question. The reason is that under + ;; Emacs 19, neither `set-buffer-menubar' nor + ;; `current-menubar' is defined, hence the byte-compiler will + ;; warn. + (funcall (symbol-function 'set-buffer-menubar) + (symbol-value 'current-menubar)) + (funcall (symbol-function 'add-submenu) '("Tools") menu)) + + ;; When the mode is not activated, only one item is visible: + ;; "Activate". + (defun follow-menu-filter (menu) + (if follow-mode + menu + '(["Activate " follow-mode t])))))) + + +;;; Register the follow mode keymap. +(or (assq 'follow-mode minor-mode-map-alist) + (setq minor-mode-map-alist + (cons (cons 'follow-mode follow-mode-map) minor-mode-map-alist))) + +;;}}} + +;;{{{ The mode + +;;;###autoload +(defun turn-on-follow-mode () + "Turn on Follow mode. Please see the function `follow-mode'." + (interactive) + (follow-mode 1)) + + +;;;###autoload +(defun turn-off-follow-mode () + "Turn off Follow mode. Please see the function `follow-mode'." + (interactive) + (follow-mode -1)) + + +;;;###autoload +(defun follow-mode (arg) + "Minor mode which combines windows into one tall virtual window. + +The feeling of a \"virtual window\" has been accomplished by the use +of two major techniques: + +* The windows always displays adjacent sections of the buffer. + This means that whenever one window is moved, all the + others will follow. (Hence the name Follow Mode.) + +* Should the point (cursor) end up outside a window, another + window displaying that point is selected, if possible. This + makes it possible to walk between windows using normal cursor + movement commands. + +Follow mode comes to its prime when used on a large screen and two +side-by-side window are used. The user can, with the help of Follow +mode, use two full-height windows as though they would have been +one. Imagine yourself editing a large function, or section of text, +and beeing able to use 144 lines instead of the normal 72... (your +mileage may vary). + +To split one large window into two side-by-side windows, the commands +`\\[split-window-horizontally]' or \ +`M-x follow-delete-other-windows-and-split' can be used. + +Only windows displayed in the same frame follow each-other. + +If the variable `follow-intercept-processes' is non-nil, Follow mode +will listen to the output of processes and redisplay accordingly. +\(This is the default.) + +When Follow mode is switched on, the hook `follow-mode-hook' +is called. When turned off, `follow-mode-off-hook' is called. + +Keys specific to Follow mode: +\\{follow-mode-map}" + (interactive "P") + (make-local-variable 'follow-mode) + (put 'follow-mode 'permanent-local t) + (let ((follow-mode-orig follow-mode)) + (setq follow-mode + (if (null arg) + (not follow-mode) + (> (prefix-numeric-value arg) 0))) + (if (and follow-mode follow-intercept-processes) + (follow-intercept-process-output)) + (cond ((and follow-mode (not follow-mode-orig)) ; On + ;; XEmacs: If this is non-nil, the window will scroll before + ;; the point will have a chance to get into the next window. + (if (boundp 'scroll-on-clipped-lines) + (set 'scroll-on-clipped-lines nil)) + (force-mode-line-update) + (add-hook 'post-command-hook 'follow-post-command-hook t) + (if (boundp 'post-command-idle-hook) + (add-hook 'post-command-idle-hook + 'follow-avoid-tail-recenter t)) + (run-hooks 'follow-mode-hook)) + + ((and (not follow-mode) follow-mode-orig) ; Off + (force-mode-line-update) + (run-hooks 'follow-mode-off-hook))))) + + +;; Register follow-mode as a minor mode. + +(or (assq 'follow-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(follow-mode follow-mode-line-text) minor-mode-alist))) + +;;}}} +;;{{{ Find file hook + +;; This will start follow-mode whenever a new file is loaded, if +;; the variable `follow-auto' is non-nil. + +(add-hook 'find-file-hooks 'follow-find-file-hook t) + +(defun follow-find-file-hook () + "Find-file hook for Follow Mode. See the variable `follow-auto'." + (if follow-auto (follow-mode t))) + +;;}}} + +;;{{{ User functions + +;;; +;;; User functions usable when in Follow mode. +;;; + +;;{{{ Scroll + +;; `scroll-up' and `-down', but for windows in Follow Mode. +;; +;; Almost like the real thing, excpet when the cursor ends up outside +;; the top or bottom... In our case however, we end up outside the +;; window and hence we are recenterd. Should we let `recenter' handle +;; the point position we would never leave the selected window. To do +;; it ourselves we would need to do our own redisplay, which is easier +;; said than done. (Why didn't I do a real display abstraction from +;; the beginning?) +;; +;; We must sometimes set `follow-internal-force-redisplay', otherwise +;; our post-command-hook will move our windows back into the old +;; position... (This would also be corrected if we would have had a +;; good redisplay abstraction.) + +(defun follow-scroll-up (&optional arg) + "Scroll text in a Follow Mode window chain up. + +If called with no ARG, the `next-screen-context-lines' last lines of +the bottom window in the chain will be visible in the top window. + +If called with an argument, scroll ARG lines up. +Negative ARG means scroll downward. + +Works like `scroll-up' when not in Follow Mode." + (interactive "P") + (cond ((not (and (boundp 'follow-mode) follow-mode)) + (scroll-up arg)) + (arg + (save-excursion (scroll-up arg)) + (setq follow-internal-force-redisplay t)) + (t + (let* ((windows (follow-all-followers)) + (end (window-end (car (reverse windows))))) + (if (eq end (point-max)) + (signal 'end-of-buffer nil) + (select-window (car windows)) + (goto-char end) + (vertical-motion (- next-screen-context-lines)) + (set-window-start (car windows) (point))))))) + + +(defun follow-scroll-down (&optional arg) + "Scroll text in a Follow Mode window chain down. + +If called with no ARG, the `next-screen-context-lines' top lines of +the top window in the chain will be visible in the bottom window. + +If called with an argument, scroll ARG lines down. +Negative ARG means scroll upward. + +Works like `scroll-up' when not in Follow Mode." + (interactive "P") + (cond ((not (and (boundp 'follow-mode) follow-mode)) + (scroll-up arg)) + (arg + (save-excursion (scroll-down arg))) + (t + (let* ((windows (follow-all-followers)) + (win (car (reverse windows))) + (start (window-start (car windows)))) + (if (eq start (point-min)) + (signal 'beginning-of-buffer nil) + (select-window win) + (goto-char start) + (vertical-motion (- (- (window-height win) + 1 + next-screen-context-lines))) + (set-window-start win (point)) + (goto-char start) + (vertical-motion (- next-screen-context-lines 1)) + (setq follow-internal-force-redisplay t)))))) + +;;}}} +;;{{{ Buffer + +;;;###autoload +(defun follow-delete-other-windows-and-split (&optional arg) + "Create two side by side windows and enter Follow Mode. + +Execute this command to display as much as possible of the text +in the selected window. All other windows, in the current +frame, are deleted and the selected window is split in two +side-by-side windows. Follow Mode is activated, hence the +two windows always will display two successive pages. +\(If one window is moved, the other one will follow.) + +If ARG is positive, the leftmost window is selected. If it negative, +the rightmost is selected. If ARG is nil, the leftmost window is +selected if the original window is the first one in the frame. + +To bind this command to a hotkey, place the following line +in your `~/.emacs' file, replacing [f7] by your favourite key: + (global-set-key [f7] 'follow-delete-other-windows-and-split)" + (interactive "P") + (let ((other (or (and (null arg) + (not (eq (selected-window) + (frame-first-window (selected-frame))))) + (and arg + (< (prefix-numeric-value arg) 0)))) + (start (window-start))) + (delete-other-windows) + (split-window-horizontally) + (if other + (progn + (other-window 1) + (set-window-start (selected-window) start) + (setq follow-internal-force-redisplay t))) + (follow-mode 1))) + +(defun follow-switch-to-buffer (buffer) + "Show BUFFER in all windows in the current Follow Mode window chain." + (interactive "BSwitch to Buffer: ") + (let ((orig-window (selected-window)) + (windows (follow-all-followers))) + (while windows + (select-window (car windows)) + (switch-to-buffer buffer) + (setq windows (cdr windows))) + (select-window orig-window))) + + +(defun follow-switch-to-buffer-all (&optional buffer) + "Show BUFFER in all windows on this frame. +Defaults to current buffer." + (interactive (list (read-buffer "Switch to Buffer: " + (current-buffer)))) + (or buffer (setq buffer (current-buffer))) + (let ((orig-window (selected-window))) + (walk-windows + (function + (lambda (win) + (select-window win) + (switch-to-buffer buffer)))) + (select-window orig-window) + (follow-redisplay))) + + +(defun follow-switch-to-current-buffer-all () + "Show current buffer in all windows on this frame, and enter Follow Mode. + +To bind this command to a hotkey place the following line +in your `~/.emacs' file: + (global-set-key [f7] 'follow-switch-to-current-buffer-all)" + (interactive) + (or (and (boundp 'follow-mode) follow-mode) + (follow-mode 1)) + (follow-switch-to-buffer-all)) + +;;}}} +;;{{{ Movement + +;; Note, these functions are not very useful, atleast not unless you +;; rebind the rather cumbersome key sequence `C-c . p'. + +(defun follow-next-window () + "Select the next window showing the same buffer." + (interactive) + (let ((succ (cdr (follow-split-followers (follow-all-followers))))) + (if succ + (select-window (car succ)) + (error "%s" "No more windows")))) + + +(defun follow-previous-window () + "Select the previous window showing the same buffer." + (interactive) + (let ((pred (car (follow-split-followers (follow-all-followers))))) + (if pred + (select-window (car pred)) + (error "%s" "No more windows")))) + + +(defun follow-first-window () + "Select the first window in the frame showing the same buffer." + (interactive) + (select-window (car (follow-all-followers)))) + + +(defun follow-last-window () + "Select the last window in the frame showing the same buffer." + (interactive) + (select-window (car (reverse (follow-all-followers))))) + +;;}}} +;;{{{ Redraw + +(defun follow-recenter (&optional arg) + "Recenter the middle window around the point, +and rearrange all other windows around the middle window. + +With a positive argument, place the current line ARG lines +from the top. With a negative, place it -ARG lines from the +bottom." + (interactive "P") + (if arg + (let ((p (point)) + (arg (prefix-numeric-value arg))) + (if (>= arg 0) + ;; Recenter relative to the top. + (progn + (follow-first-window) + (goto-char p) + (recenter arg)) + ;; Recenter relative to the bottom. + (follow-last-window) + (goto-char p) + (recenter arg) + ;; Otherwise, our post-command-hook will move the window + ;; right back. + (setq follow-internal-force-redisplay t))) + ;; Recenter in the middle. + (let* ((dest (point)) + (windows (follow-all-followers)) + (win (nth (/ (- (length windows) 1) 2) windows))) + (select-window win) + (goto-char dest) + (recenter) + ;;(setq follow-internal-force-redisplay t) + ))) + + +(defun follow-redraw () + "Arrange windows displaying the same buffer in successor order. +This function can be called even if the buffer is not in Follow mode. + +Hopefully, there should be no reason to call this function when in +Follow mode since the windows should always be aligned." + (interactive) + (sit-for 0) + (follow-redisplay)) + +;;}}} +;;{{{ End of buffer + +(defun follow-end-of-buffer (&optional arg) + "Move point to the end of the buffer. Follow Mode style. + +If the end is not visible, it will be displayed in the last possible +window in the Follow Mode window chain. + +The mark is left at the previous position. With arg N, put point N/10 +of the way from the true end." + (interactive "P") + (let ((followers (follow-all-followers)) + (pos (point))) + (cond (arg + (select-window (car (reverse followers)))) + ((follow-select-if-end-visible + (follow-windows-start-end followers))) + (t + (select-window (car (reverse followers))))) + (goto-char pos) + (end-of-buffer arg))) + +;;}}} + +;;}}} + +;;{{{ Display + +;;;; The display routines + +;;{{{ Information gathering functions + +(defun follow-all-followers (&optional testwin) + "Return all windows displaying the same buffer as the TESTWIN. +The list contains only windows displayed in the same frame as TESTWIN. +If TESTWIN is nil the selected window is used." + (or (and testwin (window-live-p testwin)) + (setq testwin (selected-window))) + (let* ((top (frame-first-window (window-frame testwin))) + (win top) + (done nil) + (windows '()) + (buffer (window-buffer testwin))) + (while (and (not done) win) + (if (eq (window-buffer win) buffer) + (setq windows (cons win windows))) + (setq win (next-window win 'not)) + (if (eq win top) + (setq done t))) + (nreverse windows))) + + +(defun follow-split-followers (windows &optional win) + "Split the WINDOWS into the sets: predecessors and successors. +Return `(PRED . SUCC)' where `PRED' and `SUCC' are ordered starting +from the selected window." + (or win + (setq win (selected-window))) + (let ((pred '())) + (while (not (eq (car windows) win)) + (setq pred (cons (car windows) pred)) + (setq windows (cdr windows))) + (cons pred (cdr windows)))) + + +;; Try to optimize this function for speed! + +(defun follow-calc-win-end (&optional win) + "Calculate the presumed window end for WIN. + +Actually, the position returned is the start of the next +window, normally is the end plus one. + +If WIN is nil, the selected window is used. + +Returns (end-pos end-of-buffer-p)" + (if follow-emacs-version-xemacs-p + ;; XEmacs can calculate the end of the window by using + ;; the 'guarantee options. GOOD! + (let ((end (window-end win t))) + (if (= end (funcall (symbol-function 'point-max) + (window-buffer win))) + (list end t) + (list (+ end 1) nil))) + ;; Emacs 19: We have to calculate the end by ourselves. + ;; This code works on both XEmacs and Emacs 19, but now + ;; that XEmacs has got custom-written code, this could + ;; be optimized for Emacs 19. + (let ((orig-win (and win (selected-window))) + height + buffer-end-p) + (if win (select-window win)) + (prog1 + (save-excursion + (goto-char (window-start)) + (setq height (- (window-height) 1)) + (setq buffer-end-p + (if (bolp) + (not (= height (vertical-motion height))) + (save-restriction + ;; Fix a mis-feature in `vertical-motion': + ;; The start of the window is assumed to + ;; coinside with the start of a line. + (narrow-to-region (point) (point-max)) + (not (= height (vertical-motion height)))))) + (list (point) buffer-end-p)) + (if orig-win + (select-window orig-win)))))) + + +;; Can't use `save-window-excursion' since it triggers a redraw. +(defun follow-calc-win-start (windows pos win) + "Calculate where WIN will start if the first in WINDOWS start at POS. + +If WIN is nil the point below all windows is returned." + (let (start) + (while (and windows (not (eq (car windows) win))) + (setq start (window-start (car windows))) + (set-window-start (car windows) pos 'noforce) + (setq pos (car (inline (follow-calc-win-end (car windows))))) + (set-window-start (car windows) start 'noforce) + (setq windows (cdr windows))) + pos)) + + +;; Build a list of windows and their start and end positions. +;; Useful to avoid calculating start/end position whenever they are needed. +;; The list has the format: +;; ((Win Start End End-of-buffer-visible-p) ...) + +;; Used to have a `save-window-excursion', but it obviously triggered +;; redraws of the display. Check if I used it for anything. + + +(defun follow-windows-start-end (windows) + "Builds a list of (WIN START END BUFFER-END-P) for every window in WINDOWS." + (let ((win-start-end '()) + (orig-win (selected-window))) + (while windows + (select-window (car windows)) + (setq win-start-end + (cons (cons (car windows) + (cons (window-start) + (follow-calc-win-end))) + win-start-end)) + (setq windows (cdr windows))) + (select-window orig-win) + (nreverse win-start-end))) + + +(defun follow-pos-visible (pos win win-start-end) + "Non-nil when POS is visible in WIN." + (let ((wstart-wend-bend (cdr (assq win win-start-end)))) + (and (>= pos (car wstart-wend-bend)) + (or (< pos (car (cdr wstart-wend-bend))) + (nth 2 wstart-wend-bend))))) + + +;; By `aligned' we mean that for all adjecent windows, the end of the +;; first is equal with the start of the successor. The first window +;; should start at a full screen line. + +(defun follow-windows-aligned-p (win-start-end) + "Non-nil if the follower WINDOWS are alinged." + (let ((res t)) + (save-excursion + (goto-char (window-start (car (car win-start-end)))) + (if (bolp) + nil + (vertical-motion 0 (car (car win-start-end))) + (setq res (eq (point) (window-start (car (car win-start-end))))))) + (while (and res (cdr win-start-end)) + ;; At least two followers left + (setq res (eq (nth 2 (car win-start-end)) + (nth 1 (car (cdr win-start-end))))) + (setq win-start-end (cdr win-start-end))) + res)) + + +;; Check if the point is visible in all windows. (So that +;; no one will be recentered.) + +(defun follow-point-visible-all-windows-p (win-start-end) + "Non-nil when the window-point is visible in all windows." + (let ((res t)) + (while (and res win-start-end) + (setq res (inline + (follow-pos-visible (window-point (car (car win-start-end))) + (car (car win-start-end)) + win-start-end))) + (setq win-start-end (cdr win-start-end))) + res)) + + +;; Make sure WIN always starts at the beginning of an whole screen +;; line. If WIN is not aligned the start is updated which probably +;; will lead to a redisplay of the screen later on. +;; +;; This is used with the first window in a follow chain. The reason +;; is that we want to detect that the point is outside the window. +;; (Without the update, the start of the window will move as the +;; user presses BackSpace, and the other window redisplay routines +;; will move the start of the window in the wrong direction.) + +(defun follow-update-window-start (win) + "Make sure that the start of WIN starts at a full screen line." + (save-excursion + (goto-char (window-start win)) + (if (bolp) + nil + (vertical-motion 0 win) + (if (eq (point) (window-start win)) + nil + (vertical-motion 1 win) + (set-window-start win (point) 'noforce))))) + +;;}}} +;;{{{ Selection functions + +;; Make a window in WINDOWS selected if it currently +;; is displaying the position DEST. +;; +;; We don't select a window if it just has been moved. + +(defun follow-select-if-visible (dest win-start-end) + "Select and return a window, if DEST is visible in it. +Return the selected window." + (let ((win nil)) + (while (and (not win) win-start-end) + ;; Don't select a window which was just moved. This makes it + ;; possible to later select the last window after a `end-of-buffer' + ;; command. + (if (follow-pos-visible dest (car (car win-start-end)) win-start-end) + (progn + (setq win (car (car win-start-end))) + (select-window win))) + (setq win-start-end (cdr win-start-end))) + win)) + + +;; Lets select a window showing the end. Make sure we only select it if it +;; it wasn't just moved here. (i.e. M-> shall not unconditionally place +;; the point in the selected window.) +;; +;; (Compability cludge: in Emacs 19 `window-end' is equal to `point-max'; +;; in XEmacs, it is equal to `point-max + 1'. Should I really bother +;; checking `window-end' now when I check `end-of-buffer' explicitylt?) + +(defun follow-select-if-end-visible (win-start-end) + "Select and return a window, if end is visible in it." + (let ((win nil)) + (while (and (not win) win-start-end) + ;; Don't select a window which was just moved. This makes it + ;; possible to later select the last window after a `end-of-buffer' + ;; command. + (if (and (eq (point-max) (nth 2 (car win-start-end))) + (nth 3 (car win-start-end)) + (eq (point-max) (min (point-max) + (window-end (car (car win-start-end)))))) + (progn + (setq win (car (car win-start-end))) + (select-window win))) + (setq win-start-end (cdr win-start-end))) + win)) + + +;; Select a window which will display the point if the windows would +;; be redisplayed with the first window fixed. This is useful for +;; example when the user has pressed return at the bottom of a window +;; as the point is not visible in any window. + +(defun follow-select-if-visible-from-first (dest windows) + "Select and return a window with DEST, if WINDOWS are redrawn from top." + (let ((win nil) + end-pos-end-p) + (save-excursion + (goto-char (window-start (car windows))) + ;; Make sure the line start in the beginning of a real screen + ;; line. + (vertical-motion 0 (car windows)) + (if (< dest (point)) + ;; Above the start, not visible. + nil + ;; At or below the start. Check the windows. + (save-window-excursion + (while (and (not win) windows) + (set-window-start (car windows) (point) 'noforce) + (setq end-pos-end-p (follow-calc-win-end (car windows))) + (goto-char (car end-pos-end-p)) + ;; Visible, if dest above end, or if eob is visible inside + ;; the window. + (if (or (car (cdr end-pos-end-p)) + (< dest (point))) + (setq win (car windows)) + (setq windows (cdr windows))))))) + (if win + (select-window win)) + win)) + + +;;}}} +;;{{{ Redisplay + +;; Redraw all the windows on the screen, starting with the top window. +;; The window used as as marker is WIN, or the selcted window if WIN +;; is nil. + +(defun follow-redisplay (&optional windows win) + "Reposition the WINDOWS around WIN. +Should the point be too close to the roof we redisplay everything +from the top. WINDOWS should contain a list of windows to +redisplay, it is assumed that WIN is a member of the list. +Should WINDOWS be nil, the windows displaying the +same buffer as WIN, in the current frame, are used. +Should WIN be nil, the selected window is used." + (or win + (setq win (selected-window))) + (or windows + (setq windows (follow-all-followers win))) + (follow-downward windows (follow-calculate-first-window-start windows win))) + + +;; Redisplay a chain of windows. Start every window directly after the +;; end of the previous window, to make sure long lines are displayed +;; correctly. + +(defun follow-downward (windows pos) + "Redisplay all WINDOWS starting at POS." + (while windows + (set-window-start (car windows) pos) + (setq pos (car (follow-calc-win-end (car windows)))) + (setq windows (cdr windows)))) + + +;;(defun follow-downward (windows pos) +;; "Redisplay all WINDOWS starting at POS." +;; (let (p) +;; (while windows +;; (setq p (window-point (car windows))) +;; (set-window-start (car windows) pos) +;; (set-window-point (car windows) (max p pos)) +;; (setq pos (car (follow-calc-win-end (car windows)))) +;; (setq windows (cdr windows))))) + + +;; Return the start of the first window. +;; +;; First, estimate the position. It the value is not perfect (i.e. we +;; have somewhere splited a line between windows) we try to enhance +;; the value. +;; +;; The guess is always perfect if no long lines is split between +;; windows. +;; +;; The worst case peformace of probably very bad, but it is very +;; unlikely that we ever will miss the correct start by more than one +;; or two lines. + +(defun follow-calculate-first-window-start (windows &optional win start) + "Calculate the start of the first window. + +WINDOWS is a chain of windows to work with. WIN is the window +to recenter around. It is assumed that WIN starts at position +START." + (or win + (setq win (selected-window))) + (or start + (setq start (window-start win))) + (let ((guess (follow-estimate-first-window-start windows win start))) + (if (car guess) + (cdr guess) + ;; The guess wasn't exact, try to enhance it. + (let ((win-start (follow-calc-win-start windows (cdr guess) win))) + (cond ((= win-start start) + (follow-debug-message "exact") + (cdr guess)) + ((< win-start start) + (follow-debug-message "above") + (follow-calculate-first-window-start-from-above + windows (cdr guess) win start)) + (t + (follow-debug-message "below") + (follow-calculate-first-window-start-from-below + windows (cdr guess) win start))))))) + + +;; `exact' is disabled due to XEmacs and fonts of variable +;; height. +(defun follow-estimate-first-window-start (windows win start) + "Estimate the position of the first window. + +Returns (EXACT . POS). If EXACT is non-nil, POS is the starting +position of the first window. Otherwise it is a good guess." + (let ((pred (car (follow-split-followers windows win))) + (exact nil)) + (save-excursion + (goto-char start) + ;(setq exact (bolp)) + (vertical-motion 0 win) + (while pred + (vertical-motion (- 1 (window-height (car pred))) (car pred)) + (if (not (bolp)) + (setq exact nil)) + (setq pred (cdr pred))) + (cons exact (point))))) + + +;; Find the starting point, start at GUESS and search downward. +;; The returned point is always a point below GUESS. + +(defun follow-calculate-first-window-start-from-above + (windows guess win start) + (save-excursion + (let ((done nil) + win-start + res) + (goto-char guess) + (while (not done) + (if (not (= (vertical-motion 1 (car windows)) 1)) + ;; Hit bottom! (Can we really do this?) + ;; We'll keep it, since it ensures termination. + (progn + (setq done t) + (setq res (point-max))) + (setq win-start (follow-calc-win-start windows (point) win)) + (if (>= win-start start) + (progn + (setq done t) + (setq res (point)))))) + res))) + + +;; Find the starting point, start at GUESS and search upward. Return +;; a point on the same line as GUESS, or above. +;; +;; (Is this ever used? I must make sure it works just in case it is +;; ever called.) + +(defun follow-calculate-first-window-start-from-below + (windows guess &optional win start) + (setq win (or win (selected-window))) + (setq start (or start (window-start win))) + (save-excursion + (let ((done nil) + win-start + res) + ;; Always calculate what happend when no line is displayed in the first + ;; window. (The `previous' res is needed below!) + (goto-char guess) + (vertical-motion 0 (car windows)) + (setq res (point)) + (while (not done) + (if (not (= (vertical-motion -1 (car windows)) -1)) + ;; Hit roof! + (progn + (setq done t) + (setq res (point-min))) + (setq win-start (follow-calc-win-start windows (point) win)) + (cond ((= win-start start) ; Perfect match, use this value + (setq done t) + (setq res (point))) + ((< win-start start) ; Walked to far, use preious result + (setq done t)) + (t ; Store result for next iteration + (setq res (point)))))) + res))) + +;;}}} +;;{{{ Avoid tail recenter + +;; This sets the window internal flag `force_start'. The effect is that +;; windows only displaying the tail isn't recentered. +;; Has to be called before every redisplay... (Great isn't it?) +;; +;; XEmacs doesn't recenter the tail, GOOD! +;; +;; A window displaying only the tail, is a windows whose +;; window-start position is equal to (point-max) of the buffer it +;; displays. +;; +;; This function is also added to `post-command-idle-hook', introduced +;; in Emacs 19.30. This is needed since the vaccine injected by the +;; call from `post-command-hook' only works until the next redisplay. +;; It is possible that the functions in the `post-command-idle-hook' +;; can cause a redisplay, and hence a new vaccine is needed. +;; +;; Sometimes, calling this function could actually cause a redisplay, +;; especially if it is placed in the debug filter section. I must +;; investigate this further... + +(defun follow-avoid-tail-recenter (&rest rest) + "Make sure windows displaying the end of a buffer aren't recentered. + +This is done by reading and rewriting the start positon of +non-first windows in Follow Mode." + (if follow-avoid-tail-recenter-p + (let* ((orig-buffer (current-buffer)) + (top (frame-first-window (selected-frame))) + (win top) + (who '()) ; list of (buffer . frame) + start + pair) ; (buffer . frame) + (while ;; look, no body! + (progn + (setq start (window-start win)) + (set-buffer (window-buffer win)) + (setq pair (cons (window-buffer win) (window-frame win))) + (if (member pair who) + (if (and (boundp 'follow-mode) follow-mode + (eq (point-max) start)) + ;; Write the same window start back, but don't + ;; set the NOFORCE flag. + (set-window-start win start)) + (setq who (cons pair who))) + (setq win (next-window win 'not t)) + (not (eq win top)))) ;; Loop while this is true. + (set-buffer orig-buffer)))) + +;;}}} + +;;}}} +;;{{{ Post Command Hook + +;;; The magic little box. This function is called after every command. + +;; This is not as complicated as it seems. It is simply a list of common +;; display situations and the actions to take, plus commands for redrawing +;; the screen if it should be unaligned. +;; +;; We divide the check into two parts; whether we are at the end or not. +;; This is due to the fact that the end can actaually be visible +;; in several window even though they are aligned. + +(defun follow-post-command-hook () + "Ensure that the windows in Follow mode are adjecent after each command." + (setq follow-inside-post-command-hook t) + (if (or (not (input-pending-p)) + ;; Sometimes, in XEmacs, mouse events are not handled + ;; properly by `input-pending-p'. A typical example is + ;; when clicking on a node in `info'. + (and (boundp 'current-mouse-event) + (symbol-value 'current-mouse-event) + (fboundp 'button-event-p) + (funcall (symbol-function 'button-event-p) + (symbol-value 'current-mouse-event)))) + ;; Work in the selected window, not in the current buffer. + (let ((orig-buffer (current-buffer)) + (win (selected-window))) + (set-buffer (window-buffer win)) + (if (and (boundp 'follow-mode) follow-mode + (not (window-minibuffer-p win))) + ;; The buffer shown in the selected window is in follow + ;; mode, lets find the current state of the display and + ;; cache the result for speed (i.e. `aligned' and `visible'.) + (let* ((windows (inline (follow-all-followers win))) + (dest (point)) + (win-start-end (progn + (follow-update-window-start (car windows)) + (follow-windows-start-end windows))) + (aligned (follow-windows-aligned-p win-start-end)) + (visible (follow-pos-visible dest win win-start-end))) + (follow-avoid-tail-recenter) + ;; Select a window to display the point. + (or follow-internal-force-redisplay + (progn + (if (eq dest (point-max)) + ;; We're at the end, we have be be careful since + ;; the display can be aligned while `dest' can + ;; be visible in several windows. + (cond + ;; Select the current window, but only when + ;; the display is correct. (When inserting + ;; character in a tail window, the display is + ;; not correct, as they are shown twice.) + ;; + ;; Never stick to the current window after a + ;; deletion. The reason is cosmetic, when + ;; typing `DEL' in a window showing only the + ;; end of the file, character are removed + ;; from the window above, which is very + ;; unintuitive. + ((and visible + aligned + (not (memq this-command + '(backward-delete-char + delete-backward-char + backward-delete-char-untabify + kill-region)))) + (follow-debug-message "Max: same")) + ;; If the end is visible, and the window + ;; doesn't seems like it just has been moved, + ;; select it. + ((follow-select-if-end-visible win-start-end) + (follow-debug-message "Max: end visible") + (setq visible t) + (setq aligned nil) + (goto-char dest)) + ;; Just show the end... + (t + (follow-debug-message "Max: default") + (select-window (car (reverse windows))) + (goto-char dest) + (setq visible nil) + (setq aligned nil))) + + ;; We're not at the end, here life is much simpler. + (cond + ;; This is the normal case! + ;; It should be optimized for speed. + ((and visible aligned) + (follow-debug-message "same")) + ;; Pick a position in any window. If the + ;; display is ok, this will pick the `correct' + ;; window. If the display is wierd do this + ;; anyway, this will be the case after a delete + ;; at the beginning of the window. + ((follow-select-if-visible dest win-start-end) + (follow-debug-message "visible") + (setq visible t) + (goto-char dest)) + ;; Not visible anywhere else, lets pick this one. + ;; (Is this case used?) + (visible + (follow-debug-message "visible in selected.")) + ;; Far out! + ((eq dest (point-min)) + (follow-debug-message "min") + (select-window (car windows)) + (goto-char dest) + (set-window-start (selected-window) (point-min)) + (setq win-start-end (follow-windows-start-end windows)) + (setq visible t) + (setq aligned nil)) + ;; If we can position the cursor without moving the first + ;; window, do it. This is the case which catches `RET' + ;; at the bottom of a window. + ((follow-select-if-visible-from-first dest windows) + (follow-debug-message "Below first") + (setq visible t) + (setq aligned t) + (follow-redisplay windows (car windows)) + (goto-char dest)) + ;; None of the above. For simplicity, we stick to the + ;; selected window. + (t + (follow-debug-message "None") + (setq visible nil) + (setq aligned nil)))) + ;; If a new window has been selected, make sure that the + ;; old is not scrolled when the point is outside the + ;; window. + (or (eq win (selected-window)) + (let ((p (window-point win))) + (set-window-start win (window-start win) nil) + (set-window-point win p))))) + ;; Make sure the point is visible in the selected window. + ;; (This could lead to a scroll.) + (if (or visible + (follow-pos-visible dest win win-start-end)) + nil + (sit-for 0) + (follow-avoid-tail-recenter) + (setq win-start-end (follow-windows-start-end windows)) + (setq aligned nil)) + ;; Redraw the windows whenever needed. + (if (or follow-internal-force-redisplay + (not (or aligned + (follow-windows-aligned-p win-start-end))) + (not (inline (follow-point-visible-all-windows-p + win-start-end)))) + (progn + (setq follow-internal-force-redisplay nil) + (follow-redisplay windows (selected-window)) + (setq win-start-end (follow-windows-start-end windows)) + ;; When the point ends up in another window. This + ;; happends when dest is in the beginning of the + ;; file and the selected window is not the first. + ;; It can also, in rare situations happend when + ;; long lines are used and there is a big + ;; difference between the width of the windows. + ;; (When scrolling one line in a wide window which + ;; will cause a move larger that an entire small + ;; window.) + (if (follow-pos-visible dest win win-start-end) + nil + (follow-select-if-visible dest win-start-end) + (goto-char dest)))) + + ;; If the region is visible, make it look good when spanning + ;; multiple windows. + (if (or (and (boundp 'mark-active) (symbol-value 'mark-active)) + (and (fboundp 'region-active-p) + (funcall (symbol-function 'region-active-p)))) + (follow-maximize-region + (selected-window) windows win-start-end)) + + (follow-avoid-tail-recenter) + ;; DEBUG + ;;(if (not (follow-windows-aligned-p + ;; (follow-windows-start-end windows))) + ;; (message "follow-mode: windows still unaligend!")) + ;; END OF DEBUG + ) ; Matches (let* + ;; Buffer not in follow mode: + ;; We still must update the windows displaying the tail so that + ;; Emacs won't recenter them. + (follow-avoid-tail-recenter)) + (set-buffer orig-buffer))) + (setq follow-inside-post-command-hook nil)) + +;;}}} +;;{{{ The region + +;; Tries to make the highlighted area representing the region look +;; good when spanning several windows. +;; +;; Not perfect, as the point can't be placed at window end, only at +;; end-1. Whis will highlight a little bit in windows above +;; the current. + +(defun follow-maximize-region (win windows win-start-end) + "Make a highlighted region stretching multiple windows look good +when in Follow mode." + (let* ((all (follow-split-followers windows win)) + (pred (car all)) + (succ (cdr all)) + data) + (while pred + (setq data (assq (car pred) win-start-end)) + (set-window-point (car pred) (max (nth 1 data) (- (nth 2 data) 1))) + (setq pred (cdr pred))) + (while succ + (set-window-point (car succ) (nth 1 (assq (car succ) win-start-end))) + (setq succ (cdr succ))))) + +;;}}} +;;{{{ Scroll bar + +;;;; Scroll-bar support code. + +;;; Why is it needed? Well, if the selected window is in follow mode, +;;; all its follower stick to it blindly. If one of them is scrolled, +;;; it immediately returns to the original position when the mouse is +;;; released. If the selected window is not a follower of the dragged +;;; window the windows will be unaligned. + +;;; The advices doesn't get compiled. Aestetically, this might be a +;;; problem but in practical life it isn't. + +;;; Discussion: Now when the other windows in the chain follow the +;;; dragged, should we really select it? + +(cond ((fboundp 'scroll-bar-drag) + ;;; + ;;; Emacs 19 style scrollbars. + ;;; + + ;; Select the dragged window if it is a follower of the + ;; selected window. + ;; + ;; Generate advices of the form: + ;; (defadvice scroll-bar-drag (after follow-scroll-bar-drag activate) + ;; "Adviced by `follow-mode'." + ;; (follow-redraw-after-event (ad-get-arg 0))) + (let ((cmds '(scroll-bar-drag + scroll-bar-drag-1 ; Executed at every move. + scroll-bar-scroll-down + scroll-bar-scroll-up + scroll-bar-set-window-start))) + (while cmds + (eval + (` (defadvice (, (intern (symbol-name (car cmds)))) + (after + (, (intern (concat "follow-" (symbol-name (car cmds))))) + activate) + "Adviced by Follow Mode." + (follow-redraw-after-event (ad-get-arg 0))))) + (setq cmds (cdr cmds)))) + + + (defun follow-redraw-after-event (event) + "Adviced by Follow mode." + (condition-case nil + (let* ((orig-win (selected-window)) + (win (nth 0 (funcall + (symbol-function 'event-start) event))) + (fmode (assq 'follow-mode + (buffer-local-variables + (window-buffer win))))) + (if (and fmode (cdr fmode)) + ;; The selected window is in follow-mode + (progn + ;; Recenter around the dragged window. + (select-window win) + (follow-redisplay) + (select-window orig-win)))) + (error nil)))) + + + ((fboundp 'scrollbar-vertical-drag) + ;;; + ;;; XEmacs style scrollbars. + ;;; + + ;; Advice all scrollbar functions on the form: + ;; + ;; (defadvice scrollbar-line-down + ;; (after follow-scrollbar-line-down activate) + ;; (follow-xemacs-scrollbar-support (ad-get-arg 0))) + + (let ((cmds '(scrollbar-line-down ; Window + scrollbar-line-up + scrollbar-page-down ; Object + scrollbar-page-up + scrollbar-to-bottom ; Window + scrollbar-to-top + scrollbar-vertical-drag ; Object + ))) + + (while cmds + (eval + (` (defadvice (, (intern (symbol-name (car cmds)))) + (after + (, (intern (concat "follow-" (symbol-name (car cmds))))) + activate) + "Adviced by `follow-mode'." + (follow-xemacs-scrollbar-support (ad-get-arg 0))))) + (setq cmds (cdr cmds)))) + + + (defun follow-xemacs-scrollbar-support (window) + "Redraw windows showing the same buffer as shown in WINDOW. +WINDOW is either the dragged window, or a cons containing the +window as its first element. This is called while the user drags +the scrollbar. + +WINDOW can be an object or a window." + (condition-case nil + (progn + (if (consp window) + (setq window (car window))) + (let ((fmode (assq 'follow-mode + (buffer-local-variables + (window-buffer window)))) + (orig-win (selected-window))) + (if (and fmode (cdr fmode)) + (progn + ;; Recenter around the dragged window. + (select-window window) + (follow-redisplay) + (select-window orig-win))))) + (error nil))))) + +;;}}} +;;{{{ Process output + +;;; The following sections installs a spy which listens to process +;;; output and tries to reposition the windows whose buffers are in +;;; Follow mode. We play safe as much as possible... +;;; +;;; When follow-mode is activated all active processes are +;;; intercepted. All new processes which change their filter function +;;; using `set-process-filter' are also intercepted. The reason is +;;; that a process can cause a redisplay recentering "tail" windows. +;;; Note that it doesn't hurt to spy on more processes than needed. +;;; +;;; Technically, we set the process filter to `follow-generic-filter'. +;;; The original filter is stored in `follow-process-filter-alist'. +;;; Our generic filter calls the original filter, or inserts the +;;; output into the buffer, if the buffer originally didn't have an +;;; output filter. It also makes sure that the windows connected to +;;; the buffer are aligned. +;;; +;;; Discussion: How to we find processes which doesn't call +;;; `set-process-filter'? (How often are processes created in a +;;; buffer after Follow mode are activated?) +;;; +;;; Discussion: Should we also advice `process-filter' to make our +;;; filter invisible to others? + +;;{{{ Advice for `set-process-filter' + +;; Do not call this with 'follow-generic-filter as the name of the +;; filter... + +(defadvice set-process-filter (before follow-set-process-filter activate) + "Follow Mode listens to calls to this function to make +sure process output will be displayed correctly in buffers +in which the mode is activated. + +Follow Mode inserts it's own process filter to do it's +magic stuff before the real process filter is called." + (if follow-intercept-processes + (progn + (setq follow-process-filter-alist + (delq (assq (ad-get-arg 0) follow-process-filter-alist) + follow-process-filter-alist)) + (follow-tidy-process-filter-alist) + (cond ((eq (ad-get-arg 1) t)) + ((eq (ad-get-arg 1) nil) + (ad-set-arg 1 'follow-generic-filter)) + (t + (setq follow-process-filter-alist + (cons (cons (ad-get-arg 0) (ad-get-arg 1)) + follow-process-filter-alist)) + (ad-set-arg 1 'follow-generic-filter)))))) + + +(defun follow-call-set-process-filter (proc filter) + "Call original `set-process-filter' without the Follow mode advice." + (ad-disable-advice 'set-process-filter 'before + 'follow-set-process-filter) + (ad-activate 'set-process-filter) + (prog1 + (set-process-filter proc filter) + (ad-enable-advice 'set-process-filter 'before + 'follow-set-process-filter) + (ad-activate 'set-process-filter))) + + +(defadvice process-filter (after follow-process-filter activate) + "Normally when Follow mode is activated all processes has the +process filter set to `follow-generic-filter'. With this advice, +the original process filter is returned." + (cond ((eq ad-return-value 'follow-generic-filter) + (setq ad-return-value + (cdr-safe (assq (ad-get-arg 0) + follow-process-filter-alist)))))) + + +(defun follow-call-process-filter (proc) + "Call original `process-filter' without the Follow mode advice." + (ad-disable-advice 'process-filter 'after + 'follow-process-filter) + (ad-activate 'process-filter) + (prog1 + (process-filter proc) + (ad-enable-advice 'process-filter 'after + 'follow-process-filter) + (ad-activate 'process-filter))) + + +(defun follow-tidy-process-filter-alist () + "Remove old processes from `follow-process-filter-alist'." + (let ((alist follow-process-filter-alist) + (ps (process-list)) + (new ())) + (while alist + (if (and (not (memq (process-status (car (car alist))) + '(exit signal closed nil))) + (memq (car (car alist)) ps)) + (setq new (cons (car alist) new))) + (setq alist (cdr alist))) + (setq follow-process-filter-alist new))) + +;;}}} +;;{{{ Start/stop interception of processes. + +;; Normally, all new processed are intercepted by our `set-process-filter'. +;; This is needed to intercept old processed which were started before we were +;; loaded, and processes we have forgotten by calling +;; `follow-stop-intercept-process-output'. + +(defun follow-intercept-process-output () + "Intercept all active processes. + +This is needed so that Follow Mode can track all display events in the +system. (See `follow-mode')" + (interactive) + (let ((list (process-list))) + (while list + (if (eq (process-filter (car list)) 'follow-generic-filter) + nil + ;; The custom `set-process-filter' defined above. + (set-process-filter (car list) (process-filter (car list)))) + (setq list (cdr list)))) + (setq follow-intercept-processes t)) + + +(defun follow-stop-intercept-process-output () + "Stop Follow Mode from spying on processes. + +All current spypoints are removed and no new will be added. + +The effect is that Follow mode won't be able to handle buffers +connected to processes. + +The only reason to call this function is if the Follow mode spy filter +would interfere with some other package. If this happens, please +report this using the `follow-submit-feedback' function." + (interactive) + (follow-tidy-process-filter-alist) + (let ((list (process-list))) + (while list + (if (eq (process-filter (car list)) 'follow-generic-filter) + (progn + (follow-call-set-process-filter + (car list) + (cdr-safe (assq (car list) follow-process-filter-alist))) + (setq follow-process-filter-alist + (delq (assq (car list) follow-process-filter-alist) + follow-process-filter-alist)))) + (setq list (cdr list)))) + (setq follow-intercept-processes nil)) + +;;}}} +;;{{{ The filter + +;;; The following section is a naive method to make buffers with +;;; process output to work with Follow mode. Whenever the start of the +;;; window displaying the buffer is moved, we moves it back to it's +;;; original position and try to select a new window. (If we fail, +;;; the normal redisplay functions of Emacs will scroll it right +;;; back!) + +(defun follow-generic-filter (proc output) + "Process output filter for process connected to buffers in Follow mode." + (let* ((old-buffer (current-buffer)) + (orig-win (selected-window)) + (buf (process-buffer proc)) + (win (and buf (if (eq buf (window-buffer orig-win)) + orig-win + (get-buffer-window buf t)))) + (return-to-orig-win (and win (not (eq win orig-win)))) + (orig-window-start (and win (window-start win)))) + + ;; If input is pending, the `sit-for' below won't redraw the + ;; display. In that case, calling `follow-avoid-tail-recenter' may + ;; provoke the process hadnling code to sceduling a redisplay. + ;(or (input-pending-p) + ; (follow-avoid-tail-recenter)) + + ;; Output the `output'. + (let ((filter (cdr-safe (assq proc follow-process-filter-alist)))) + (cond + ;; Call the original filter function + (filter + (funcall filter proc output)) + + ;; No filter, but we've got a buffer. Just output into it. + (buf + (set-buffer buf) + (if (not (marker-buffer (process-mark proc))) + (set-marker (process-mark proc) (point-max))) + (let ((moving (= (point) (process-mark proc))) + (odeactivate (and (boundp 'deactivate-mark) + (symbol-value 'deactivate-mark))) + (old-buffer-read-only buffer-read-only)) + (setq buffer-read-only nil) + (save-excursion + (goto-char (process-mark proc)) + ;; `insert-before-markers' just in case the users next + ;; command is M-y. + (insert-before-markers output) + (set-marker (process-mark proc) (point))) + (if moving (goto-char (process-mark proc))) + (if (boundp 'deactivate-mark) + ;; This could really be + ;; (setq deactivate-mark odeactivate) + ;; but this raises an error when compiling on XEmacs. + (funcall (symbol-function 'set) + 'deactivate-mark odeactivate)) + (setq buffer-read-only old-buffer-read-only))))) + + ;; If we're in follow mode, do our stuff. Select a new window and + ;; redisplay. (Actually, it is redundant to check `buf', but I + ;; feel it's more correct.) + (if (and buf win (window-live-p win)) + (progn + (set-buffer buf) + (if (and (boundp 'follow-mode) follow-mode) + (progn + (select-window win) + (let* ((windows (follow-all-followers win)) + (win-start-end (follow-windows-start-end windows)) + (new-window-start (window-start win)) + (new-window-point (window-point win))) + (cond + ;; The window was moved. Move it back and + ;; select a new. If no better could be found, + ;; we stick the the new start position. This + ;; is used when the original process filter + ;; tries to position the cursor at the bottom + ;; of the window. Example: `lyskom'. + ((not (eq orig-window-start new-window-start)) + (follow-debug-message "filter: Moved") + (set-window-start win orig-window-start) + (follow-redisplay windows win) + (setq win-start-end (follow-windows-start-end windows)) + (follow-select-if-visible new-window-point + win-start-end) + (goto-char new-window-point) + (if (eq win (selected-window)) + (set-window-start win new-window-start)) + (setq win-start-end (follow-windows-start-end windows))) + ;; Stick to this window, if point is visible in it. + ((pos-visible-in-window-p new-window-point) + (follow-debug-message "filter: Visible in window")) + ;; Avoid redisplaying the first window. If the + ;; point is visible at a window below, + ;; redisplay and select it. + ((follow-select-if-visible-from-first + new-window-point windows) + (follow-debug-message "filter: Seen from first") + (follow-redisplay windows (car windows)) + (goto-char new-window-point) + (setq win-start-end + (follow-windows-start-end windows))) + ;; None of the above. We stick to the current window. + (t + (follow-debug-message "filter: nothing"))) + + ;; Here we have slected a window. Make sure the + ;; windows are aligned and the point is visible + ;; in the selected window. + (if (and (not (follow-pos-visible + (point) (selected-window) win-start-end)) + (not return-to-orig-win)) + (progn + (sit-for 0) + (setq win-start-end + (follow-windows-start-end windows)))) + + (if (or follow-internal-force-redisplay + (not (follow-windows-aligned-p win-start-end))) + (follow-redisplay windows))))))) + + ;; return to the original window. + (if return-to-orig-win + (select-window orig-win)) + (set-buffer old-buffer)) + + ;; Normally, if the display has been changed, it is redrawn. All + ;; windows showing only the end of a buffer is unconditionally + ;; recentered, we can't prevent it by calling + ;; `follow-avoid-tail-recenter'. + ;; + ;; By performing a redisplay on our own, Emacs need not perform + ;; the above described redisplay. (However, bu performing it when + ;; there are input available just seems to make things worse.) + (if (and follow-avoid-tail-recenter-p + (not (input-pending-p))) + (sit-for 0))) + +;;}}} + +;;}}} +;;{{{ Window size change + +;; In Emacs 19.29, the functions in `window-size-change-functions' are +;; called every time a window in a frame changes size. Most notably, it +;; is called after the frame has been resized. +;; +;; We basically call our post-command-hook for every buffer which is +;; visible in any window in the resized frame, which is in follow-mode. +;; +;; Since this function can be called indirectly from +;; `follow-post-command-hook' we have a potential infinite loop. We +;; handle this problem by simply not doing anything at all in this +;; situation. The variable `follow-inside-post-command-hook' contains +;; information about whether the execution actually is inside the +;; post-command-hook or not. + +(if (boundp 'window-size-change-functions) + (add-hook 'window-size-change-functions 'follow-window-size-change)) + + +(defun follow-window-size-change (frame) + "Redraw all windows in FRAME, when in Follow mode." + ;; Below, we call `post-command-hook'. This makes sure that we + ;; doesn't start a mutally recursive endless loop. + (if follow-inside-post-command-hook + nil + (let ((buffers '()) + (orig-window (selected-window)) + (orig-buffer (current-buffer)) + (orig-frame (selected-frame)) + windows + buf) + (select-frame frame) + (unwind-protect + (walk-windows + (function + (lambda (win) + (setq buf (window-buffer win)) + (if (memq buf buffers) + nil + (set-buffer buf) + (if (and (boundp 'follow-mode) + follow-mode) + (progn + (setq windows (follow-all-followers win)) + (if (memq orig-window windows) + (progn + ;; Make sure we're redrawing around the + ;; selected window. + ;; + ;; We must be really careful not to do this + ;; when we are (indirectly) called by + ;; `post-command-hook'. + (select-window orig-window) + (follow-post-command-hook) + (setq orig-window (selected-window))) + (follow-redisplay windows win)) + (setq buffers (cons buf buffers)))))))) + (select-frame orig-frame) + (set-buffer orig-buffer) + (select-window orig-window))))) + +;;}}} + +;;{{{ XEmacs isearch + +;; In XEmacs, isearch often finds matches in other windows than the +;; currently selected. However, when exiting the old window +;; configuration is restored, with the exception of the beginning of +;; the start of the window for the selected window. This is not much +;; help for us. +;; +;; We overwrite the stored window configuration with the current, +;; unless we are in `slow-search-mode', i.e. only a few lines +;; of text is visible. + +(if follow-emacs-version-xemacs-p + (defadvice isearch-done (before follow-isearch-done activate) + (if (and (boundp 'follow-mode) + follow-mode + (boundp 'isearch-window-configuration) + isearch-window-configuration + (boundp 'isearch-slow-terminal-mode) + (not isearch-slow-terminal-mode)) + (let ((buf (current-buffer))) + (setq isearch-window-configuration + (current-window-configuration)) + (set-buffer buf))))) + +;;}}} +;;{{{ Tail window handling + +;;; In Emacs (not XEmacs) windows showing nothing are sometimes +;;; recentered. When in Follow Mode, this is not desireable for +;;; non-first windows in the window chain. This section tries to +;;; make the windows stay where they should be. +;;; +;;; If the display is updated, all windows starting at (point-max) are +;;; going to be recentered at the next redisplay, unless we do a +;;; read-and-write cycle to update the `force' flag inside the windows. +;;; +;;; In 19.30, a new varible `window-scroll-functions' is called every +;;; time a window is recentered. It is not perfect for our situation, +;;; since when it is called for a tail window, it is to late. However, +;;; if it is called for another window, we can try to update our +;;; windows. +;;; +;;; By patching `sit-for' we can make sure that to catch all explicit +;;; updates initiated by lisp programs. Internal calls, on the other +;;; hand, are not handled. +;;; +;;; Please note that the function `follow-avoid-tail-recenter' is also +;;; called from other places, e.g. `post-command-hook' and +;;; `post-command-idle-hook'. + +;; If this function is called it is to late for this window, but +;; we might save other windows from beeing recentered. + +(if (and follow-avoid-tail-recenter-p (boundp 'window-scroll-functions)) + (add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)) + + +;; This prevents all packages which calls `sit-for' directly +;; to recenter tail windows. + +(if follow-avoid-tail-recenter-p + (defadvice sit-for (before follow-sit-for activate) + "Adviced by Follow Mode. + +Avoid to recenter windows displaying only the end of a file as when +displaying a short file in two windows, using Follow Mode." + (follow-avoid-tail-recenter))) + + +;; Without this advice, `mouse-drag-region' would start to recenter +;; tail windows. + +(if (and follow-avoid-tail-recenter-p + (fboundp 'move-overlay)) + (defadvice move-overlay (before follow-move-overlay activate) + "Adviced by Follow Mode. Don't recenter windows showing only +the end of a buffer. This prevents `mouse-drag-region' from +messing things up." + (follow-avoid-tail-recenter))) + +;;}}} +;;{{{ profile support + +;; The following (non-evaluated) section can be used to +;; profile this package using `elp'. +;; +;; Invalid indentation on purpose! + +(cond (nil +(setq elp-function-list + '(window-end + vertical-motion + ; sit-for ;; elp can't handle advices... + follow-mode + follow-all-followers + follow-split-followers + follow-redisplay + follow-downward + follow-calculate-first-window-start + follow-estimate-first-window-start + follow-calculate-first-window-start-from-above + follow-calculate-first-window-start-from-below + follow-calc-win-end + follow-calc-win-start + follow-pos-visible + follow-windows-start-end + follow-select-if-visible + follow-select-if-visible-from-first + follow-windows-aligned-p + follow-point-visible-all-windows-p + follow-avoid-tail-recenter + follow-update-window-start + follow-post-command-hook + )))) + +;;}}} + +;;{{{ The end + +;;; +;;; We're done! +;;; + +(provide 'follow) + +;;}}} + +;; /------------------------------------------------------------------------\ +;; | "I [..] am rarely happier then when spending an entire day programming | +;; | my computer to perform automatically a task that it would otherwise | +;; | take me a good ten seconds to do by hand. Ten seconds, I tell myself, | +;; | is ten seconds. Time is valuable and ten seconds' worth of it is well | +;; | worth the investment of a day's happy activity working out a way to | +;; | save it". -- Douglas Adams, "Last Chance to See" | +;; \------------------------------------------------------------------------/ + +;;; follow.el ends here