X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/87233a14e07a61981e3ce51350efb8b7ee5adcd2..34dc21db6e57ebbad81a196002fcd3cc557f096e:/lisp/follow.el diff --git a/lisp/follow.el b/lisp/follow.el index 4c76b43da2..ddb25beb18 100644 --- a/lisp/follow.el +++ b/lisp/follow.el @@ -1,8 +1,10 @@ ;;; follow.el --- synchronize windows showing the same buffer -;; Copyright (C) 1995-1997, 1999, 2001-2012 Free Software Foundation, Inc. + +;; Copyright (C) 1995-1997, 1999, 2001-2014 Free Software Foundation, +;; Inc. ;; Author: Anders Lindgren -;; Maintainer: FSF (Anders' email bounces, Sep 2005) +;; Maintainer: emacs-devel@gnu.org (Anders' email bounces, Sep 2005) ;; Created: 1995-05-25 ;; Keywords: display, window, minor-mode, convenience @@ -33,7 +35,7 @@ ;; 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 +;; * Should 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. @@ -142,7 +144,7 @@ ;; this command be added to the global keymap. ;; ;; follow-recenter C-c . C-l -;; Place the point in the center of the middle window, +;; Place 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 @@ -200,6 +202,7 @@ ;;; Code: (require 'easymenu) +(eval-when-compile (require 'cl-lib)) ;;; Variables @@ -309,7 +312,7 @@ are \" Fw\", or simply \"\"." (set-default symbol value))) (defvar follow-cache-command-list - '(next-line previous-line forward-char backward-char) + '(next-line previous-line forward-char backward-char right-char left-char) "List of commands that don't require recalculation. In order to be able to use the cache, a command should not change the @@ -321,9 +324,10 @@ The commands in this list are checked at load time. To mark other commands as suitable for caching, set the symbol property `follow-mode-use-cache' to non-nil.") -(defvar follow-debug nil - "Non-nil when debugging Follow mode.") - +(defcustom follow-debug nil + "If non-nil, emit Follow mode debugging messages." + :type 'boolean + :group 'follow) ;; Internal variables: @@ -333,8 +337,8 @@ property `follow-mode-use-cache' to non-nil.") (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-inactive-menu nil + "The menu visible when Follow mode is inactive.") (defvar follow-inside-post-command-hook nil "Non-nil when inside Follow modes `post-command-hook'. @@ -387,7 +391,7 @@ virtual window. This is accomplished by two main techniques: 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 +* Should 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. @@ -513,7 +517,7 @@ Works like `scroll-up' when not in Follow mode." (declare-function comint-adjust-point "comint" (window)) (defvar comint-scroll-show-maximum-output) -(defun follow-comint-scroll-to-bottom (&optional window) +(defun follow-comint-scroll-to-bottom (&optional _window) "Scroll the bottom-most window in the current Follow chain. This is to be called by `comint-postoutput-scroll-to-bottom'." (let* ((buffer (current-buffer)) @@ -529,7 +533,7 @@ This is to be called by `comint-postoutput-scroll-to-bottom'." (select-window win) (goto-char pos) (setq follow-windows-start-end-cache nil) - (follow-adjust-window win pos) + (follow-adjust-window win) (unless is-selected (select-window selected) (set-buffer buffer)))))) @@ -570,7 +574,7 @@ selected if the original window is the first one in the frame." (interactive "P") (let ((other (or (and (null arg) (not (eq (selected-window) - (frame-first-window (selected-frame))))) + (frame-first-window)))) (and arg (< (prefix-numeric-value arg) 0)))) (start (window-start))) @@ -741,12 +745,9 @@ contains only windows in the same frame as WIN. If WIN is nil, it defaults to the selected window." (unless (window-live-p win) (setq win (selected-window))) - (let ((buffer (window-buffer win)) - windows) - (dolist (w (window-list (window-frame win) 'no-minibuf win)) - (if (eq (window-buffer w) buffer) - (push w windows))) - (sort windows 'follow--window-sorter))) + (let ((windows (get-buffer-window-list + (window-buffer win) 'no-minibuf (window-frame win)))) + (sort windows #'follow--window-sorter))) (defun follow-split-followers (windows &optional win) "Split WINDOWS into two sets: predecessors and successors. @@ -844,7 +845,7 @@ returned by `follow-windows-start-end'." (setq win-start-end (cdr win-start-end))) result)) -;; Check if the point is visible in all windows. (So that +;; Check if point is visible in all windows. (So that ;; no one will be recentered.) (defun follow-point-visible-all-windows-p (win-start-end) @@ -863,7 +864,7 @@ returned by `follow-windows-start-end'." ;; 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. +;; is that we want to detect that 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.) @@ -881,22 +882,21 @@ returned by `follow-windows-start-end'." (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 win-end wse) + (let (win wse) (while (and (not win) win-start-end) ;; Don't select a window that was just moved. This makes it ;; possible to later select the last window after a ;; `end-of-buffer' command. (setq wse (car win-start-end)) (when (follow-pos-visible dest (car wse) win-start-end) - (setq win (car wse) - win-end (nth 2 wse)) + (setq win (car wse)) (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 wasn't just moved here. (I.e. M-> shall not unconditionally place -;; the point in the selected window.) +;; point in the selected window.) ;; ;; (Compatibility kludge: in Emacs `window-end' is equal to `point-max'; ;; in XEmacs, it is equal to `point-max + 1'. Should I really bother @@ -922,10 +922,10 @@ Return the selected window." win)) -;; Select a window that will display the point if the windows would +;; Select a window that will display 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. +;; as point is not visible in any window. (defun follow-select-if-visible-from-first (dest windows) "Try to select one of WINDOWS without repositioning the topmost window. @@ -967,7 +967,7 @@ Otherwise, return nil." (defun follow-redisplay (&optional windows win preserve-win) "Reposition the WINDOWS around WIN. -Should the point be too close to the roof we redisplay everything +Should 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 @@ -1081,7 +1081,7 @@ should be a member of WINDOWS, starts at position START." This is done by reading and rewriting the start position of non-first windows in Follow mode." (let* ((orig-buffer (current-buffer)) - (top (frame-first-window (selected-frame))) + (top (frame-first-window)) (win top) who) ; list of (buffer . frame) ;; If the only window in the frame is a minibuffer @@ -1122,156 +1122,157 @@ non-first windows in Follow mode." (with-current-buffer (window-buffer win) (unless (and (symbolp this-command) (get this-command 'follow-mode-use-cache)) - (setq follow-windows-start-end-cache nil))) - (follow-adjust-window win (point))))) - -(defun follow-adjust-window (win dest) - ;; Adjust the window WIN and its followers. - (with-current-buffer (window-buffer win) - (when (and follow-mode - (not (window-minibuffer-p win))) - (let* ((windows (follow-all-followers win)) - (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)) - selected-window-up-to-date) - (unless (and aligned visible) (setq follow-windows-start-end-cache nil)) + (follow-adjust-window win))))) - ;; Select a window to display point. - (unless follow-internal-force-redisplay - (if (eq dest (point-max)) - ;; Be careful at point-max: 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 characters in a tail - ;; window, the display is not correct, as they are - ;; shown twice.) - ;; - ;; Never stick to the current window after a deletion. - ;; Otherwise, when typing `DEL' in a window showing - ;; only the end of the file, a character would be - ;; 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 aligned nil) - (goto-char dest)) - ;; Just show the end... - (t - (follow-debug-message "Max: default") - (select-window (car (last windows))) - (goto-char dest) - (setq visible nil 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 picks the `correct' window. - ((follow-select-if-visible dest win-start-end) - (follow-debug-message "visible") - (goto-char dest) - ;; Perform redisplay, in case line is partially visible. - (setq visible nil)) - ;; Not visible anywhere else, lets pick this one. - (visible - (follow-debug-message "visible in selected.")) - ;; If DEST is before the first window start, select the - ;; first window. - ((< dest (nth 1 (car win-start-end))) - (follow-debug-message "before first") - (select-window (car windows)) - (goto-char dest) - (setq visible nil aligned nil)) - ;; If we can position the cursor without moving the first - ;; window, do it. This is the case that catches `RET' at - ;; the bottom of a window. - ((follow-select-if-visible-from-first dest windows) - (follow-debug-message "Below first") - (setq visible t aligned t)) - ;; None of the above. Stick to the selected window. - (t - (follow-debug-message "None") - (setq visible nil aligned nil)))) - - ;; If a new window was selected, make sure that the old is - ;; not scrolled when the point is outside the window. - (unless (eq win (selected-window)) - (let ((p (window-point win))) - (set-window-start win (window-start win) nil) - (set-window-point win p)))) - - (unless visible - ;; If point may not be visible in the selected window, - ;; perform a redisplay; this ensures scrolling. - (let ((opoint (point))) - (redisplay) - ;; If this `redisplay' moved point, we got clobbered by a - ;; previous call to `set-window-start'. Try again. - (when (/= (point) opoint) - (goto-char opoint) - (redisplay))) - - (setq selected-window-up-to-date t) - (follow-avoid-tail-recenter) - (setq win-start-end (follow-windows-start-end windows) - follow-windows-start-end-cache nil - aligned nil)) - - ;; Now redraw the windows around the selected window. - (unless (and (not follow-internal-force-redisplay) - (or aligned - (follow-windows-aligned-p win-start-end)) - (follow-point-visible-all-windows-p win-start-end)) - (setq follow-internal-force-redisplay nil) - (follow-redisplay windows (selected-window) - selected-window-up-to-date) - (setq win-start-end (follow-windows-start-end windows) - follow-windows-start-end-cache nil) - ;; The point can ends up in another window when DEST is at - ;; the beginning of the buffer and the selected window is - ;; not the first. It can also happen 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.) - (unless (follow-pos-visible dest win win-start-end) - (follow-select-if-visible dest win-start-end) - (goto-char dest))) - - ;; If the region is visible, make it look good when spanning - ;; multiple windows. - (when (region-active-p) - (follow-maximize-region - (selected-window) windows win-start-end))) - - ;; Whether or not the buffer was in follow mode, update windows - ;; displaying the tail so that Emacs won't recenter them. - (follow-avoid-tail-recenter)))) +(defun follow-adjust-window (win) + ;; Adjust the window WIN and its followers. + (cl-assert (eq (window-buffer win) (current-buffer))) + (when (and follow-mode + (not (window-minibuffer-p win))) + (let* ((dest (point)) + (windows (follow-all-followers win)) + (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)) + selected-window-up-to-date) + (unless (and aligned visible) + (setq follow-windows-start-end-cache nil)) + + ;; Select a window to display point. + (unless follow-internal-force-redisplay + (if (eq dest (point-max)) + ;; Be careful at point-max: 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 characters in a tail + ;; window, the display is not correct, as they are + ;; shown twice.) + ;; + ;; Never stick to the current window after a deletion. + ;; Otherwise, when typing `DEL' in a window showing + ;; only the end of the file, a character would be + ;; 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 aligned nil) + (goto-char dest)) + ;; Just show the end... + (t + (follow-debug-message "Max: default") + (select-window (car (last windows))) + (goto-char dest) + (setq visible nil 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 picks the `correct' window. + ((follow-select-if-visible dest win-start-end) + (follow-debug-message "visible") + (goto-char dest) + ;; Perform redisplay, in case line is partially visible. + (setq visible nil)) + ;; Not visible anywhere else, lets pick this one. + (visible + (follow-debug-message "visible in selected.")) + ;; If DEST is before the first window start, select the + ;; first window. + ((< dest (nth 1 (car win-start-end))) + (follow-debug-message "before first") + (select-window (car windows)) + (goto-char dest) + (setq visible nil aligned nil)) + ;; If we can position the cursor without moving the first + ;; window, do it. This is the case that catches `RET' at + ;; the bottom of a window. + ((follow-select-if-visible-from-first dest windows) + (follow-debug-message "Below first") + (setq visible t aligned t)) + ;; None of the above. Stick to the selected window. + (t + (follow-debug-message "None") + (setq visible nil aligned nil)))) + + ;; If a new window was selected, make sure that the old is + ;; not scrolled when point is outside the window. + (unless (eq win (selected-window)) + (let ((p (window-point win))) + (set-window-start win (window-start win) nil) + (set-window-point win p)))) + + (unless visible + ;; If point may not be visible in the selected window, + ;; perform a redisplay; this ensures scrolling. + (let ((opoint (point))) + (redisplay) + ;; If this `redisplay' moved point, we got clobbered by a + ;; previous call to `set-window-start'. Try again. + (when (/= (point) opoint) + (goto-char opoint) + (redisplay))) + + (setq selected-window-up-to-date t) + (follow-avoid-tail-recenter) + (setq win-start-end (follow-windows-start-end windows) + follow-windows-start-end-cache nil + aligned nil)) + + ;; Now redraw the windows around the selected window. + (unless (and (not follow-internal-force-redisplay) + (or aligned + (follow-windows-aligned-p win-start-end)) + (follow-point-visible-all-windows-p win-start-end)) + (setq follow-internal-force-redisplay nil) + (follow-redisplay windows (selected-window) + selected-window-up-to-date) + (setq win-start-end (follow-windows-start-end windows) + follow-windows-start-end-cache nil) + ;; Point can end up in another window when DEST is at + ;; the beginning of the buffer and the selected window is + ;; not the first. It can also happen 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.) + (unless (follow-pos-visible dest win win-start-end) + (follow-select-if-visible dest win-start-end) + (goto-char dest))) + + ;; If the region is visible, make it look good when spanning + ;; multiple windows. + (when (region-active-p) + (follow-maximize-region + (selected-window) windows win-start-end))) + + ;; Whether or not the buffer was in follow mode, update windows + ;; displaying the tail so that Emacs won't recenter them. + (follow-avoid-tail-recenter))) ;;; 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 +;; Not perfect, as point can't be placed at window end, only at ;; end-1. This will highlight a little bit in windows above ;; the current. @@ -1296,6 +1297,12 @@ non-first windows in Follow mode." ;; This handles the case where the user drags the scroll bar of a ;; non-selected window whose buffer is in Follow mode. +(declare-function scroll-bar-toolkit-scroll "scroll-bar" (event)) +(declare-function scroll-bar-drag "scroll-bar" (event)) +(declare-function scroll-bar-scroll-up "scroll-bar" (event)) +(declare-function scroll-bar-scroll-down "scroll-bar" (event)) +(declare-function mwheel-scroll "mwheel" (event)) + (defun follow-scroll-bar-toolkit-scroll (event) (interactive "e") (scroll-bar-toolkit-scroll event)