diff --git a/etc/NEWS b/etc/NEWS index 84abb56e56a..0d79d30f4ee 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -57,6 +57,16 @@ recenter the visited source file. Its value can be a number (for example, ** New command kill-matching-buffers kills buffers whose name matches a regexp. +** Minibuffer changes: + +*** isearch started in the minibuffer searches in the minibuffer history. +Reverse isearch commands (C-r, C-M-r) search in previous minibuffer +history elements, and forward isearch commands (C-s, C-M-s) search in +next history elements. When the reverse search reaches the first history +element, it wraps to the last history element, and the forward search +wraps to the first history element. When the search is terminated, the +history element containing the search string becomes the current. + * New Modes and Packages in Emacs 23.1 diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 88481804473..b8872038d0a 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,21 @@ +2007-07-23 Juri Linkov + + * isearch.el (isearch-message-function): New variable. + (isearch-update, isearch-search): Use it. + + * simple.el (goto-history-element): New function created from + next-history-element. + (next-history-element): Most code moved to goto-history-element. + Call goto-history-element with (- minibuffer-history-position n). + (previous-history-element): Call goto-history-element with (+ + minibuffer-history-position n). + (minibuffer-setup-hook): Add minibuffer-history-isearch-setup. + (minibuffer-history-isearch-message-overlay): New buffer-local variable. + (minibuffer-history-isearch-setup, minibuffer-history-isearch-end) + (minibuffer-history-isearch-search, minibuffer-history-isearch-message) + (minibuffer-history-isearch-wrap, minibuffer-history-isearch-push-state) + (minibuffer-history-isearch-pop-state): New functions. + 2007-07-23 Thien-Thi Nguyen * vc-hooks.el (vc-stay-local-p): Fix bug: Avoid remove-if-not. diff --git a/lisp/simple.el b/lisp/simple.el index c052ce3574b..10374e16cd2 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -1300,55 +1300,61 @@ makes the search case-sensitive." (defvar minibuffer-temporary-goal-position nil) +(defun goto-history-element (nabs) + "Puts element of the minibuffer history in the minibuffer. +The argument NABS specifies the absolute history position." + (interactive "p") + (let ((minimum (if minibuffer-default -1 0)) + elt minibuffer-returned-to-present) + (if (and (zerop minibuffer-history-position) + (null minibuffer-text-before-history)) + (setq minibuffer-text-before-history + (minibuffer-contents-no-properties))) + (if (< nabs minimum) + (if minibuffer-default + (error "End of history; no next item") + (error "End of history; no default available"))) + (if (> nabs (length (symbol-value minibuffer-history-variable))) + (error "Beginning of history; no preceding item")) + (unless (memq last-command '(next-history-element + previous-history-element)) + (let ((prompt-end (minibuffer-prompt-end))) + (set (make-local-variable 'minibuffer-temporary-goal-position) + (cond ((<= (point) prompt-end) prompt-end) + ((eobp) nil) + (t (point)))))) + (goto-char (point-max)) + (delete-minibuffer-contents) + (setq minibuffer-history-position nabs) + (cond ((= nabs -1) + (setq elt minibuffer-default)) + ((= nabs 0) + (setq elt (or minibuffer-text-before-history "")) + (setq minibuffer-returned-to-present t) + (setq minibuffer-text-before-history nil)) + (t (setq elt (nth (1- minibuffer-history-position) + (symbol-value minibuffer-history-variable))))) + (insert + (if (and (eq minibuffer-history-sexp-flag (minibuffer-depth)) + (not minibuffer-returned-to-present)) + (let ((print-level nil)) + (prin1-to-string elt)) + elt)) + (goto-char (or minibuffer-temporary-goal-position (point-max))))) + (defun next-history-element (n) "Puts next element of the minibuffer history in the minibuffer. With argument N, it uses the Nth following element." (interactive "p") (or (zerop n) - (let ((narg (- minibuffer-history-position n)) - (minimum (if minibuffer-default -1 0)) - elt minibuffer-returned-to-present) - (if (and (zerop minibuffer-history-position) - (null minibuffer-text-before-history)) - (setq minibuffer-text-before-history - (minibuffer-contents-no-properties))) - (if (< narg minimum) - (if minibuffer-default - (error "End of history; no next item") - (error "End of history; no default available"))) - (if (> narg (length (symbol-value minibuffer-history-variable))) - (error "Beginning of history; no preceding item")) - (unless (memq last-command '(next-history-element - previous-history-element)) - (let ((prompt-end (minibuffer-prompt-end))) - (set (make-local-variable 'minibuffer-temporary-goal-position) - (cond ((<= (point) prompt-end) prompt-end) - ((eobp) nil) - (t (point)))))) - (goto-char (point-max)) - (delete-minibuffer-contents) - (setq minibuffer-history-position narg) - (cond ((= narg -1) - (setq elt minibuffer-default)) - ((= narg 0) - (setq elt (or minibuffer-text-before-history "")) - (setq minibuffer-returned-to-present t) - (setq minibuffer-text-before-history nil)) - (t (setq elt (nth (1- minibuffer-history-position) - (symbol-value minibuffer-history-variable))))) - (insert - (if (and (eq minibuffer-history-sexp-flag (minibuffer-depth)) - (not minibuffer-returned-to-present)) - (let ((print-level nil)) - (prin1-to-string elt)) - elt)) - (goto-char (or minibuffer-temporary-goal-position (point-max)))))) + (goto-history-element (- minibuffer-history-position n)))) (defun previous-history-element (n) "Puts previous element of the minibuffer history in the minibuffer. With argument N, it uses the Nth previous element." (interactive "p") - (next-history-element (- n))) + (or (zerop n) + (goto-history-element (+ minibuffer-history-position n)))) (defun next-complete-history-element (n) "Get next history element which completes the minibuffer before the point. @@ -1381,6 +1387,137 @@ Return 0 if current buffer is not a minibuffer." ;; the buffer; this should be 0 for normal buffers. (1- (minibuffer-prompt-end))) +;; isearch minibuffer history +(add-hook 'minibuffer-setup-hook 'minibuffer-history-isearch-setup) + +(defvar minibuffer-history-isearch-message-overlay) +(make-variable-buffer-local 'minibuffer-history-isearch-message-overlay) + +(defun minibuffer-history-isearch-setup () + "Set up a minibuffer for using isearch to search the minibuffer history. +Intended to be added to `minibuffer-setup-hook'." + (set (make-local-variable 'isearch-search-fun-function) + 'minibuffer-history-isearch-search) + (set (make-local-variable 'isearch-message-function) + 'minibuffer-history-isearch-message) + (set (make-local-variable 'isearch-wrap-function) + 'minibuffer-history-isearch-wrap) + (set (make-local-variable 'isearch-push-state-function) + 'minibuffer-history-isearch-push-state) + (add-hook 'isearch-mode-end-hook 'minibuffer-history-isearch-end nil t)) + +(defun minibuffer-history-isearch-end () + "Clean up the minibuffer after terminating isearch in the minibuffer." + (if minibuffer-history-isearch-message-overlay + (delete-overlay minibuffer-history-isearch-message-overlay))) + +(defun minibuffer-history-isearch-search () + "Return the proper search function, for isearch in minibuffer history." + (cond + (isearch-word + (if isearch-forward 'word-search-forward 'word-search-backward)) + (t + (lambda (string bound noerror) + (let ((search-fun + ;; Use standard functions to search within minibuffer text + (cond + (isearch-regexp + (if isearch-forward 're-search-forward 're-search-backward)) + (t + (if isearch-forward 'search-forward 'search-backward)))) + found) + ;; Avoid lazy-highlighting matches in the minibuffer prompt when + ;; searching forward. Lazy-highlight calls this lambda with the + ;; bound arg, so skip the minibuffer prompt. + (if (and bound isearch-forward (< (point) (minibuffer-prompt-end))) + (goto-char (minibuffer-prompt-end))) + (or + ;; 1. First try searching in the initial minibuffer text + (funcall search-fun string + (if isearch-forward bound (minibuffer-prompt-end)) + noerror) + ;; 2. If the above search fails, start putting next/prev history + ;; elements in the minibuffer successively, and search the string + ;; in them. Do this only when bound is nil (i.e. not while + ;; lazy-highlighting search strings in the current minibuffer text). + (unless bound + (condition-case nil + (progn + (while (not found) + (cond (isearch-forward + (next-history-element 1) + (goto-char (minibuffer-prompt-end))) + (t + (previous-history-element 1) + (goto-char (point-max)))) + (setq isearch-barrier (point) isearch-opoint (point)) + ;; After putting the next/prev history element, search + ;; the string in them again, until next-history-element + ;; or previous-history-element raises an error at the + ;; beginning/end of history. + (setq found (funcall search-fun string + (unless isearch-forward + ;; For backward search, don't search + ;; in the minibuffer prompt + (minibuffer-prompt-end)) + noerror))) + ;; Return point of the new search result + (point)) + ;; Return nil when next(prev)-history-element fails + (error nil))))))))) + +(defun minibuffer-history-isearch-message (&optional c-q-hack ellipsis) + "Display the minibuffer history search prompt. +If there are no search errors, this function displays an overlay with +the isearch prompt which replaces the original minibuffer prompt. +Otherwise, it displays the standard isearch message returned from +`isearch-message'." + (if (not (and (minibufferp) isearch-success (not isearch-error))) + ;; Use standard function `isearch-message' when not in the minibuffer, + ;; or search fails, or has an error (like incomplete regexp). + ;; This function overwrites minibuffer text with isearch message, + ;; so it's possible to see what is wrong in the search string. + (isearch-message c-q-hack ellipsis) + ;; Otherwise, put the overlay with the standard isearch prompt over + ;; the initial minibuffer prompt. + (if (overlayp minibuffer-history-isearch-message-overlay) + (move-overlay minibuffer-history-isearch-message-overlay + (point-min) (minibuffer-prompt-end)) + (setq minibuffer-history-isearch-message-overlay + (make-overlay (point-min) (minibuffer-prompt-end))) + (overlay-put minibuffer-history-isearch-message-overlay 'evaporate t)) + (overlay-put minibuffer-history-isearch-message-overlay + 'display (isearch-message-prefix c-q-hack ellipsis)) + ;; And clear any previous isearch message. + (message ""))) + +(defun minibuffer-history-isearch-wrap () + "Wrap the minibuffer history search when search is failed. +Move point to the first history element for a forward search, +or to the last history element for a backward search." + (unless isearch-word + ;; When `minibuffer-history-isearch-search' fails on reaching the + ;; beginning/end of the history, wrap the search to the first/last + ;; minibuffer history element. + (if isearch-forward + (goto-history-element (length (symbol-value minibuffer-history-variable))) + (goto-history-element 0)) + (setq isearch-success t)) + (goto-char (if isearch-forward (minibuffer-prompt-end) (point-max)))) + +(defun minibuffer-history-isearch-push-state () + "Save a function restoring the state of minibuffer history search. +Save `minibuffer-history-position' to the additional state parameter +in the search status stack." + `(lambda (cmd) + (minibuffer-history-isearch-pop-state cmd ,minibuffer-history-position))) + +(defun minibuffer-history-isearch-pop-state (cmd hist-pos) + "Restore the minibuffer history search state. +Go to the history element by the absolute history position `hist-pos'." + (goto-history-element hist-pos)) + + ;Put this on C-x u, so we can force that rather than C-_ into startup msg (defalias 'advertised-undo 'undo)