summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Mackenzie <acm@muc.de>2015-12-20 12:33:30 +0000
committerAlan Mackenzie <acm@muc.de>2015-12-20 12:33:30 +0000
commitdf32db2f5f3dcad4b2b16fd52e51e1c7bd846609 (patch)
tree74c24ce0fd04942ec496aab04d8f25482944107e
parent6a8a41c5104b29846ed6e69da7576e0960f2bf14 (diff)
parenta72a9fbbbc9cd5f5933719b11489c2578eb0aa59 (diff)
Merge branch 'scratch/follow' into emacs-25
This allows Isearch, etc., to work well when Follow Mode is active.
-rw-r--r--doc/lispref/functions.texi17
-rw-r--r--doc/lispref/positions.texi12
-rw-r--r--doc/lispref/windows.texi88
-rw-r--r--lisp/follow.el176
-rw-r--r--lisp/isearch.el125
-rw-r--r--lisp/replace.el8
-rw-r--r--lisp/textmodes/ispell.el79
-rw-r--r--lisp/window.el146
8 files changed, 577 insertions, 74 deletions
diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi
index 8835667b82..7cc041fa77 100644
--- a/doc/lispref/functions.texi
+++ b/doc/lispref/functions.texi
@@ -861,15 +861,18 @@ into a list. @code{mapc} always returns @var{sequence}.
@defun mapconcat function sequence separator
@code{mapconcat} applies @var{function} to each element of
-@var{sequence}: the results, which must be strings, are concatenated.
-Between each pair of result strings, @code{mapconcat} inserts the string
-@var{separator}. Usually @var{separator} contains a space or comma or
-other suitable punctuation.
+@var{sequence}; the results, which must be sequences of characters
+(strings, vectors, or lists), are concatenated into a single string
+return value. Between each pair of result sequences, @code{mapconcat}
+inserts the characters from @var{separator}, which also must be a
+string, or a vector or list of characters. @xref{Sequences Arrays
+Vectors}.
The argument @var{function} must be a function that can take one
-argument and return a string. The argument @var{sequence} can be any
-kind of sequence except a char-table; that is, a list, a vector, a
-bool-vector, or a string.
+argument and returns a sequence of characters: a string, a vector, or
+a list. The argument @var{sequence} can be any kind of sequence
+except a char-table; that is, a list, a vector, a bool-vector, or a
+string.
@example
@group
diff --git a/doc/lispref/positions.texi b/doc/lispref/positions.texi
index 72b76ce5c8..9daf5cef05 100644
--- a/doc/lispref/positions.texi
+++ b/doc/lispref/positions.texi
@@ -572,6 +572,18 @@ The value returned is the window line number point has moved to, with
the top line in the window numbered 0.
@end deffn
+@vindex move-to-window-group-line-function
+@defun move-to-window-group-line count
+This function is like @code{move-to-window-line}, except that when the
+selected window is a part of a group of windows (@pxref{Window
+Group}), @code{move-to-window-group-line} will move to a position with
+respect to the entire group, not just the single window. This
+condition holds when the buffer local variable
+@code{move-to-window-group-line-function} is set to a function. In
+this case, @code{move-to-window-group-line} calls the function with
+the argument @var{count}, then returns its result.
+@end defun
+
@defun compute-motion from frompos to topos width offsets window
This function scans the current buffer, calculating screen positions.
It scans the buffer forward from position @var{from}, assuming that is
diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 5c7947eeca..e45201b057 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -133,6 +133,30 @@ This function returns the selected window (which is always a live
window).
@end defun
+@anchor{Window Group}Sometimes several windows collectively and
+cooperatively display a buffer, for example, under the management of
+Follow Mode (@pxref{Follow Mode,,, emacs}), where the windows together
+display a bigger portion of the buffer than one window could alone.
+It is often useful to consider such a @dfn{window group} as a single
+entity. Several functions such as @code{window-group-start}
+(@pxref{Window Start and End}) allow you to do this by supplying, as
+an argument, one of the windows as a stand in for the whole group.
+
+@defun selected-window-group
+@vindex selected-window-group-function
+When the selected window is a member of a group of windows, this
+function returns a list of the windows in the group, ordered such that
+the first window in the list is displaying the earliest part of the
+buffer, and so on. Otherwise the function returns a list containing
+just the selected window.
+
+The selected window is considered part of a group when the buffer
+local variable @code{selected-window-group-function} is set to a
+function. In this case, @code{selected-window-group} calls it with no
+arguments and returns its result (which should be the list of windows
+in the group).
+@end defun
+
@node Windows and Frames
@section Windows and Frames
@@ -3098,6 +3122,17 @@ window-start position; if you move point, do not expect the window-start
position to change in response until after the next redisplay.
@end defun
+@defun window-group-start &optional window
+@vindex window-group-start-function
+This function is like @code{window-start}, except that when
+@var{window} is a part of a group of windows (@pxref{Window Group}),
+@code{window-group-start} returns the start position of the entire
+group. This condition holds when the buffer local variable
+@code{window-group-start-function} is set to a function. In this
+case, @code{window-group-start} calls the function with the single
+argument @var{window}, then returns its result.
+@end defun
+
@cindex window end position
@defun window-end &optional window update
This function returns the position where display of its buffer ends in
@@ -3124,6 +3159,18 @@ way real redisplay would do. It does not alter the
text will end if scrolling is not required.
@end defun
+@vindex window-group-end-function
+@defun window-group-end window update
+This function is like @code{window-end}, except that when @var{window}
+is a part of a group of windows (@pxref{Window Group}),
+@code{window-group-end} returns the end position of the entire group.
+This condition holds when the buffer local variable
+@code{window-group-end-function} is set to a function. In this case,
+@code{window-group-end} calls the function with the two arguments
+@var{window} and @var{update}, then returns its result. The argument
+@var{update} has the same meaning as in @code{window-end}.
+@end defun
+
@defun set-window-start window position &optional noforce
This function sets the display-start position of @var{window} to
@var{position} in @var{window}'s buffer. It returns @var{position}.
@@ -3187,6 +3234,19 @@ off screen at the next redisplay, then redisplay computes a new window-start
position that works well with point, and thus @var{position} is not used.
@end defun
+@vindex set-window-group-start-function
+@defun set-window-group-start window position &optional noforce
+This function is like @code{set-window-start}, except that when
+@var{window} is a part of a group of windows (@pxref{Window Group}),
+@code{set-window-group-start} sets the start position of the entire
+group. This condition holds when the buffer local variable
+@code{set-window-group-start-function} is set to a function. In this
+case, @code{set-window-group-start} calls the function with the three
+arguments @var{window}, @var{position}, and @var{noforce}, then
+returns its result. The arguments @var{position} and @var{noforce} in
+this function have the same meaning as in @code{set-window-start}.
+@end defun
+
@defun pos-visible-in-window-p &optional position window partially
This function returns non-@code{nil} if @var{position} is within the
range of text currently visible on the screen in @var{window}. It
@@ -3228,6 +3288,21 @@ Here is an example:
@end example
@end defun
+@vindex pos-visible-in-window-group-p-function
+@defun pos-visible-in-window-group-p &optional position window partially
+This function is like @code{pos-visible-in-window-p}, except that when
+@var{window} is a part of a group of windows (@pxref{Window Group}),
+@code{pos-visible-in-window-group-p} tests the visibility of @var{pos}
+in the entire group, not just in the single @var{window}. This
+condition holds when the buffer local variable
+@code{pos-visible-in-window-group-p-function} is set to a function.
+In this case @code{pos-visible-in-window-group-p} calls the function
+with the three arguments @var{position}, @var{window}, and
+@var{partially}, then returns its result. The arguments
+@var{position} and @var{partially} have the same meaning as in
+@code{pos-visible-in-window-p}.
+@end defun
+
@defun window-line-height &optional line window
This function returns the height of text line @var{line} in
@var{window}. If @var{line} is one of @code{header-line} or
@@ -3471,6 +3546,19 @@ the top of the window. The command @code{recenter-top-bottom} offers
a more convenient way to achieve this.
@end deffn
+@vindex recenter-window-group-function
+@defun recenter-window-group &optional count
+This function is like @code{recenter}, except that when the selected
+window is part of a group of windows (@pxref{Window Group}),
+@code{recenter-window-group} scrolls the entire group. This condition
+holds when the buffer local variable
+@code{recenter-window-group-function} is set to a function. In this
+case, @code{recenter-window-group} calls the function with the
+argument @var{count}, then returns its result. The argument
+@var{count} has the same meaning as in @code{recenter}, but with
+respect to the entire window group.
+@end defun
+
@defopt recenter-redisplay
If this variable is non-@code{nil}, calling @code{recenter} with a
@code{nil} argument redraws the frame. The default value is
diff --git a/lisp/follow.el b/lisp/follow.el
index 37de923e6a..71e8824947 100644
--- a/lisp/follow.el
+++ b/lisp/follow.el
@@ -421,7 +421,21 @@ Keys specific to Follow mode:
(progn
(add-hook 'compilation-filter-hook 'follow-align-compilation-windows t t)
(add-hook 'post-command-hook 'follow-post-command-hook t)
- (add-hook 'window-size-change-functions 'follow-window-size-change t))
+ (add-hook 'window-size-change-functions 'follow-window-size-change t)
+ (add-hook 'after-change-functions 'follow-after-change nil t)
+ (add-hook 'isearch-update-post-hook 'follow-post-command-hook nil t)
+ (add-hook 'replace-update-post-hook 'follow-post-command-hook nil t)
+ (add-hook 'ispell-update-post-hook 'follow-post-command-hook nil t)
+
+ (setq window-group-start-function 'follow-window-start)
+ (setq window-group-end-function 'follow-window-end)
+ (setq set-window-group-start-function 'follow-set-window-start)
+ (setq recenter-window-group-function 'follow-recenter)
+ (setq pos-visible-in-window-group-p-function
+ 'follow-pos-visible-in-window-p)
+ (setq selected-window-group-function 'follow-all-followers)
+ (setq move-to-window-group-line-function 'follow-move-to-window-line))
+
;; Remove globally-installed hook functions only if there is no
;; other Follow mode buffer.
(let ((buffers (buffer-list))
@@ -432,6 +446,19 @@ Keys specific to Follow mode:
(unless following
(remove-hook 'post-command-hook 'follow-post-command-hook)
(remove-hook 'window-size-change-functions 'follow-window-size-change)))
+
+ (kill-local-variable 'move-to-window-group-line-function)
+ (kill-local-variable 'selected-window-group-function)
+ (kill-local-variable 'pos-visible-in-window-group-p-function)
+ (kill-local-variable 'recenter-window-group-function)
+ (kill-local-variable 'set-window-group-start-function)
+ (kill-local-variable 'window-group-end-function)
+ (kill-local-variable 'window-group-start-function)
+
+ (remove-hook 'ispell-update-post-hook 'follow-post-command-hook t)
+ (remove-hook 'replace-update-post-hook 'follow-post-command-hook t)
+ (remove-hook 'isearch-update-post-hook 'follow-post-command-hook t)
+ (remove-hook 'after-change-functions 'follow-after-change t)
(remove-hook 'compilation-filter-hook 'follow-align-compilation-windows t)))
(defun follow-find-file-hook ()
@@ -1015,6 +1042,10 @@ Otherwise, return nil."
;; is nil. Start every window directly after the end of the previous
;; window, to make sure long lines are displayed correctly.
+(defvar follow-start-end-invalid t
+ "When non-nil, indicates `follow-windows-start-end-cache' is invalid.")
+(make-variable-buffer-local 'follow-start-end-invalid)
+
(defun follow-redisplay (&optional windows win preserve-win)
"Reposition the WINDOWS around WIN.
Should point be too close to the roof we redisplay everything
@@ -1047,7 +1078,8 @@ repositioning the other windows."
(dolist (w windows)
(unless (and preserve-win (eq w win))
(set-window-start w start))
- (setq start (car (follow-calc-win-end w))))))
+ (setq start (car (follow-calc-win-end w))))
+ (setq follow-start-end-invalid nil)))
(defun follow-estimate-first-window-start (windows win start)
"Estimate the position of the first window.
@@ -1443,6 +1475,146 @@ non-first windows in Follow mode."
(add-hook 'window-scroll-functions 'follow-avoid-tail-recenter t)
+;;; Low level window start and end.
+
+;; These routines are the Follow Mode versions of the low level
+;; functions described on page "Window Start and End" of the elisp
+;; manual, e.g. `window-group-start'. The aim is to be able to handle
+;; Follow Mode windows by replacing `window-start' by
+;; `window-group-start', etc.
+
+(defun follow-after-change (_beg _end _old-len)
+ "After change function: set `follow-start-end-invalid'."
+ (setq follow-start-end-invalid t))
+
+(defun follow-window-start (&optional window)
+ "Return position at which display currently starts in the
+Follow Mode group of windows which includes WINDOW.
+
+WINDOW must be a live window and defaults to the selected one.
+This is updated by redisplay or by calling
+`follow-set-window-start'."
+ (let ((windows (follow-all-followers window)))
+ (window-start (car windows))))
+
+(defun follow-window-end (&optional window update)
+ "Return position at which display currently ends in the Follow
+ Mode group of windows which includes WINDOW.
+
+ WINDOW must be a live window and defaults to the selected one.
+ This is updated by redisplay, when it runs to completion.
+ Simply changing the buffer text or setting `window-start' does
+ not update this value.
+
+ Return nil if there is no recorded value. (This can happen if
+ the last redisplay of WINDOW was preempted, and did not
+ finish.) If UPDATE is non-nil, compute the up-to-date position
+ if it isn't already recorded."
+ (let* ((windows (follow-all-followers window))
+ (last (car (last windows))))
+ (when (and update follow-start-end-invalid)
+ (follow-redisplay windows (car windows)))
+ (window-end last update)))
+
+(defun follow-set-window-start (window pos &optional noforce)
+ "Make display in the Follow Mode group of windows which includes
+WINDOW start at position POS in WINDOW's buffer.
+
+WINDOW must be a live window and defaults to the selected one. Return
+POS. Optional third arg NOFORCE non-nil inhibits next redisplay from
+overriding motion of point in order to display at this exact start."
+ (let ((windows (follow-all-followers window)))
+ (setq follow-start-end-invalid t)
+ (set-window-start (car windows) pos noforce)))
+
+(defun follow-pos-visible-in-window-p (&optional pos window partially)
+ "Return non-nil if position POS is currently on the frame in one of
+ the windows in the Follow Mode group which includes WINDOW.
+
+WINDOW must be a live window and defaults to the selected one.
+
+Return nil if that position is scrolled vertically out of view. If a
+character is only partially visible, nil is returned, unless the
+optional argument PARTIALLY is non-nil. If POS is only out of view
+because of horizontal scrolling, return non-nil. If POS is t, it
+specifies the position of the last visible glyph in WINDOW. POS
+defaults to point in WINDOW; WINDOW defaults to the selected window.
+
+If POS is visible, return t if PARTIALLY is nil; if PARTIALLY is non-nil,
+the return value is a list of 2 or 6 elements (X Y [RTOP RBOT ROWH VPOS]),
+where X and Y are the pixel coordinates relative to the top left corner
+of the actual window containing it. The remaining elements are
+omitted if the character after POS is fully visible; otherwise, RTOP
+and RBOT are the number of pixels off-window at the top and bottom of
+the screen line (\"row\") containing POS, ROWH is the visible height
+of that row, and VPOS is the row number \(zero-based)."
+ (let* ((windows (follow-all-followers window))
+ (last (car (last windows))))
+ (when follow-start-end-invalid
+ (follow-redisplay windows (car windows)))
+ (let* ((cache (follow-windows-start-end windows))
+ (last-elt (car (last cache)))
+ our-pos pertinent-elt)
+ (setq pertinent-elt
+ (if (eq pos t)
+ last-elt
+ (setq our-pos (or pos (point)))
+ (catch 'element
+ (while cache
+ (when (< our-pos (nth 2 (car cache)))
+ (throw 'element (car cache)))
+ (setq cache (cdr cache)))
+ last-elt)))
+ (pos-visible-in-window-p our-pos (car pertinent-elt) partially))))
+
+(defun follow-move-to-window-line (arg)
+ "Position point relative to the Follow mode group containing the selected window.
+ARG nil means position point at center of the window group.
+Else, ARG specifies vertical position within the window group;
+zero means top of the first window in the group, negative means
+ relative to bottom of the last window in the group."
+ (let* ((windows (follow-all-followers))
+ (start-end (follow-windows-start-end windows))
+ (rev-start-end (reverse start-end))
+ (lines 0)
+ middle-window elt count)
+ (select-window
+ (cond
+ ((null arg)
+ (setq rev-start-end (nthcdr (/ (length windows) 2) rev-start-end))
+ (prog1 (car (car rev-start-end))
+ (while (setq rev-start-end (cdr rev-start-end))
+ (setq elt (car rev-start-end)
+ count (count-screen-lines (cadr elt) (nth 2 elt)
+ nil (car elt))
+ lines (+ lines count)))))
+ ((>= arg 0)
+ (while (and (cdr start-end)
+ (progn
+ (setq elt (car start-end)
+ count (count-screen-lines (cadr elt) (nth 2 elt)
+ nil (car elt)))
+ (>= arg count)))
+ (setq arg (- arg count)
+ lines (+ lines count)
+ start-end (cdr start-end)))
+ (car (car start-end)))
+ (t ; (< arg 0)
+ (while (and (cadr rev-start-end)
+ (progn
+ (setq elt (car rev-start-end)
+ count (count-lines (cadr elt) (nth 2 elt)))
+ (<= arg (- count))))
+ (setq arg (+ arg count)
+ rev-start-end (cdr rev-start-end)))
+ (prog1 (car (car rev-start-end))
+ (while (setq rev-start-end (cdr rev-start-end))
+ (setq elt (car rev-start-end)
+ count (count-screen-lines (cadr elt) (nth 2 elt)
+ nil (car elt))
+ lines (+ lines count)))))))
+ (+ lines (move-to-window-line arg))))
+
;;; Profile support
;; The following (non-evaluated) section can be used to
diff --git a/lisp/isearch.el b/lisp/isearch.el
index 8c98d36f4a..05dc2931d9 100644
--- a/lisp/isearch.el
+++ b/lisp/isearch.el
@@ -961,7 +961,8 @@ used to set the value of `isearch-regexp-function'."
(defun isearch-update ()
"This is called after every isearch command to update the display.
-The last thing it does is to run `isearch-update-post-hook'."
+The second last thing it does is to run `isearch-update-post-hook'.
+The last thing is to trigger a new round of lazy highlighting."
(unless (eq (current-buffer) isearch--current-buffer)
(when (buffer-live-p isearch--current-buffer)
(with-current-buffer isearch--current-buffer
@@ -978,12 +979,10 @@ The last thing it does is to run `isearch-update-post-hook'."
(null executing-kbd-macro))
(progn
(if (not (input-pending-p))
- (if isearch-message-function
- (funcall isearch-message-function)
- (isearch-message)))
+ (funcall (or isearch-message-function #'isearch-message)))
(if (and isearch-slow-terminal-mode
(not (or isearch-small-window
- (pos-visible-in-window-p))))
+ (pos-visible-in-window-group-p))))
(let ((found-point (point)))
(setq isearch-small-window t)
(move-to-window-line 0)
@@ -1004,10 +1003,10 @@ The last thing it does is to run `isearch-update-post-hook'."
(let ((current-scroll (window-hscroll))
visible-p)
(set-window-hscroll (selected-window) isearch-start-hscroll)
- (setq visible-p (pos-visible-in-window-p nil nil t))
+ (setq visible-p (pos-visible-in-window-group-p nil nil t))
(if (or (not visible-p)
;; When point is not visible because of hscroll,
- ;; pos-visible-in-window-p returns non-nil, but
+ ;; pos-visible-in-window-group-p returns non-nil, but
;; the X coordinate it returns is 1 pixel beyond
;; the last visible one.
(>= (car visible-p) (window-body-width nil t)))
@@ -1020,12 +1019,12 @@ The last thing it does is to run `isearch-update-post-hook'."
(setq ;; quit-flag nil not for isearch-mode
isearch-adjusted nil
isearch-yank-flag nil)
- (when isearch-lazy-highlight
- (isearch-lazy-highlight-new-loop))
;; We must prevent the point moving to the end of composition when a
;; part of the composition has just been searched.
(setq disable-point-adjustment t)
- (run-hooks 'isearch-update-post-hook))
+ (run-hooks 'isearch-update-post-hook)
+ (when isearch-lazy-highlight
+ (isearch-lazy-highlight-new-loop)))
(defun isearch-done (&optional nopush edit)
"Exit Isearch mode.
@@ -1058,7 +1057,7 @@ NOPUSH is t and EDIT is t."
(setq minibuffer-message-timeout isearch-original-minibuffer-message-timeout)
(isearch-dehighlight)
(lazy-highlight-cleanup lazy-highlight-cleanup)
- (let ((found-start (window-start))
+ (let ((found-start (window-group-start))
(found-point (point)))
(when isearch-window-configuration
(set-window-configuration isearch-window-configuration)
@@ -1068,7 +1067,7 @@ NOPUSH is t and EDIT is t."
;; This has an annoying side effect of clearing the last_modiff
;; field of the window, which can cause unwanted scrolling,
;; so don't do it unless truly necessary.
- (set-window-start (selected-window) found-start t))))
+ (set-window-group-start (selected-window) found-start t))))
(setq isearch-mode nil)
(if isearch-input-method-local-p
@@ -1299,13 +1298,6 @@ You can update the global isearch variables by setting new values to
(unwind-protect
(progn ,@body)
- ;; Set point at the start (end) of old match if forward (backward),
- ;; so after exiting minibuffer isearch resumes at the start (end)
- ;; of this match and can find it again.
- (if (and old-other-end (eq old-point (point))
- (eq isearch-forward isearch-new-forward))
- (goto-char old-other-end))
-
;; Always resume isearching by restarting it.
(isearch-mode isearch-forward
isearch-regexp
@@ -1318,7 +1310,17 @@ You can update the global isearch variables by setting new values to
isearch-message isearch-new-message
isearch-forward isearch-new-forward
isearch-regexp-function isearch-new-regexp-function
- isearch-case-fold-search isearch-new-case-fold))
+ isearch-case-fold-search isearch-new-case-fold)
+
+ ;; Restore the minibuffer message before moving point.
+ (funcall (or isearch-message-function #'isearch-message) nil t)
+
+ ;; Set point at the start (end) of old match if forward (backward),
+ ;; so after exiting minibuffer isearch resumes at the start (end)
+ ;; of this match and can find it again.
+ (if (and old-other-end (eq old-point (point))
+ (eq isearch-forward isearch-new-forward))
+ (goto-char old-other-end)))
;; Empty isearch-string means use default.
(when (= 0 (length isearch-string))
@@ -1931,6 +1933,8 @@ If search string is empty, just beep."
(length isearch-string))))
isearch-message (mapconcat 'isearch-text-char-description
isearch-string "")))
+ ;; Do the following before moving point.
+ (funcall (or isearch-message-function #'isearch-message) nil t)
;; Use the isearch-other-end as new starting point to be able
;; to find the remaining part of the search string again.
;; This is like what `isearch-search-and-update' does,
@@ -2107,6 +2111,8 @@ With argument, add COUNT copies of the character."
(setq isearch-case-fold-search
(isearch-no-upper-case-p isearch-string isearch-regexp))))
;; Not regexp, not reverse, or no match at point.
+ ;; Do the following before moving point.
+ (funcall (or isearch-message-function #'isearch-message) nil t)
(if (and isearch-other-end (not isearch-adjusted))
(goto-char (if isearch-forward isearch-other-end
(min isearch-opoint
@@ -2273,10 +2279,12 @@ Return nil if it's completely visible, or if point is visible,
together with as much of the search string as will fit; the symbol
`above' if we need to scroll the text downwards; the symbol `below',
if upwards."
- (let ((w-start (window-start))
- (w-end (window-end nil t))
- (w-L1 (save-excursion (move-to-window-line 1) (point)))
- (w-L-1 (save-excursion (move-to-window-line -1) (point)))
+ (let ((w-start (window-group-start))
+ (w-end (window-group-end nil t))
+ (w-L1 (save-excursion
+ (save-selected-window (move-to-window-group-line 1) (point))))
+ (w-L-1 (save-excursion
+ (save-selected-window (move-to-window-group-line -1) (point))))
start end) ; start and end of search string in buffer
(if isearch-forward
(setq end isearch-point start (or isearch-other-end isearch-point))
@@ -2303,15 +2311,15 @@ the bottom."
(if above
(progn
(goto-char start)
- (recenter 0)
- (when (>= isearch-point (window-end nil t))
+ (recenter-window-group 0)
+ (when (>= isearch-point (window-group-end nil t))
(goto-char isearch-point)
- (recenter -1)))
+ (recenter-window-group -1)))
(goto-char end)
- (recenter -1)
- (when (< isearch-point (window-start))
+ (recenter-window-group -1)
+ (when (< isearch-point (window-group-start))
(goto-char isearch-point)
- (recenter 0))))
+ (recenter-window-group 0))))
(goto-char isearch-point))
(defvar isearch-pre-scroll-point nil)
@@ -2458,6 +2466,7 @@ Search is updated accordingly."
(isearch-ring-adjust1 advance)
(if search-ring-update
(progn
+ (funcall (or isearch-message-function #'isearch-message) nil t)
(isearch-search)
(isearch-push-state)
(isearch-update))
@@ -2530,6 +2539,13 @@ If there is no completion possible, say so and continue searching."
(defun isearch-message (&optional c-q-hack ellipsis)
;; Generate and print the message string.
+
+ ;; N.B.: This function should always be called with point at the
+ ;; search point, because in certain (rare) circumstances, undesired
+ ;; scrolling can happen when point is elsewhere. These
+ ;; circumstances are when follow-mode is active, the search string
+ ;; spans two (or several) windows, and the message about to be
+ ;; displayed will cause the echo area to expand.
(let ((cursor-in-echo-area ellipsis)
(m isearch-message)
(fail-pos (isearch-fail-pos t)))
@@ -2723,9 +2739,6 @@ update the match data, and return point."
(defun isearch-search ()
;; Do the search with the current search string.
- (if isearch-message-function
- (funcall isearch-message-function nil t)
- (isearch-message nil t))
(if (and (eq isearch-case-fold-search t) search-upper-case)
(setq isearch-case-fold-search
(isearch-no-upper-case-p isearch-string isearch-regexp)))
@@ -3023,6 +3036,7 @@ since they have special meaning in a regexp."
(defvar isearch-lazy-highlight-timer nil)
(defvar isearch-lazy-highlight-last-string nil)
(defvar isearch-lazy-highlight-window nil)
+(defvar isearch-lazy-highlight-window-group nil)
(defvar isearch-lazy-highlight-window-start nil)
(defvar isearch-lazy-highlight-window-end nil)
(defvar isearch-lazy-highlight-case-fold-search nil)
@@ -3064,8 +3078,8 @@ by other Emacs features."
(sit-for 0) ;make sure (window-start) is credible
(or (not (equal isearch-string
isearch-lazy-highlight-last-string))
- (not (eq (selected-window)
- isearch-lazy-highlight-window))
+ (not (memq (selected-window)
+ isearch-lazy-highlight-window-group))
(not (eq isearch-lazy-highlight-case-fold-search
isearch-case-fold-search))
(not (eq isearch-lazy-highlight-regexp
@@ -3076,9 +3090,9 @@ by other Emacs features."
isearch-lax-whitespace))
(not (eq isearch-lazy-highlight-regexp-lax-whitespace
isearch-regexp-lax-whitespace))
- (not (= (window-start)
+ (not (= (window-group-start)
isearch-lazy-highlight-window-start))
- (not (= (window-end) ; Window may have been split/joined.
+ (not (= (window-group-end) ; Window may have been split/joined.
isearch-lazy-highlight-window-end))
(not (eq isearch-forward
isearch-lazy-highlight-forward))
@@ -3086,7 +3100,7 @@ by other Emacs features."
(not (equal isearch-error
isearch-lazy-highlight-error))))
;; something important did indeed change
- (lazy-highlight-cleanup t) ;kill old loop & remove overlays
+ (lazy-highlight-cleanup t) ;kill old loop & remove overlays
(setq isearch-lazy-highlight-error isearch-error)
;; It used to check for `(not isearch-error)' here, but actually
;; lazy-highlighting might find matches to highlight even when
@@ -3094,8 +3108,9 @@ by other Emacs features."
(setq isearch-lazy-highlight-start-limit beg
isearch-lazy-highlight-end-limit end)
(setq isearch-lazy-highlight-window (selected-window)
- isearch-lazy-highlight-window-start (window-start)
- isearch-lazy-highlight-window-end (window-end)
+ isearch-lazy-highlight-window-group (selected-window-group)
+ isearch-lazy-highlight-window-start (window-group-start)
+ isearch-lazy-highlight-window-end (window-group-end)
;; Start lazy-highlighting at the beginning of the found
;; match (`isearch-other-end'). If no match, use point.
;; One of the next two variables (depending on search direction)
@@ -3113,10 +3128,10 @@ by other Emacs features."
isearch-lazy-highlight-regexp-lax-whitespace isearch-regexp-lax-whitespace
isearch-lazy-highlight-regexp-function isearch-regexp-function
isearch-lazy-highlight-forward isearch-forward)
- (unless (equal isearch-string "")
- (setq isearch-lazy-highlight-timer
- (run-with-idle-timer lazy-highlight-initial-delay nil
- 'isearch-lazy-highlight-update)))))
+ (unless (equal isearch-string "")
+ (setq isearch-lazy-highlight-timer
+ (run-with-idle-timer lazy-highlight-initial-delay nil
+ 'isearch-lazy-highlight-update)))))
(defun isearch-lazy-highlight-search ()
"Search ahead for the next or previous match, for lazy highlighting.
@@ -3139,13 +3154,13 @@ Attempt to do the search exactly the way the pending Isearch would."
(+ isearch-lazy-highlight-start
;; Extend bound to match whole string at point
(1- (length isearch-lazy-highlight-last-string)))
- (window-end)))
+ (window-group-end)))
(max (or isearch-lazy-highlight-start-limit (point-min))
(if isearch-lazy-highlight-wrapped
(- isearch-lazy-highlight-end
;; Extend bound to match whole string at point
(1- (length isearch-lazy-highlight-last-string)))
- (window-start))))))
+ (window-group-start))))))
;; Use a loop like in `isearch-search'.
(while retry
(setq success (isearch-search-string
@@ -3169,7 +3184,7 @@ Attempt to do the search exactly the way the pending Isearch would."
(with-local-quit
(save-selected-window
(if (and (window-live-p isearch-lazy-highlight-window)
- (not (eq (selected-window) isearch-lazy-highlight-window)))
+ (not (memq (selected-window) isearch-lazy-highlight-window-group)))
(select-window isearch-lazy-highlight-window))
(save-excursion
(save-match-data
@@ -3189,12 +3204,12 @@ Attempt to do the search exactly the way the pending Isearch would."
(if isearch-lazy-highlight-forward
(if (= mb (if isearch-lazy-highlight-wrapped
isearch-lazy-highlight-start
- (window-end)))
+ (window-group-end)))
(setq found nil)
(forward-char 1))
(if (= mb (if isearch-lazy-highlight-wrapped
isearch-lazy-highlight-end
- (window-start)))
+ (window-group-start)))
(setq found nil)
(forward-char -1)))
@@ -3204,8 +3219,8 @@ Attempt to do the search exactly the way the pending Isearch would."
;; 1000 is higher than ediff's 100+,
;; but lower than isearch main overlay's 1001
(overlay-put ov 'priority 1000)
- (overlay-put ov 'face lazy-highlight-face)
- (overlay-put ov 'window (selected-window))))
+ (overlay-put ov 'face lazy-highlight-face)))
+ ;(overlay-put ov 'window (selected-window))))
;; Remember the current position of point for
;; the next call of `isearch-lazy-highlight-update'
;; when `lazy-highlight-max-at-a-time' is too small.
@@ -3221,12 +3236,12 @@ Attempt to do the search exactly the way the pending Isearch would."
(setq isearch-lazy-highlight-wrapped t)
(if isearch-lazy-highlight-forward
(progn
- (setq isearch-lazy-highlight-end (window-start))
+ (setq isearch-lazy-highlight-end (window-group-start))
(goto-char (max (or isearch-lazy-highlight-start-limit (point-min))
- (window-start))))
- (setq isearch-lazy-highlight-start (window-end))
+ (window-group-start))))
+ (setq isearch-lazy-highlight-start (window-group-end))
(goto-char (min (or isearch-lazy-highlight-end-limit (point-max))
- (window-end))))))))
+ (window-group-end))))))))
(unless nomore
(setq isearch-lazy-highlight-timer
(run-at-time lazy-highlight-interval nil
diff --git a/lisp/replace.el b/lisp/replace.el
index 54b3a71bda..d48f4f3fdf 100644
--- a/lisp/replace.el
+++ b/lisp/replace.el
@@ -2011,6 +2011,9 @@ passed in. If LITERAL is set, no checking is done, anyway."
(when backward (goto-char (nth 0 match-data)))
noedit)
+(defvar replace-update-post-hook nil
+ "Function(s) to call after query-replace has found a match in the buffer.")
+
(defvar replace-search-function nil
"Function to use when searching for strings to replace.
It is used by `query-replace' and `replace-string', and is called
@@ -2264,7 +2267,7 @@ It must return a string."
(and nonempty-match
(or (not regexp-flag)
(and (if backward
- (looking-back search-string)
+ (looking-back search-string nil)
(looking-at search-string))
(let ((match (match-data)))
(and (/= (nth 0 match) (nth 1 match))
@@ -2318,7 +2321,8 @@ It must return a string."
;; `real-match-data'.
(while (not done)
(set-match-data real-match-data)
- (replace-highlight
+ (run-hooks 'replace-update-post-hook) ; Before `replace-highlight'.
+ (replace-highlight
(match-beginning 0) (match-end 0)
start end search-string
regexp-flag delimited-flag case-fold-search backward)
diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el
index aa51446d6b..05a5da57b6 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -1782,6 +1782,51 @@ Extended character mode can be changed for this buffer by placing
a `~' followed by an extended-character mode -- such as `~.tex'.
The last occurring definition in the buffer will be used.")
+(defun ispell--\\w-filter (char)
+ "Return CHAR in a string when CHAR doesn't have \"word\" syntax,
+nil otherwise. CHAR must be a character."
+ (let ((str (string char)))
+ (and
+ (not (string-match "\\w" str))
+ str)))
+
+(defun ispell--make-\\w-expression (chars)
+ "Make a regular expression like \"\\(\\w\\|[-_]\\)\".
+This (parenthesized) expression matches either a character of
+\"word\" syntax or one in CHARS.
+
+CHARS is a string of characters. A member of CHARS is omitted
+from the expression if it already has word syntax. (Be careful
+about special characters such as ?\\, ?^, ?], and ?- in CHARS.)
+If after this filtering there are no chars left, or only one, a
+special form of the expression is generated."
+ (let ((filtered
+ (mapconcat #'ispell--\\w-filter chars "")))
+ (concat
+ "\\(\\w"
+ (cond
+ ((equal filtered "")
+ "\\)")
+ ((eq (length filtered) 1)
+ (concat "\\|" filtered "\\)"))
+ (t
+ (concat "\\|[" filtered "]\\)"))))))
+
+(defun ispell--make-filename-or-URL-re ()
+ "Construct a regexp to match some file names or URLs or email addresses.
+The expression is crafted to match as great a variety of these
+objects as practicable, without too many false matches happening."
+ (concat ;"\\(--+\\|_+\\|"
+ "\\(/\\w\\|\\("
+ (ispell--make-\\w-expression "-_")
+ "+[.:@]\\)\\)"
+ (ispell--make-\\w-expression "-_")
+ "*\\([.:/@]+"
+ (ispell--make-\\w-expression "-_~=?&")
+ "+\\)+"
+ ;"\\)"
+ ))
+
;;;###autoload
(defvar ispell-skip-region-alist
`((ispell-words-keyword forward-line)
@@ -1798,7 +1843,7 @@ The last occurring definition in the buffer will be used.")
;; Matches e-mail addresses, file names, http addresses, etc. The
;; `-+' `_+' patterns are necessary for performance reasons when
;; `-' or `_' part of word syntax.
- (,(purecopy "\\(--+\\|_+\\|\\(/\\w\\|\\(\\(\\w\\|[-_]\\)+[.:@]\\)\\)\\(\\w\\|[-_]\\)*\\([.:/@]+\\(\\w\\|[-_~=?&]\\)+\\)+\\)"))
+; (,(purecopy "\\(--+\\|_+\\|\\(/\\w\\|\\(\\(\\w\\|[-_]\\)+[.:@]\\)\\)\\(\\w\\|[-_]\\)*\\([.:/@]+\\(\\w\\|[-_~=?&]\\)+\\)+\\)"))
;; above checks /.\w sequences
;;("\\(--+\\|\\(/\\|\\(\\(\\w\\|[-_]\\)+[.:@]\\)\\)\\(\\w\\|[-_]\\)*\\([.:/@]+\\(\\w\\|[-_~=?&]\\)+\\)+\\)")
;; This is a pretty complex regexp. It can be simplified to the following:
@@ -2248,6 +2293,11 @@ If so, ask if it needs to be saved."
(setq ispell-pdict-modified-p nil))
+(defvar ispell-update-post-hook nil
+ "A normal hook invoked from the ispell command loop.
+It is called once per iteration, before displaying a prompt to
+the user.")
+
(defun ispell-command-loop (miss guess word start end)
"Display possible corrections from list MISS.
GUESS lists possibly valid affix construction of WORD.
@@ -2315,8 +2365,10 @@ Global `ispell-quit' set to start location to continue spell session."
count (ispell-int-char (1+ count))))
(setq count (ispell-int-char (- count ?0 skipped))))
+ (run-hooks 'ispell-update-post-hook)
+
;; ensure word is visible
- (if (not (pos-visible-in-window-p end))
+ (if (not (pos-visible-in-window-group-p end))
(sit-for 0))
;; Display choices for misspelled word.
@@ -2845,13 +2897,20 @@ Also position fit window to BUFFER and select it."
(prog1
(condition-case nil
(split-window
- nil (- ispell-choices-win-default-height) 'above)
+ ;; Chose the last of a window group, since
+ ;; otherwise, the lowering of another window's
+ ;; TL corner would cause the logical order of
+ ;; the windows to be changed.
+ (car (last (selected-window-group)))
+ (- ispell-choices-win-default-height) 'above)
(error nil))
(modify-frame-parameters frame '((unsplittable . t))))))
(and (not unsplittable)
(condition-case nil
(split-window
- nil (- ispell-choices-win-default-height) 'above)
+ ;; See comment above.
+ (car (last (selected-window-group)))
+ (- ispell-choices-win-default-height) 'above)
(error nil)))
(display-buffer buffer))))
(if (not window)
@@ -3374,7 +3433,8 @@ Must be called after `ispell-buffer-local-parsing' due to dependence on mode."
(if (string= "" comment-end) "^" (regexp-quote comment-end)))
(if (and (null ispell-check-comments) comment-start)
(regexp-quote comment-start))
- (ispell-begin-skip-region ispell-skip-region-alist)))
+ (ispell-begin-skip-region ispell-skip-region-alist)
+ (ispell--make-filename-or-URL-re)))
"\\|"))
@@ -3413,6 +3473,8 @@ Manual checking must include comments and tib references.
The list is of the form described by variable `ispell-skip-region-alist'.
Must be called after `ispell-buffer-local-parsing' due to dependence on mode."
(let ((skip-alist ispell-skip-region-alist))
+ (setq skip-alist (append (list (list (ispell--make-filename-or-URL-re)))
+ skip-alist))
;; only additional explicit region definition is tex.
(if (eq ispell-parser 'tex)
(setq case-fold-search nil
@@ -4106,9 +4168,10 @@ You can bind this to the key C-c i in GNUS or mail by adding to
(ispell-non-empty-string vm-included-text-prefix)))
(t default-prefix)))
(ispell-skip-region-alist
- (cons (list (concat "^\\(" cite-regexp "\\)")
- (function forward-line))
- ispell-skip-region-alist))
+ (cons (list (ispell--make-filename-or-URL-re))
+ (cons (list (concat "^\\(" cite-regexp "\\)")
+ (function forward-line))
+ ispell-skip-region-alist)))
(old-case-fold-search case-fold-search)
(dictionary-alist ispell-message-dictionary-alist)
(ispell-checking-message t))
diff --git a/lisp/window.el b/lisp/window.el
index 54ac811572..c57fef441f 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -7870,6 +7870,152 @@ Return non-nil if the window was shrunk, nil otherwise."
(remove-hook 'kill-buffer-hook delete-window-hook t))))))
+;;;
+;; Groups of windows (Follow Mode).
+;;
+;; This section of functions extends the functionality of some window
+;; manipulating commands to groups of windows co-operatively
+;; displaying a buffer, typically with Follow Mode.
+;;
+;; The xxx-function variables are permanent locals so that their local
+;; status is undone only when explicitly programmed, not when a buffer
+;; is reverted or a mode function is called.
+
+(defvar window-group-start-function nil)
+(make-variable-buffer-local 'window-group-start-function)
+(put 'window-group-start-function 'permanent-local t)
+(defun window-group-start (&optional window)
+ "Return position at which display currently starts in the group of
+windows containing WINDOW. When a grouping mode (such as Follow Mode)
+is not active, this function is identical to `window-start'.
+
+WINDOW must be a live window and defaults to the selected one.
+This is updated by redisplay or by calling `set-window*-start'."
+ (if (functionp window-group-start-function)
+ (funcall window-group-start-function window)
+ (window-start window)))
+
+(defvar window-group-end-function nil)
+(make-variable-buffer-local 'window-group-end-function)
+(put 'window-group-end-function 'permanent-local t)
+(defun window-group-end (&optional window update)
+ "Return position at which display currently ends in the group of
+windows containing WINDOW. When a grouping mode (such as Follow Mode)
+is not active, this function is identical to `window-end'.
+
+WINDOW must be a live window and defaults to the selected one.
+This is updated by redisplay, when it runs to completion.
+Simply changing the buffer text or setting `window-group-start'
+does not update this value.
+Return nil if there is no recorded value. (This can happen if the
+last redisplay of WINDOW was preempted, and did not finish.)
+If UPDATE is non-nil, compute the up-to-date position
+if it isn't already recorded."
+ (if (functionp window-group-end-function)
+ (funcall window-group-end-function window update)
+ (window-end window update)))
+
+(defvar set-window-group-start-function nil)
+(make-variable-buffer-local 'set-window-group-start-function)
+(put 'set-window-group-start-function 'permanent-local t)
+(defun set-window-group-start (window pos &optional noforce)
+ "Make display in the group of windows containing WINDOW start at
+position POS in WINDOW's buffer. When a grouping mode (such as Follow
+Mode) is not active, this function is identical to `set-window-start'.
+
+WINDOW must be a live window and defaults to the selected one. Return
+POS. Optional third arg NOFORCE non-nil inhibits next redisplay from
+overriding motion of point in order to display at this exact start."
+ (if (functionp set-window-group-start-function)
+ (funcall set-window-group-start-function window pos noforce)
+ (set-window-start window pos noforce)))
+
+(defvar recenter-window-group-function nil)
+(make-variable-buffer-local 'recenter-window-group-function)
+(put 'recenter-window-group-function 'permanent-local t)
+(defun recenter-window-group (&optional arg)
+ "Center point in the group of windows containing the selected window
+and maybe redisplay frame. When a grouping mode (such as Follow Mode)
+is not active, this function is identical to `recenter'.
+
+With a numeric prefix argument ARG, recenter putting point on screen line ARG
+relative to the first window in the selected window group. If ARG is
+negative, it counts up from the bottom of the last window in the
+group. (ARG should be less than the total height of the window group.)
+
+If ARG is omitted or nil, then recenter with point on the middle line of
+the selected window group; if the variable `recenter-redisplay' is
+non-nil, also erase the entire frame and redraw it (when
+`auto-resize-tool-bars' is set to `grow-only', this resets the
+tool-bar's height to the minimum height needed); if
+`recenter-redisplay' has the special value `tty', then only tty frames
+are redrawn.
+
+Just C-u as prefix means put point in the center of the window
+and redisplay normally--don't erase and redraw the frame."
+ (if (functionp recenter-window-group-function)
+ (funcall recenter-window-group-function arg)
+ (recenter arg)))
+
+(defvar pos-visible-in-window-group-p-function nil)
+(make-variable-buffer-local 'pos-visible-in-window-group-p-function)
+(put 'pos-visible-in-window-group-p-function 'permanent-local t)
+(defun pos-visible-in-window-group-p (&optional pos window partially)
+ "Return non-nil if position POS is currently on the frame in the
+window group containing WINDOW. When a grouping mode (such as Follow
+Mode) is not active, this function is identical to
+`pos-visible-in-window-p'.
+
+WINDOW must be a live window and defaults to the selected one.
+
+Return nil if that position is scrolled vertically out of view. If a
+character is only partially visible, nil is returned, unless the
+optional argument PARTIALLY is non-nil. If POS is only out of view
+because of horizontal scrolling, return non-nil. If POS is t, it
+specifies the position of the last visible glyph in the window group.
+POS defaults to point in WINDOW; WINDOW defaults to the selected
+window.
+
+If POS is visible, return t if PARTIALLY is nil; if PARTIALLY is non-nil,
+the return value is a list of 2 or 6 elements (X Y [RTOP RBOT ROWH VPOS]),
+where X and Y are the pixel coordinates relative to the top left corner
+of the window. The remaining elements are omitted if the character after
+POS is fully visible; otherwise, RTOP and RBOT are the number of pixels
+off-window at the top and bottom of the screen line (\"row\") containing
+POS, ROWH is the visible height of that row, and VPOS is the row number
+\(zero-based)."
+ (if (functionp pos-visible-in-window-group-p-function)
+ (funcall pos-visible-in-window-group-p-function pos window partially)
+ (pos-visible-in-window-p pos window partially)))
+
+(defvar selected-window-group-function nil)
+(make-variable-buffer-local 'selected-window-group-function)
+(put 'selected-window-group-function 'permanent-local t)
+(defun selected-window-group ()
+ "Return the list of windows in the group containing the selected window.
+When a grouping mode (such as Follow Mode) is not active, the
+result is a list containing only the selected window."
+ (if (functionp selected-window-group-function)
+ (funcall selected-window-group-function)
+ (list (selected-window))))
+
+(defvar move-to-window-group-line-function nil)
+(make-variable-buffer-local 'move-to-window-group-line-function)
+(put 'move-to-window-group-line-function 'permanent-local t)
+(defun move-to-window-group-line (arg)
+ "Position point relative to the the current group of windows.
+When a grouping mode (such as Follow Mode) is not active, this
+function is identical to `move-to-window-line'.
+
+ARG nil means position point at center of the window group.
+Else, ARG specifies the vertical position within the window
+group; zero means top of first window in the group, negative
+means relative to the bottom of the last window in the group."
+ (if (functionp move-to-window-group-line-function)
+ (funcall move-to-window-group-line-function arg)
+ (move-to-window-line arg)))
+
+
(defvar recenter-last-op nil
"Indicates the last recenter operation performed.
Possible values: `top', `middle', `bottom', integer or float numbers.