diff options
-rw-r--r-- | doc/lispref/functions.texi | 17 | ||||
-rw-r--r-- | doc/lispref/positions.texi | 12 | ||||
-rw-r--r-- | doc/lispref/windows.texi | 88 | ||||
-rw-r--r-- | lisp/follow.el | 176 | ||||
-rw-r--r-- | lisp/isearch.el | 125 | ||||
-rw-r--r-- | lisp/replace.el | 8 | ||||
-rw-r--r-- | lisp/textmodes/ispell.el | 79 | ||||
-rw-r--r-- | lisp/window.el | 146 |
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. |