Mercurial > emacs
changeset 94090:6027fc6333c9
Complete rewrite.
author | Stefan Monnier <monnier@iro.umontreal.ca> |
---|---|
date | Mon, 14 Apr 2008 19:54:30 +0000 |
parents | 01d0ddc5bfce |
children | 14fafab62797 |
files | lisp/ChangeLog lisp/emacs-lisp/crm.el |
diffstat | 2 files changed, 105 insertions(+), 434 deletions(-) [+] |
line wrap: on
line diff
--- a/lisp/ChangeLog Mon Apr 14 18:44:04 2008 +0000 +++ b/lisp/ChangeLog Mon Apr 14 19:54:30 2008 +0000 @@ -1,5 +1,7 @@ 2008-04-14 Stefan Monnier <monnier@iro.umontreal.ca> + * emacs-lisp/crm.el: Complete rewrite. + * tmm.el (tmm-completion-delete-prompt): Don't hardcode point-min==1. (tmm-add-prompt): Make sure completion-setup-hook is preserved even in case of an error in display-completion-list.
--- a/lisp/emacs-lisp/crm.el Mon Apr 14 18:44:04 2008 +0000 +++ b/lisp/emacs-lisp/crm.el Mon Apr 14 19:54:30 2008 +0000 @@ -60,7 +60,12 @@ ;; `completing-read'. They should be similar -- it was intentional. ;; Some of this code started out as translation from C code in -;; src/minibuf.c to Emacs Lisp code. +;; src/minibuf.c to Emacs Lisp code. After this code was rewritten in Elisp +;; and made to operate on any field, this file was completely rewritten to +;; just reuse that code. + +;; Thanks to Sen Nagata <sen@eccosys.com> for the original version of the +;; code, and sorry for throwing it all out. --Stef ;; Thanks to Richard Stallman for all of his help (many of the good ;; ideas in here are from him), Gerd Moellmann for his attention, @@ -69,20 +74,23 @@ ;;; Questions and Thoughts: -;; -the author has gone through a number of test-and-fix cycles w/ -;; this code, so it should be usable. please let me know if you find -;; any problems. - ;; -should `completing-read-multiple' allow a trailing separator in ;; a return value when REQUIRE-MATCH is t? if not, should beep when a user ;; tries to exit the minibuffer via RET? -;; -TODO: possibly make return values from `crm-do-completion' into constants +;; -tip: use M-f and M-b for ease of navigation among elements. -;; -TODO: find out whether there is an appropriate way to distinguish between -;; functions intended for internal use and those that aren't. - -;; -tip: use M-f and M-b for ease of navigation among elements. +;; - the difference between minibuffer-completion-table and +;; crm-completion-table is just crm--collection-fn. In most cases it +;; shouldn't make any difference. But if a non-CRM completion function +;; happens to be used, it will use minibuffer-completion-table and +;; crm--collection-fn will try to make it do "more or less the right +;; thing" by making it complete on the last element, which is about as +;; good as we can hope for right now. +;; I'm not sure if it's important or not. Maybe we could just throw away +;; crm-completion-table and crm--collection-fn, but there doesn't seem to +;; be a pressing need for it, and since Sen did bother to write it, we may +;; as well keep it, in case it helps. ;;; History: ;; @@ -100,12 +108,26 @@ completion candidates. Modify this value to make `completing-read-multiple' use a separator other than `crm-default-separator'.") -;; actual filling in of these maps occurs below via `crm-init-keymaps' -(defvar crm-local-completion-map nil +(defvar crm-local-completion-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map minibuffer-local-completion-map) + (define-key map [remap minibuffer-complete] #'crm-complete) + (define-key map [remap minibuffer-complete-word] #'crm-complete-word) + (define-key map [remap minibuffer-completion-help] #'crm-completion-help) + map) "Local keymap for minibuffer multiple input with completion. Analog of `minibuffer-local-completion-map'.") -(defvar crm-local-must-match-map nil +(defvar crm-local-must-match-map + (let ((map (make-sparse-keymap))) + ;; We'd want to have multiple inheritance here. + (set-keymap-parent map minibuffer-local-must-match-map) + (define-key map [remap minibuffer-complete] #'crm-complete) + (define-key map [remap minibuffer-complete-word] #'crm-complete-word) + (define-key map [remap minibuffer-completion-help] #'crm-completion-help) + (define-key map [remap minibuffer-complete-and-exit] + #'crm-complete-and-exit) + map) "Local keymap for minibuffer multiple input with exact match completion. Analog of `minibuffer-local-must-match-map' for crm.") @@ -114,38 +136,8 @@ This is a table used for completion by `completing-read-multiple' and its supporting functions.") -;; this is supposed to be analogous to last_exact_completion in src/minibuf.c -(defvar crm-last-exact-completion nil - "Completion string if last attempt reported \"Complete, but not unique\".") - -(defvar crm-left-of-element nil - "String to the left of the current element.") - -(defvar crm-current-element nil - "The current element.") - -(defvar crm-right-of-element nil - "String to the right of the current element.") - -(defvar crm-beginning-of-element nil - "Buffer position representing the beginning of the current element.") - -(defvar crm-end-of-element nil - "Buffer position representing the end of the current element.") - -;; emulates temp_echo_area_glyphs from src/minibuf.c -(defun crm-temp-echo-area-glyphs (message-string) - "Temporarily display MESSAGE-STRING in echo area. -After user-input or 2 seconds, erase the displayed string." - (save-excursion - (goto-char (point-max)) - (insert message-string) - (sit-for 2) - (backward-char (length message-string)) - (delete-char (length message-string)))) - ;; this function evolved from a posting by Stefan Monnier -(defun crm-collection-fn (string predicate flag) +(defun crm--collection-fn (string predicate flag) "Function used by `completing-read-multiple' to compute completion values. The value of STRING is the string to be completed. @@ -159,407 +151,84 @@ For more information on STRING, PREDICATE, and FLAG, see the Elisp Reference sections on 'Programmed Completion' and 'Basic Completion Functions'." - (let ((lead "")) - (when (string-match (concat ".*" crm-separator) string) - (setq lead (substring string 0 (match-end 0))) - (setq string (substring string (match-end 0)))) - (if (eq flag 'lambda) - ;; return t for exact match, nil otherwise - (let ((result (try-completion string crm-completion-table predicate))) - (if (stringp result) - nil - (if result - t - nil)))) - (if flag - ;; called via (all-completions string 'crm-completion-fn predicate)? - (all-completions string crm-completion-table predicate) - ;; called via (try-completion string 'crm-completion-fn predicate)? - (let ((result (try-completion string crm-completion-table predicate))) - (if (stringp result) - (concat lead result) - result))))) - -(defun crm-find-current-element () - "Parse the minibuffer to find the current element. -If no element can be found, return nil. - -If an element is found, bind: - - -the variable `crm-current-element' to the current element, - - -the variables `crm-left-of-element' and `crm-right-of-element' to - the strings to the left and right of the current element, - respectively, and - - -the variables `crm-beginning-of-element' and `crm-end-of-element' to - the buffer positions of the beginning and end of the current element - respectively, + (let ((beg 0)) + (while (string-match crm-separator string beg) + (setq beg (match-end 0))) + (completion-table-with-context (substring string 0 beg) + crm-completion-table + (substring string beg) + predicate + flag))) -and return t." - (let* ((prompt-end (minibuffer-prompt-end)) - (minibuffer-string (buffer-substring prompt-end (point-max))) - (end-index (or (string-match "," minibuffer-string (- (point) prompt-end)) - (- (point-max) prompt-end))) - (target-string (substring minibuffer-string 0 end-index)) - (index (or (string-match - (concat crm-separator "\\([^" crm-separator "]*\\)$") - target-string) - (string-match - (concat "^\\([^" crm-separator "]*\\)$") - target-string)))) - (if (not (numberp index)) - ;; no candidate found - nil - (progn - ;; - (setq crm-beginning-of-element (match-beginning 1)) - (setq crm-end-of-element (+ end-index prompt-end)) - ;; string to the left of the current element - (setq crm-left-of-element - (substring target-string 0 (match-beginning 1))) - ;; the current element - (setq crm-current-element (match-string 1 target-string)) - ;; string to the right of the current element - (setq crm-right-of-element (substring minibuffer-string end-index)) - t)))) +(defun crm--select-current-element () + "Parse the minibuffer to find the current element. +Place an overlay on the element, with a `field' property, and return it." + (let* ((bob (minibuffer-prompt-end)) + (start (save-excursion + (if (re-search-backward crm-separator bob t) + (match-end 0) + bob))) + (end (save-excursion + (if (re-search-forward crm-separator nil t) + (match-beginning 0) + (point-max)))) + (ol (make-overlay start end nil nil t))) + (overlay-put ol 'field (make-symbol "crm")) + ol)) -(defun crm-test-completion (candidate) - "Return t if CANDIDATE is an exact match for a valid completion." - (let ((completions - ;; TODO: verify whether the arguments are appropriate - (all-completions - candidate crm-completion-table minibuffer-completion-predicate))) - (if (member candidate completions) - t - nil))) - -(defun crm-minibuffer-completion-help () +(defun crm-completion-help () "Display a list of possible completions of the current minibuffer element." (interactive) - (message "Making completion list...") - (if (not (crm-find-current-element)) - nil - (let ((completions (all-completions crm-current-element - minibuffer-completion-table - minibuffer-completion-predicate))) - (message nil) - (if (null completions) - (crm-temp-echo-area-glyphs " [No completions]") - (with-output-to-temp-buffer "*Completions*" - (display-completion-list - (sort completions 'string-lessp) - crm-current-element))))) + (let ((ol (crm-select-current-element))) + (unwind-protect + (minibuffer-completion-help) + (delete-overlay ol))) nil) -(defun crm-do-completion () - "This is the internal completion engine. -This function updates the text in the minibuffer -to complete the current string, and returns a number between 0 and 6. -The meanings of the return values are: - - 0 - the string has no possible completion - 1 - the string is already a valid and unique match - 2 - not used - 3 - the string is already a valid match (but longer matches exist too) - 4 - the string was completed to a valid match - 5 - some completion has been done, but the result is not a match - 6 - no completion was done, and the string is not an exact match" - - (if (not (crm-find-current-element)) - nil - (let (last completion completedp) - (setq completion - (try-completion crm-current-element - minibuffer-completion-table - minibuffer-completion-predicate)) - (setq last crm-last-exact-completion) - (setq crm-last-exact-completion nil) - - (catch 'crm-exit - - (if (null completion) ; no possible completion - (progn - (crm-temp-echo-area-glyphs " [No match]") - (throw 'crm-exit 0))) - - (if (eq completion t) ; was already an exact and unique completion - (throw 'crm-exit 1)) - - (setq completedp - (null (string-equal completion crm-current-element))) - - (if completedp - (progn - (delete-region (minibuffer-prompt-end) (point-max)) - (insert crm-left-of-element completion) - ;; (if crm-complete-up-to-point - ;; (insert crm-separator)) - (insert crm-right-of-element) - (backward-char (length crm-right-of-element)) - ;; TODO: is this correct? - (setq crm-current-element completion))) - - (if (null (crm-test-completion crm-current-element)) - (progn - (if completedp ; some completion happened - (throw 'crm-exit 5) - (if completion-auto-help - (crm-minibuffer-completion-help) - (crm-temp-echo-area-glyphs " [Next char not unique]"))) - (throw 'crm-exit 6)) - (if completedp - (throw 'crm-exit 4))) - - (setq crm-last-exact-completion completion) - (if (not (null last)) - (progn - (if (not (null (equal crm-current-element last))) - (crm-minibuffer-completion-help)))) - - ;; returning -- was already an exact completion - (throw 'crm-exit 3))))) - -(defun crm-minibuffer-complete () +(defun crm-complete () "Complete the current element. If no characters can be completed, display a list of possible completions. Return t if the current element is now a valid match; otherwise return nil." (interactive) - ;; take care of scrolling if necessary -- completely cribbed from minibuf.c - (if (not (eq last-command this-command)) - ;; ok? - (setq minibuffer-scroll-window nil)) - (let ((window minibuffer-scroll-window)) - (if (and (not (null window)) - ;; ok? - (not (null (window-buffer window)))) - (let (tem) - (set-buffer (window-buffer window)) - ;; ok? - (setq tem (pos-visible-in-window-p (point-max) window)) - (if (not (null tem)) - ;; ok? - (set-window-start window (point-min) nil) - (scroll-other-window nil)) - ;; reaching here means exiting the function w/ return value of nil - nil) - - (let* ( - ;(crm-end-of-element nil) - (result (crm-do-completion))) - (cond - ((eq 0 result) - nil) - ((eq 1 result) - ;; adapted from Emacs 21 - (if (not (eq (point) crm-end-of-element)) - (goto-char (+ 1 crm-end-of-element))) - (crm-temp-echo-area-glyphs " [Sole completion]") - t) - ((eq 3 result) - ;; adapted from Emacs 21 - (if (not (eq (point) crm-end-of-element)) - (goto-char (+ 1 crm-end-of-element))) - (crm-temp-echo-area-glyphs " [Complete, but not unique]") - t)))))) - -;; i love traffic lights...but only when they're green -(defun crm-find-longest-completable-substring (string) - "Determine the longest completable (left-anchored) substring of STRING. -The description \"left-anchored\" means the positions of the characters -in the substring must be the same as those of the corresponding characters -in STRING. Anchoring is what `^' does in a regular expression. - -The table and predicate used for completion are -`minibuffer-completion-table' and `minibuffer-completion-predicate', -respectively. - -A non-nil return value means that there is some substring which is -completable. A return value of t means that STRING itself is -completable. If a string value is returned it is the longest -completable proper substring of STRING. If nil is returned, STRING -does not have any non-empty completable substrings. + (let ((ol (crm-select-current-element))) + (unwind-protect + (minibuffer-complete) + (delete-overlay ol)))) -Remember: \"left-anchored\" substring" - (let* ((length-of-string (length string)) - (index length-of-string) - (done (if (> length-of-string 0) - nil - t)) - (first t) ; ugh, special handling for first time through... - goal-string - result) - ;; loop through left-anchored substrings in order of descending length, - ;; find the first substring that is completable - (while (not done) - (setq result (try-completion (substring string 0 index) - minibuffer-completion-table - minibuffer-completion-predicate)) - (if result - ;; found completable substring - (progn - (setq done t) - (if (and (eq result t) first) - ;; exactly matching string first time through - (setq goal-string t) - ;; fully-completed proper substring - (setq goal-string (substring string 0 index))))) - (setq index (1- index)) - (if first - (setq first nil)) - (if (<= index 0) - (setq done t))) - ;; possible values include: t, nil, some string - goal-string)) +(defun crm-complete-word () + "Complete the current element at most a single word. +Like `minibuffer-complete-word' but for `completing-read-multiple'." + (interactive) + (let ((ol (crm-select-current-element))) + (unwind-protect + (minibuffer-complete-word) + (delete-overlay ol)))) -;; TODO: decide whether trailing separator is allowed. current -;; implementation appears to allow it -(defun crm-strings-completed-p (separated-string) - "Verify that strings in SEPARATED-STRING are completed strings. -A return value of t means that all strings were verified. A number is -returned if verification was unsuccessful. This number represents the -position in SEPARATED-STRING up to where completion was successful." - (let ((strings (split-string separated-string crm-separator)) - ;; buffers start at 1, not 0 - (current-position 1) - current-string - result - done) - (while (and strings (not done)) - (setq current-string (car strings) - result (try-completion current-string - minibuffer-completion-table - minibuffer-completion-predicate)) - (if (eq result t) - (setq strings (cdr strings) - current-position (+ current-position - (length current-string) - ;; automatically adding 1 for separator - ;; character - 1)) - ;; still one more case of a match - (if (stringp result) - (let ((string-list - (all-completions result - minibuffer-completion-table - minibuffer-completion-predicate))) - (if (member result string-list) - ;; ho ho, code duplication... - (setq strings (cdr strings) - current-position (+ current-position - (length current-string) - 1)) - (progn - (setq done t) - ;; current-string is a partially-completed string - (setq current-position (+ current-position - (length current-string)))))) - ;; current-string cannot be completed - (let ((completable-substring - (crm-find-longest-completable-substring current-string))) - (setq done t) - (setq current-position (+ current-position - (length completable-substring))))))) - ;; return our result - (if (null strings) - t - current-position))) - -;; try to complete candidate, then check all separated strings. move -;; point to problem position if checking fails for some string. if -;; checking succeeds for all strings, exit. -(defun crm-minibuffer-complete-and-exit () +(defun crm-complete-and-exit () "If all of the minibuffer elements are valid completions then exit. All elements in the minibuffer must match. If there is a mismatch, move point to the location of mismatch and do not exit. -This function is modeled after `minibuffer_complete_and_exit' in src/minibuf.c" +This function is modeled after `minibuffer-complete-and-exit'." (interactive) - - (if (not (crm-find-current-element)) - nil - (let (result) - - (setq result - (catch 'crm-exit - - (if (eq (minibuffer-prompt-end) (point-max)) - (throw 'crm-exit t)) - - ;; TODO: this test is suspect? - (if (not (null (crm-test-completion crm-current-element))) - (throw 'crm-exit "check")) - - ;; TODO: determine how to detect errors - (let ((result (crm-do-completion))) - - (cond - ((or (eq 1 result) - (eq 3 result)) - (throw 'crm-exit "check")) - ((eq 4 result) - (if (not (null minibuffer-completion-confirm)) - (progn - (crm-temp-echo-area-glyphs " [Confirm]") - nil) - (throw 'crm-exit "check"))) - (nil))))) - - (if (null result) - nil - (if (equal result "check") - (let ((check-strings - (crm-strings-completed-p - (buffer-substring (minibuffer-prompt-end) (point-max))))) - ;; check all of minibuffer - (if (eq check-strings t) - (throw 'exit nil) - (if (numberp check-strings) - (progn - (goto-char check-strings) - (crm-temp-echo-area-glyphs " [An element did not match]")) - (message "Unexpected error")))) - (if (eq result t) - (throw 'exit nil) - (message "Unexpected error"))))))) - -(defun crm-init-keymaps () - "Initialize the keymaps used by `completing-read-multiple'. -Two keymaps are used depending on the value of the REQUIRE-MATCH -argument of the function `completing-read-multiple'. - -If REQUIRE-MATCH is nil, the keymap `crm-local-completion-map' is used. -This keymap inherits from the keymap named `minibuffer-local-completion-map'. -The only difference is that TAB is bound to `crm-minibuffer-complete' in -the inheriting keymap. - -If REQUIRE-MATCH is non-nil, the keymap `crm-local-must-match-map' is used. -This keymap inherits from the keymap named `minibuffer-local-must-match-map'. -The inheriting keymap binds RET to `crm-minibuffer-complete-and-exit' -and TAB to `crm-minibuffer-complete'." - (unless crm-local-completion-map - (setq crm-local-completion-map (make-sparse-keymap)) - (set-keymap-parent crm-local-completion-map - minibuffer-local-completion-map) - ;; key definitions - (define-key crm-local-completion-map - (kbd "TAB") - (function crm-minibuffer-complete))) - - (unless crm-local-must-match-map - (setq crm-local-must-match-map (make-sparse-keymap)) - (set-keymap-parent crm-local-must-match-map - minibuffer-local-must-match-map) - ;; key definitions - (define-key crm-local-must-match-map - (kbd "RET") - (function crm-minibuffer-complete-and-exit)) - (define-key crm-local-must-match-map - (kbd "TAB") - (function crm-minibuffer-complete)))) - -(crm-init-keymaps) + (let ((doexit t)) + (goto-char (minibuffer-prompt-end)) + (while + (and doexit + (let ((ol (crm-select-current-element))) + (goto-char (overlay-end ol)) + (unwind-protect + (catch 'exit + (minibuffer-complete-and-exit) + ;; This did not throw `exit', so there was a problem. + (setq doexit nil)) + (goto-char (overlay-end ol)) + (delete-overlay ol)) + (not (eobp)))) + ;; Skip to the next element. + (forward-char 1)) + (if doexit (exit-minibuffer)))) ;; superemulates behavior of completing_read in src/minibuf.c ;;;###autoload @@ -592,18 +261,12 @@ See the documentation for `completing-read' for details on the arguments: PROMPT, TABLE, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF, and INHERIT-INPUT-METHOD." - (let* ((minibuffer-completion-table (function crm-collection-fn)) + (let* ((minibuffer-completion-table #'crm--collection-fn) (minibuffer-completion-predicate predicate) ;; see completing_read in src/minibuf.c (minibuffer-completion-confirm (unless (eq require-match t) require-match)) (crm-completion-table table) - crm-last-exact-completion - crm-current-element - crm-left-of-element - crm-right-of-element - crm-beginning-of-element - crm-end-of-element (map (if require-match crm-local-must-match-map crm-local-completion-map)) @@ -615,6 +278,12 @@ (and def (string-equal input "") (setq input def)) (split-string input crm-separator))) +(define-obsolete-function-alias 'crm-minibuffer-complete 'crm-complete "23.1") +(define-obsolete-function-alias + 'crm-minibuffer-completion-help 'crm-completion-help "23.1") +(define-obsolete-function-alias + 'crm-minibuffer-complete-and-exit 'crm-complete-and-exit "23.1") + ;; testing and debugging ;; (defun crm-init-test-environ () ;; "Set up some variables for testing."