branch: elpa/dirvish
commit a7f8e93f92f0b387b4a6c156dcd05ea03c0557ba
Author: Alex Lu <hellosimon1...@hotmail.com>
Commit: Alex Lu <hellosimon1...@hotmail.com>

    feat: modular directory data fetching (#162 #238)
    
    We now fetch basic data (file attributes, file types) and version control 
data
    separately.  Both of these jobs are done asynchronously still.  In our 
previous
    implementation, these data were retrieved from a single process, sometimes 
Emacs
    had a hard time parsing the data, which invokes GC then freezes Emacs for a
    moment.
    
    closes #162: as git process wouldn't be spawned when a directory is not 
being
    version controlled.
    
    closes #238: as git process wouldn't be spawned on a "remote" host, in this 
case
    , a virtually remote host.
---
 dirvish-tramp.el              |  15 +++--
 dirvish.el                    | 149 ++++++++++++++++++++----------------------
 extensions/dirvish-subtree.el |   2 +-
 extensions/dirvish-vc.el      |  45 ++++++++++++-
 extensions/dirvish-yank.el    |   5 +-
 5 files changed, 128 insertions(+), 88 deletions(-)

diff --git a/dirvish-tramp.el b/dirvish-tramp.el
index 4cbafb22b5..f331aff7b0 100644
--- a/dirvish-tramp.el
+++ b/dirvish-tramp.el
@@ -72,26 +72,29 @@ FN is the original `dired-noselect' closure."
 (defun dirvish-tramp-dir-data-proc-s (proc _exit)
   "Sentinel for `dirvish-data-for-dir''s process PROC."
   (unwind-protect
-      (pcase-let* ((`(,dir ,buf ,setup) (process-get proc 'meta))
+      (pcase-let* ((`(,dir ,buf ,inhibit-setup) (process-get proc 'meta))
                    (str (with-current-buffer (process-buffer proc)
                           (substring-no-properties (buffer-string))))
                    (data (split-string str "\n")))
         (when (buffer-live-p buf)
           (with-current-buffer buf
             (dirvish-tramp--ls-parser dir data)
-            (when setup (run-hooks 'dirvish-setup-hook))
+            (unless inhibit-setup (run-hooks 'dirvish-setup-hook))
             (unless (derived-mode-p 'wdired-mode) (dirvish-update-body-h)))))
     (dirvish--kill-buffer (process-buffer proc))))
 
 (cl-defmethod dirvish-data-for-dir
-  (dir buffer setup &context ((dirvish-prop :remote) string))
-  "DIR BUFFER SETUP DIRVISH-PROP."
+  (dir buffer inhibit-setup &context ((dirvish-prop :remote) string))
+  "Fetch data for DIR in BUFFER.
+It is called when DIRVISH-PROP has key `:remote' as a string, which
+means DIR is in a remote host.  Run `dirvish-setup-hook' after data
+parsing unless INHIBIT-SETUP is non-nil."
   (when (dirvish-tramp--async-p (dirvish-prop :tramp))
     (let* ((process-connection-type nil)
-           (buf (dirvish--util-buffer (make-temp-name "dir-data-")))
+           (buf (dirvish--util-buffer (make-temp-name "dir-tramp-")))
            (cmd (format "ls -1lahi %s" (file-local-name dir)))
            (proc (start-file-process-shell-command (buffer-name buf) buf cmd)))
-      (process-put proc 'meta (list dir buffer setup))
+      (process-put proc 'meta (list dir buffer inhibit-setup))
       (set-process-sentinel proc #'dirvish-tramp-dir-data-proc-s))))
 
 (dirvish-define-preview tramp (file _ dv)
diff --git a/dirvish.el b/dirvish.el
index 9a88decd68..ae9874b2a0 100644
--- a/dirvish.el
+++ b/dirvish.el
@@ -486,6 +486,18 @@ If INHIBIT-HIDING is non-nil, do not hide the buffer."
          (name (format "%s*Dirvish-%s%s" (if inhibit-hiding "" " ") type id)))
     (if no-create (get-buffer name) (get-buffer-create name))))
 
+(defun dirvish--make-proc (form sentinel buffer-or-name &rest puts)
+  "Make process for shell or batch FORM in BUFFER-OR-NAME.
+Set process's SENTINEL had PUTS accordingly."
+  (let* ((buf (or buffer-or-name (make-temp-name "*dirvish-batch*")))
+         (print-length nil) (print-level nil)
+         (cmd (if (stringp (car form)) form
+                (list dirvish-emacs-bin
+                      "-Q" "-batch" "--eval" (prin1-to-string form))))
+         (proc (make-process :name "dirvish" :connection-type nil :buffer buf
+                             :command cmd :sentinel sentinel)))
+    (while-let ((k (pop puts)) (v (pop puts))) (process-put proc k v))))
+
 ;;;; Core
 
 (cl-defstruct (dirvish (:conc-name dv-))
@@ -701,7 +713,7 @@ buffer, it defaults to filename under the cursor when it is 
nil."
 (defun dirvish-insert-subdir-a (dirname &rest _)
   "Setup newly inserted subdir DIRNAME for this Dirvish buffer."
   (dirvish--hide-dired-header)
-  (dirvish-data-for-dir dirname (current-buffer) nil))
+  (dirvish--dir-data-async dirname (current-buffer) t))
 
 (defun dirvish-wdired-enter-a (&rest _)
   "Advice for `wdired-change-to-wdired-mode'."
@@ -883,17 +895,14 @@ When FORCE, ensure the preview get refreshed."
   (when-let* ((attrs (ignore-errors (file-attributes file)))
               (size (file-attribute-size attrs)))
     (cond ((file-directory-p file) ; default directory previewer
-           (let* ((script
-                   `(with-current-buffer
-                        (let ,(mapcar (lambda (env) `(,(car env) ,(cdr env)))
-                                      (remove (cons 'inhibit-message t)
-                                              dirvish-preview-environment))
-                          (setq insert-directory-program
-                                ,insert-directory-program)
-                          (dired-noselect ,file "-AlGh"))
-                      (buffer-string)))
-                  (cmd (prin1-to-string `(message "\n%s" ,script))))
-             `(dired . (,dirvish-emacs-bin "-Q" "-batch" "--eval" ,cmd))))
+           (let ((script
+                  `(let ,(mapcar (lambda (env) `(,(car env) ,(cdr env)))
+                                 (remove (cons 'inhibit-message t)
+                                         dirvish-preview-environment))
+                     (setq insert-directory-program ,insert-directory-program)
+                     (with-current-buffer (dired-noselect ,file "-AlGh")
+                       (message "\n%s" (buffer-string))))))
+             `(dired . ,script)))
           ((> size (or large-file-warning-threshold 10000000))
            `(info . ,(format "File %s is too big for literal preview." file)))
           ((member ext dirvish-media-exts)
@@ -935,13 +944,12 @@ When PROC finishes, fill preview buffer with process 
result."
 
 (defun dirvish--run-shell-for-preview (dv recipe)
   "Dispatch shell cmd with RECIPE for session DV."
-  (when-let* ((proc (get-buffer-process (get-buffer " *Dirvish-temp*"))))
-    (delete-process proc))
-  (let ((buf (dirvish--util-buffer 'preview dv nil t))
-        (proc (make-process :name "sh-out" :connection-type nil
-                            :buffer " *Dirvish-temp*" :command (cdr recipe)
-                            :sentinel 'dirvish-shell-preview-proc-s)))
-    (process-put proc 'cmd-info (car recipe))
+  (let ((proc (get-buffer-process (get-buffer " *dirvish-sh*")))
+        (buf (dirvish--util-buffer 'preview dv nil t)))
+    (when proc (delete-process proc))
+    (dirvish--make-proc
+     (cdr recipe) 'dirvish-shell-preview-proc-s " *dirvish-sh*"
+     'cmd-info (car recipe))
     (with-current-buffer buf (erase-buffer) (remove-overlays) buf)))
 
 (cl-defmethod dirvish-preview-dispatch ((recipe (head shell)) dv)
@@ -1103,7 +1111,7 @@ Dirvish sets `revert-buffer-function' to this function."
   (dirvish--hide-dired-header)
   (when ignore-auto ; meaning it is called interactively from user
     (setq-local dirvish--attrs-hash (make-hash-table))
-    (dirvish-data-for-dir default-directory (current-buffer) t))
+    (dirvish--dir-data-async default-directory (current-buffer)))
   (run-hooks 'dirvish-after-revert-hook))
 
 (defun dirvish-init-dired-buffer ()
@@ -1201,63 +1209,50 @@ LEVEL is the depth of current window."
     (setq cursor-type nil window-size-fixed 'height
           mode-line-format nil header-line-format nil)))
 
-(defsubst dirvish--dir-data-getter (dir)
-  "Script for DIR data retrieving."
-  `(with-temp-buffer
-     (let ((hash (make-hash-table))
-           (bk (ignore-errors (vc-responsible-backend ,dir))))
-       ;; keep this until `vc-git' fixed upstream.  See: #224 and #273
-       (advice-add #'vc-git--git-status-to-vc-state :around
-                   (lambda (fn code-list)
-                     (apply fn (list (delete-dups code-list)))))
-       (dolist (file (directory-files ,dir t nil t))
-         (let* ((attrs (file-attributes file))
-                (state (and bk (vc-state-refresh file bk)))
-                ;; TODO: eventually this should belong to `dirvish-vc'
-                ;; we spawn a separate process to deal with git stuff.
-                (git (and (eq bk 'Git)
-                          (shell-command-to-string
-                           (format "git log -1 --pretty=%%s %s"
-                                   (shell-quote-argument file)))))
-                (tp (nth 0 attrs)))
-           (cond
-            ((eq t tp) (setq tp '(dir . nil)))
-            (tp (setq tp `(,(if (file-directory-p tp) 'dir 'file) . ,tp)))
-            (t (setq tp '(file . nil))))
+(defun dirvish--dir-data-async (dir buffer &optional inhibit-setup)
+  "Asynchronously fetch metadata for DIR, stored locally in BUFFER.
+INHIBIT-SETUP is passed to `dirvish-data-for-dir'."
+  (dirvish--make-proc
+   `(prin1
+     (let* ((hs (make-hash-table))
+            (remote? (file-remote-p ,dir))
+            (bk (unless remote? (vc-responsible-backend ,dir t))))
+       (dolist (file (unless remote? (directory-files ,dir t nil t)))
+         (let* ((attrs (file-attributes file)) (tp (nth 0 attrs)))
+           (cond ((eq t tp) (setq tp '(dir . nil)))
+                 (tp (setq tp `(,(if (file-directory-p tp) 'dir 'file) . ,tp)))
+                 (t (setq tp '(file . nil))))
            (puthash (intern (secure-hash 'md5 file))
-                    `(:builtin ,attrs :type ,tp
-                               ,@(and state (list :vc-state state))
-                               ,@(and git (list :git-msg git)))
-                    hash)))
-       (prin1 (cons bk hash) (current-buffer)))
-     (buffer-substring-no-properties (point-min) (point-max))))
-
-(defun dirvish-dir-data-proc-s (proc _exit)
-  "Parse the directory metadata from PROC's output STR."
-  (pcase-let ((`(,buf . ,setup) (process-get proc 'meta))
-              (`(,vc . ,data) (with-current-buffer (process-buffer proc)
-                                (read (buffer-string)))))
-    (when (buffer-live-p buf)
-      (with-current-buffer buf
-        (maphash (lambda (k v) (puthash k v dirvish--attrs-hash)) data)
-        (when setup
-          (dirvish-prop :vc-backend vc)
-          (run-hooks 'dirvish-setup-hook))
-        (unless (derived-mode-p 'wdired-mode) (dirvish-update-body-h)))))
-  (delete-process proc)
-  (dirvish--kill-buffer (process-buffer proc)))
-
-(cl-defgeneric dirvish-data-for-dir (dir buffer setup)
-  "Fetch data for files in DIR, stored locally in BUFFER.
-Run `dirvish-setup-hook' afterwards when SETUP is non-nil."
-  (let* ((buf (make-temp-name "dir-data-"))
-         (print-length nil)
-         (print-level nil)
-         (c (format "%S" `(message "%s" ,(dirvish--dir-data-getter dir))))
-         (proc (make-process :name "dir-data" :connection-type nil :buffer buf
-                             :command (list dirvish-emacs-bin "-Q" "-batch" 
"--eval" c)
-                             :sentinel 'dirvish-dir-data-proc-s)))
-    (process-put proc 'meta (cons buffer setup))))
+                    `(:builtin ,attrs :type ,tp) hs)))
+       (cons bk hs)))
+   (lambda (p _)
+     (pcase-let ((`(,buf . ,inhibit-setup) (process-get p 'meta))
+                 (`(,vc . ,data)
+                  (with-current-buffer (process-buffer p)
+                    (read (buffer-string)))))
+       (when (buffer-live-p buf)
+         (with-current-buffer buf
+           (maphash (lambda (k v) (puthash k v dirvish--attrs-hash)) data)
+           (dirvish-prop :vc-backend vc)
+           (dirvish-data-for-dir dir buf inhibit-setup))))
+     (delete-process p)
+     (dirvish--kill-buffer (process-buffer p)))
+   nil 'meta (cons buffer inhibit-setup)))
+
+(cl-defgeneric dirvish-data-for-dir (dir buffer inhibit-setup)
+  "Fetch data for DIR in BUFFER, maybe INHIBIT-SETUP.")
+
+(cl-defmethod dirvish-data-for-dir
+  (dir buffer inhibit-setup
+       &context ((dirvish-prop :vc-backend) boolean)
+       &context ((dirvish-prop :remote) boolean))
+  "Fetch data for DIR in BUFFER.
+It is called when neither `:vc-backend' nor `:remote' is included in
+DIRVISH-PROPs, i.e. DIR is in localhost and is not being
+version-controlled.  Run `dirvish-setup-hook' after data parsing unless
+INHIBIT-SETUP is non-nil."
+  (unless (derived-mode-p 'wdired-mode) (dirvish-update-body-h))
+  (unless inhibit-setup (run-hooks 'dirvish-setup-hook)))
 
 (defun dirvish--window-split-order ()
   "Compute the window split order."
@@ -1299,7 +1294,7 @@ Run `dirvish-setup-hook' afterwards when SETUP is 
non-nil."
       (let ((window-safe-min-height 0) (window-resize-pixelwise t))
         (dolist (win util-windows) (fit-window-to-buffer win 2 1))))
     (unless (dirvish-prop :cached)
-      (dirvish-data-for-dir default-directory (current-buffer) t)
+      (dirvish--dir-data-async default-directory (current-buffer))
       (dirvish-prop :cached t))
     (setq dirvish--this dv)
     (dirvish--maybe-toggle-cursor)
diff --git a/extensions/dirvish-subtree.el b/extensions/dirvish-subtree.el
index 70699cdeb0..2441eaba98 100644
--- a/extensions/dirvish-subtree.el
+++ b/extensions/dirvish-subtree.el
@@ -212,7 +212,7 @@ creation even the entry is in nested subtree nodes."
   (let* ((dir (dired-get-filename))
          (listing (dirvish-subtree--readin dir))
          buffer-read-only beg end)
-    (dirvish-data-for-dir dir (current-buffer) nil)
+    (dirvish--dir-data-async dir (current-buffer) t)
     (with-silent-modifications
       (save-excursion
         (setq beg (progn (move-end-of-line 1) (insert "\n") (point)))
diff --git a/extensions/dirvish-vc.el b/extensions/dirvish-vc.el
index 36d9b27bc0..a192e25c0d 100644
--- a/extensions/dirvish-vc.el
+++ b/extensions/dirvish-vc.el
@@ -99,6 +99,48 @@ detail explanation of these states."
 
 (defvar vc-dir-process-buffer)
 
+(cl-defmethod dirvish-data-for-dir
+  (dir buffer inhibit-setup
+       &context ((dirvish-prop :vc-backend) symbol)
+       &context ((dirvish-prop :remote) boolean))
+  "Fetch data for DIR in BUFFER.
+It is called when `:vc-backend' is included in DIRVISH-PROPs while
+`:remote' is not, i.e. a local version-controlled directory.  Run
+`dirvish-setup-hook' after data parsing unless INHIBIT-SETUP is non-nil."
+  (dirvish--make-proc
+   `(prin1
+     (let ((hs (make-hash-table)) (bk ',(dirvish-prop :vc-backend)))
+       ;; keep this until `vc-git' fixed upstream.  See: #224 and #273
+       (advice-add #'vc-git--git-status-to-vc-state :around
+                   (lambda (fn codes) (apply fn (list (delete-dups codes)))))
+       (dolist (file (directory-files ,dir t nil t))
+         (let ((state (vc-state-refresh file bk))
+               (msg (and (eq bk 'Git)
+                         (shell-command-to-string
+                          (format "git log -1 --pretty=%%s %s"
+                                  (shell-quote-argument file))))))
+           (puthash (intern (secure-hash 'md5 file))
+                    `(:vc-state ,state :git-msg ,msg) hs)))
+       hs))
+   (lambda (p _)
+     (pcase-let ((`(,buf . ,inhibit-setup) (process-get p 'meta))
+                 (data (with-current-buffer (process-buffer p)
+                         (read (buffer-string)))))
+       (when (buffer-live-p buf)
+         (with-current-buffer buf
+           (maphash
+            (lambda (k v)
+              (let ((orig (gethash k dirvish--attrs-hash)))
+                (setf (plist-get orig :vc-state) (plist-get v :vc-state))
+                (setf (plist-get orig :git-msg) (plist-get v :git-msg))
+                (puthash k orig dirvish--attrs-hash)))
+            data)
+           (unless (derived-mode-p 'wdired-mode) (dirvish-update-body-h))
+           (unless inhibit-setup (run-hooks 'dirvish-setup-hook)))))
+     (delete-process p)
+     (dirvish--kill-buffer (process-buffer p)))
+   nil 'meta (cons buffer inhibit-setup)))
+
 (cl-defmethod transient-infix-set ((obj dirvish-vc-preview) value)
   "Set relevant value in DIRVISH-VC-PREVIEW instance OBJ to VALUE."
   (oset obj value value)
@@ -137,8 +179,7 @@ This attribute only works on graphic displays."
                 (state (dirvish-attribute-cache f-name :vc-state))
                 (face (alist-get state dirvish-vc-state-face-alist))
                 (display `(left-fringe dirvish-vc-gutter . ,(cons face nil))))
-      (overlay-put ; TODO: very slow when the directory doesn't have any commit
-       ov 'before-string (propertize " " 'display display)))
+      (overlay-put ov 'before-string (propertize " " 'display display)))
     `(ov . ,ov)))
 
 (dirvish-define-attribute git-msg
diff --git a/extensions/dirvish-yank.el b/extensions/dirvish-yank.el
index 1197fa5361..ce7d91b342 100644
--- a/extensions/dirvish-yank.el
+++ b/extensions/dirvish-yank.el
@@ -334,9 +334,10 @@ It sets the value for every variable matching 
INCLUDE-REGEXP."
                   finally (cl-loop for b in (buffer-list) thereis
                                    (and (string-match "\\`\\*ftp.*"
                                                       (buffer-name b))
-                                        (prog1 b (kill-buffer b))))))))
+                                        (prog1 b (kill-buffer b)))))))
+         print-level print-length)
     (dirvish-yank--execute
-     (format "%S" cmd) (list (current-buffer) srcs dest method) 'batch)))
+     (prin1-to-string cmd) (list (current-buffer) srcs dest method) 'batch)))
 
 (defun dirvish-yank--apply (method dest)
   "Apply yank METHOD to DEST."

Reply via email to