tramp-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 1/1] tramp-file-name-all-completions optimizations with bugfixes


From: Julian Scheid
Subject: [PATCH 1/1] tramp-file-name-all-completions optimizations with bugfixes
Date: Mon, 17 Aug 2009 20:42:02 +1200

---
 lisp/ChangeLog |   11 +++
 lisp/tramp.el  |  204 +++++++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 176 insertions(+), 39 deletions(-)

diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 4e75aed..313da54 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,14 @@
+2009-08-17  Julian Scheid  <address@hidden>
+
+        * tramp.el (tramp-perl-file-name-all-completions): New defconst.
+        (tramp-handle-file-name-all-completions): Try using Perl to get
+        partial completions.  When perl not available, combine `cd' and
+        `ls' into single remote operation and use shell expansion to get
+        partial remote directory contents.  Set `file-exists-p' cache for
+        directory and any files returned by ls.  Change cache handling to
+        support partial directory contents.  Use error message emitted by
+        remote `cd' or Perl code for local tramp-error.
+
 2009-08-16  Michael Albinus  <address@hidden>
 
        * tramp-cache.el (top): Autoload `tramp-time-less-p'.
diff --git a/lisp/tramp.el b/lisp/tramp.el
index c4019cb..3fe3d52 100644
--- a/lisp/tramp.el
+++ b/lisp/tramp.el
@@ -1605,6 +1605,35 @@ on the remote file system.
 Escape sequence %s is replaced with name of Perl binary.
 This string is passed to `format', so percent characters need to be doubled.")
 
+(defconst tramp-perl-file-name-all-completions
+  "%s -e 'sub case {
+ my $str=shift;
+ if ($ARGV[2]) {
+  return lc($str);
+ }
+ else {
+  return $str;
+ }
+}
+opendir(d, $ARGV[0]) || die(\"$ARGV[0]: $!\\nfail\\n\");
address@hidden = readdir(d); closedir(d);
+foreach $f (@files) {
+ if (case(substr($f, 0, length($ARGV[1]))) eq case($ARGV[1])) {
+  if (-d \"$ARGV[0]/$f\") {
+   print \"$f/\\n\";
+  }
+  else {
+   print \"$f\\n\";
+  }
+ }
+}
+print \"ok\\n\"
+' \"$1\" \"$2\" \"$3\" 2>/dev/null"
+  "Perl script to produce output suitable for use with
+`file-name-all-completions' on the remote file system.  Escape
+sequence %s is replaced with name of Perl binary.  This string is
+passed to `format', so percent characters need to be doubled.")
+
 ;; Perl script to implement `file-attributes' in a Lisp `read'able
 ;; output.  If you are hacking on this, note that you get *no* output
 ;; unless this spits out a complete line, including the '\n' at the
@@ -3152,50 +3181,147 @@ value of `default-file-modes', without execute 
permissions."
   "Like `file-name-all-completions' for Tramp files."
   (unless (save-match-data (string-match "/" filename))
     (with-parsed-tramp-file-name (expand-file-name directory) nil
-      ;; Flush the directory cache.  There could be changed directory
-      ;; contents.
-      (when (and (integerp tramp-completion-reread-directory-timeout)
-                (> (tramp-time-diff
-                    (current-time)
-                    (tramp-get-file-property
-                     v localname "last-completion" '(0 0 0)))
-                   tramp-completion-reread-directory-timeout))
-       (tramp-flush-file-property v localname))
 
       (all-completions
        filename
        (mapcar
        'list
-       (with-file-property v localname "file-name-all-completions"
-         (let (result)
-          (tramp-barf-unless-okay
-           v
-           (format "cd %s" (tramp-shell-quote-argument localname))
-           "tramp-handle-file-name-all-completions: Couldn't `cd %s'"
-           (tramp-shell-quote-argument localname))
-
-          ;; Get a list of directories and files, including reliably
-          ;; tagging the directories with a trailing '/'.  Because I
-          ;; rock.  address@hidden
-          (tramp-send-command
-           v
-           (format (concat "%s -a 2>/dev/null | while read f; do "
-                           "if %s -d \"$f\" 2>/dev/null; "
-                           "then echo \"$f/\"; else echo \"$f\"; fi; done")
-                   (tramp-get-ls-command v)
-                   (tramp-get-test-command v)))
-
-          ;; Now grab the output.
-          (with-current-buffer (tramp-get-buffer v)
-            (goto-char (point-max))
-            (while (zerop (forward-line -1))
-              (push (buffer-substring
-                     (point) (tramp-compat-line-end-position))
-                    result)))
-
-          (tramp-set-file-property
-           v localname "last-completion" (current-time))
-          result)))))))
+        (or
+         ;; Try cache first
+         (and
+          ;; Ignore if expired
+          (or (not (integerp tramp-completion-reread-directory-timeout))
+              (<= (tramp-time-diff
+                   (current-time)
+                   (tramp-get-file-property
+                    v localname "last-completion" '(0 0 0)))
+                  tramp-completion-reread-directory-timeout))
+
+          ;; Try cache entries for filename, filename with last
+          ;; character removed, filename with last two characters
+          ;; removed, ..., and finally the empty string - all
+          ;; concatenated to the local directory name
+
+          ;; This is inefficient for very long filenames, pity
+          ;; `reduce' is not available...
+          (car
+           (apply
+            'append
+            (mapcar
+             (lambda (x)
+               (let ((cache-hit
+                      (tramp-get-file-property
+                       v
+                       (concat localname (substring filename 0 x))
+                       "file-name-all-completions"
+                       nil)))
+                 (when cache-hit (list cache-hit))))
+             (number-sequence (length filename) 0 -1)))))
+
+         ;; Cache expired or no matching cache entry found so we need
+         ;; to perform a remote operation
+         (let (result)
+           ;; Get a list of directories and files, including reliably
+           ;; tagging the directories with a trailing '/'.  Because I
+           ;; rock.  address@hidden
+
+           ;; Changed to perform `cd' in the same remote op and only
+           ;; get entries starting with `filename'. Capture any `cd'
+           ;; error messages.  Ensure any `cd' and `echo' aliases are
+           ;; ignored.
+           (tramp-send-command
+            v
+            (if (tramp-get-remote-perl v)
+                (progn
+                  (tramp-maybe-send-script
+                   v tramp-perl-file-name-all-completions
+                   "tramp_perl_file_name_all_completions")
+                  (format "tramp_perl_file_name_all_completions %s %s %d"
+                          (tramp-shell-quote-argument localname)
+                          (tramp-shell-quote-argument filename)
+                          (if read-file-name-completion-ignore-case 1 0)))
+
+              (format (concat
+                       "(\\cd %s 2>&1 && (%s %s -a 2>/dev/null"
+                       ;; `ls' with wildcard might fail with `Argument
+                       ;; list too long' error in some corner cases; if
+                       ;; `ls' fails after `cd' succeeded, chances are
+                       ;; that's the case, so let's retry without
+                       ;; wildcard.  This will return "too many" entries
+                       ;; but that isn't harmful.
+                       " || %s -a 2>/dev/null)"
+                       " | while read f; do"
+                       " if %s -d \"$f\" 2>/dev/null;"
+                       " then \\echo \"$f/\"; else \\echo \"$f\"; fi; done"
+                       " && \\echo ok) || \\echo fail")
+                      (tramp-shell-quote-argument localname)
+                      (tramp-get-ls-command v)
+                      ;; When `filename' is empty, just `ls' without
+                      ;; filename argument is more efficient than `ls *'
+                      ;; for very large directories and might avoid the
+                      ;; `Argument list too long' error.
+                      ;;
+                      ;; With and only with wildcard, we need to add
+                      ;; `-d' to prevent `ls' from descending into
+                      ;; sub-directories.
+                      (if (zerop (length filename))
+                          "."
+                        (concat (tramp-shell-quote-argument filename) "* -d"))
+                      (tramp-get-ls-command v)
+                      (tramp-get-test-command v))))
+
+           ;; Now grab the output.
+           (with-current-buffer (tramp-get-buffer v)
+             (goto-char (point-max))
+
+             ;; Check result code, found in last line of output
+             (forward-line -1)
+             (if (looking-at "^fail$")
+                 (progn
+                   ;; Grab error message from line before last line
+                   ;; (it was put there by `cd 2>&1')
+                   (forward-line -1)
+                   (tramp-error
+                    v 'file-error
+                    "tramp-handle-file-name-all-completions: %s"
+                    (buffer-substring
+                     (point) (tramp-compat-line-end-position))))
+               ;; For peace of mind, if buffer doesn't end in `fail'
+               ;; then it should end in `ok'.  If neither are in the
+               ;; buffer something went seriously wrong on the remote
+               ;; side.
+               (unless (looking-at "^ok$")
+                 (tramp-error
+                  v 'file-error
+                  "\
+tramp-handle-file-name-all-completions: internal error accessing `%s': `%s'"
+                  (tramp-shell-quote-argument localname) (buffer-string))))
+
+             (while (zerop (forward-line -1))
+               (push (buffer-substring
+                      (point) (tramp-compat-line-end-position))
+                     result)))
+
+           ;; Because the remote op went through OK we know the
+           ;; directory we `cd'-ed to exists
+           (tramp-set-file-property
+            v localname "file-exists-p" t)
+
+           ;; Because the remote op went through OK we know every
+           ;; file listed by `ls' exists.
+           (mapcar (lambda (entry)
+                     (tramp-set-file-property
+                      v (concat localname entry) "file-exists-p" t))
+                   result)
+
+           (tramp-set-file-property
+            v localname "last-completion" (current-time))
+
+           ;; Store result in the cache
+           (tramp-set-file-property
+            v (concat localname filename)
+            "file-name-all-completions"
+            result))))))))
 
 ;; The following isn't needed for Emacs 20 but for 19.34?
 (defun tramp-handle-file-name-completion
-- 
1.6.4





reply via email to

[Prev in Thread] Current Thread [Next in Thread]