> How 'bout the patch below which makes no assumption (that I know of) about > substitute-in-file-name, and will thus work correctly even with weird > magic file name handlers.
Regarding performance of my code: I just bumped into a performance problem. The problem is that substitute-in-file-name can take a non-negligible amount of time to execute when there's a "~user" in the file name, because it calls getpwnam to figure out whether "user" actually exists or not. At least here with 8K users in our YP database, my code causes file-name-shadow-mode to take around 0.5-1s to refresh the screen after each key stroke if there's a ~user in the file name I'm editing. So I've tweaked my code to streamline the common case where the shadow doesn't need to be moved. I also fixed the problem where a file name like "/home/foo/bar/:toto//tata" was sometimes shadowed as "r/:toto//tata" instead of just "/tata". Stefan --- orig/lisp/rfn-eshadow.el +++ mod/lisp/rfn-eshadow.el @@ -97,7 +97,7 @@ '(face file-name-shadow field shadow) "Properties given to the `shadowed' part of a filename in the minibuffer. Only used when `file-name-shadow-mode' is active. -If emacs is not running under a window system, +If Emacs is not running under a window system, `file-name-shadow-tty-properties' is used instead." :type file-name-shadow-properties-custom-type :group 'minibuffer) @@ -123,20 +123,6 @@ ;;; Internal variables -;; Regexp to locate dividing point between shadow and real pathname -(defconst rfn-eshadow-regexp - (cond ((memq system-type '(ms-dos windows-nt)) - ;; This horrible regexp considers the following patterns as - ;; starting an absolute pathname, when following a `/' or an `\': - ;; L: / // ~ $ \\ \\\\ - "\\(.*[^/]+/+?\\|/*?\\|\\)\\(~\\|$[^$]\\|$\\'\\|[][\\^a-z]:\\|//?\\([^][\\^a-z/$~]\\|[^/$~][^:]\\|[^/$~]?\\'\\)\\)") - (t - ;; default is for unix-style filenames - "\\(.*/\\)\\([/~]\\|$[^$]\\|$\\'\\)")) - "Regular expression used to match shadowed filenames. -There should be at least one regexp group; the end of the first one -is used as the end of the shadowed portion of the filename.") - ;; A list of minibuffers to which we've added a post-command-hook. (defvar rfn-eshadow-frobbed-minibufs nil) @@ -170,31 +156,54 @@ (add-to-list 'rfn-eshadow-frobbed-minibufs (current-buffer)) (add-hook 'post-command-hook #'rfn-eshadow-update-overlay nil t))) +(defsubst rfn-eshadow-sifn-equal (goal pos) + (equal goal (buffer-substring-no-properties pos (point-max)))) + ;; post-command-hook to update overlay (defun rfn-eshadow-update-overlay () "Update `rfn-eshadow-overlay' to cover shadowed part of minibuffer input. This is intended to be used as a minibuffer post-command-hook for `file-name-shadow-mode'; the minibuffer should have already been set up by `rfn-eshadow-setup-minibuffer'." - ;; This is not really a correct implementation; it won't always do the - ;; right thing in the presence of environment variables that - ;; substitute-in-file-name would expand; currently it just assumes any - ;; environment variable contains an absolute filename. - (save-excursion - (let ((inhibit-point-motion-hooks t)) - (goto-char (minibuffer-prompt-end)) - ;; Update the overlay (which will evaporate if it's empty). - (move-overlay rfn-eshadow-overlay - (point) - (if (looking-at rfn-eshadow-regexp) - (match-end 1) - (point)))))) + ;; This code usually is instantaneous, but if the file name includes + ;; a "~<user>", substitute-in-file-name will lookup your system's list of + ;; users to see whether "<user>" is an actual user or not, and that + ;; can sometimes take a while, so we wrap this in `while-no-input'. + (while-no-input + (condition-case nil + (let ((goal (substitute-in-file-name (minibuffer-contents))) + (mid (overlay-end rfn-eshadow-overlay)) + (start (minibuffer-prompt-end)) + (end (point-max))) + (unless + ;; Catch the common case where the shadow does not need to move. + (and mid + (or (eq mid end) + (not (rfn-eshadow-sifn-equal goal (1+ mid)))) + (or (eq mid start) + (rfn-eshadow-sifn-equal goal mid))) + ;; Binary search for the greatest position still equivalent to + ;; the whole. + (while (or (< (1+ start) end) + (if (and (< (1+ end) (point-max)) + (rfn-eshadow-sifn-equal goal (1+ end))) + ;; (SIFN end) != goal, but (SIFN (1+end)) == goal, + ;; We've reached a discontinuity: this can happen + ;; e.g. if `end' point to "/:...". + (setq start (1+ end) end (point-max)))) + (setq mid (/ (+ start end) 2)) + (if (equal (condition-case nil + (rfn-eshadow-sifn-equal goal mid) + (error nil)) + goal) + (setq start mid) + (setq end mid))) + (move-overlay rfn-eshadow-overlay (minibuffer-prompt-end) start))) + ;; `substitute-in-file-name' can fail on partial input. + ;; (error nil) + ))) -;;; Note this definition must be at the end of the file, because -;;; `define-minor-mode' actually calls the mode-function if the -;;; associated variable is non-nil, which requires that all needed -;;; functions be already defined. [This is arguably a bug in d-m-m] ;;;###autoload (define-minor-mode file-name-shadow-mode "Toggle File-Name Shadow mode. @@ -222,5 +231,5 @@ (provide 'rfn-eshadow) -;;; arch-tag: dcf70a52-0115-4ec2-b1e3-4f8d3541a888 +;; arch-tag: dcf70a52-0115-4ec2-b1e3-4f8d3541a888 ;;; rfn-eshadow.el ends here _______________________________________________ Emacs-devel mailing list Emacs-devel@gnu.org http://lists.gnu.org/mailman/listinfo/emacs-devel