branch: elpa/dirvish commit e59c4f501ff595e7ae1df1543176d0ea25e050db Author: Alex Lu <hellosimon1...@hotmail.com> Commit: Alex Lu <hellosimon1...@hotmail.com>
feat(narrow): asynchronous filtering everything.exe --- dirvish-extras.el | 2 +- dirvish-widgets.el | 17 ++- dirvish.el | 18 ++- extensions/dirvish-collapse.el | 2 +- extensions/dirvish-emerge.el | 2 +- extensions/dirvish-fd.el | 327 ++++++++++++++++++----------------------- extensions/dirvish-narrow.el | 147 +++++++++--------- 7 files changed, 254 insertions(+), 261 deletions(-) diff --git a/dirvish-extras.el b/dirvish-extras.el index 4a8bcad303..ec1f8dc576 100644 --- a/dirvish-extras.el +++ b/dirvish-extras.el @@ -426,7 +426,7 @@ current layout defined in `dirvish-layout-recipes'." ("s" "Sort current buffer" dirvish-quicksort) ("l" "Setup listing switches" dirvish-ls-switches-menu) ("f" "Setup fd-find switches" dirvish-fd-switches-menu - :if (lambda () (dirvish-prop :fd-arglist))) + :if (lambda () (dirvish-prop :fd-info))) ("S" "Setup rsync switches" dirvish-rsync-switches-menu) ("m" "Manage marks" dirvish-mark-menu) ("e" "Manage emerged groups" dirvish-emerge-menu) diff --git a/dirvish-widgets.el b/dirvish-widgets.el index 8f34562ecb..95cb3b14d8 100644 --- a/dirvish-widgets.el +++ b/dirvish-widgets.el @@ -426,16 +426,19 @@ GROUP-TITLES is a list of group titles." (propertize truename 'face 'dired-symlink)))) (dirvish-define-mode-line index - "Current file's index and total files count." - (let* ((ct (dirvish-prop :count)) (cpos (- (line-number-at-pos (point)) 1)) - (fpos (- (line-number-at-pos (point-max)) 2)) - (cur (if ct "" (format "%3d " cpos))) - (end (if ct (format " found %s matches " ct) (format "/%3d " fpos)))) - (if (or (dirvish--selected-p) ct) + "Cursor file's index and total files count within current subdir." + (let* ((count (if (cdr dired-subdir-alist) + (format "[ %s subdirs ] " (length dired-subdir-alist)) "")) + (smin (line-number-at-pos (dired-subdir-min))) + (cpos (- (line-number-at-pos (point)) smin)) + (fpos (- (line-number-at-pos (dired-subdir-max)) smin 1)) + (cur (format "%3d " cpos)) (end (format "/%3d " fpos))) + (if (dirvish--selected-p) (put-text-property 0 (length end) 'face 'bold end) + (put-text-property 0 (length count) 'face 'dirvish-inactive count) (put-text-property 0 (length cur) 'face 'dirvish-inactive cur) (put-text-property 0 (length end) 'face 'dirvish-inactive end)) - (format "%s%s" cur end))) + (format "%s%s%s" cur end count))) (dirvish-define-mode-line free-space "Amount of free space on `default-directory''s file system." diff --git a/dirvish.el b/dirvish.el index 02381104b7..59cbe2a382 100644 --- a/dirvish.el +++ b/dirvish.el @@ -117,6 +117,21 @@ the full-frame layout when file previews are needed." "Face used for mode-line segments in unfocused Dirvish windows." :group 'dirvish) +(defface dirvish-proc-running + '((t :inherit warning)) + "Face used if asynchronous process is running." + :group 'dirvish) + +(defface dirvish-proc-finished + '((t :inherit success)) + "Face used if asynchronous process has finished." + :group 'dirvish) + +(defface dirvish-proc-failed + '((t :inherit error)) + "Face used if asynchronous process has failed." + :group 'dirvish) + (defcustom dirvish-use-mode-line t "Whether to display mode line in dirvish buffers. The valid value are: @@ -903,6 +918,7 @@ When the attribute does not exist, set it with BODY." for (k . v) = (funcall fn f-beg f-end f-str f-name f-attrs f-type l-beg l-end hl-face w-width) do (pcase k ('ov (overlay-put v 'dirvish-a-ov t)) + ('ovs (dolist (ov v) (overlay-put ov 'dirvish-a-ov t))) ('left (setq left (concat v left))) ('right (setq right (concat v right)))) finally @@ -1061,7 +1077,7 @@ use `car'. If HEADER, use `dirvish-header-line-height' instead." (defun dirvish--apply-hiding-p (ctx) "Return t when it should hide cursor/details within context CTX." (cond ((booleanp ctx) ctx) - ((dirvish-prop :fd-arglist) + ((dirvish-prop :fd-info) (memq 'dirvish-fd ctx)) ((and (dirvish-curr) (dv-curr-layout (dirvish-curr))) (memq 'dirvish ctx)) diff --git a/extensions/dirvish-collapse.el b/extensions/dirvish-collapse.el index 528b62bed7..bf3a914812 100644 --- a/extensions/dirvish-collapse.el +++ b/extensions/dirvish-collapse.el @@ -71,7 +71,7 @@ (dirvish-define-attribute collapse "Collapse unique nested paths." - :when (and (not (dirvish-prop :fd-arglist)) + :when (and (not (dirvish-prop :fd-info)) (not (dirvish-prop :remote))) (when-let* ((cache (dirvish-collapse--cache f-name)) (head (car cache)) diff --git a/extensions/dirvish-emerge.el b/extensions/dirvish-emerge.el index 4e2633527c..fb87f34890 100644 --- a/extensions/dirvish-emerge.el +++ b/extensions/dirvish-emerge.el @@ -454,7 +454,7 @@ PREDS are locally composed predicates." (defun dirvish-emerge--apply () "Readin `dirvish-emerge-groups' and apply them." - (when (and (not (dirvish-prop :fd-arglist)) + (when (and (not (dirvish-prop :fd-info)) (or (dirvish-prop :force-emerge) (< (hash-table-count dirvish--dir-data) dirvish-emerge-max-file-count))) diff --git a/extensions/dirvish-fd.el b/extensions/dirvish-fd.el index e250dd0177..6bbfe92fa8 100644 --- a/extensions/dirvish-fd.el +++ b/extensions/dirvish-fd.el @@ -61,14 +61,10 @@ "Listing program for `fd'." :type '(string :tag "Listing program, such as `ls'") :group 'dirvish) -(defcustom dirvish-fd-default-dir "/" - "Default directory for `dirvish-fd-jump'." - :group 'dirvish :type 'directory) -(defconst dirvish-fd-bufname "π%sπ%sπ") -(defconst dirvish-fd-header - (dirvish--mode-line-composer '(fd-switches) '(fd-took) t)) -(defvar-local dirvish-fd--input "" "Last used fd user input.") +(defconst dirvish-fd-bufname "π%sπ%sπ") ; TODO: remove this +(defconst dirvish-fd-header ; TODO: refactor this + (dirvish--mode-line-composer '(fd-info) '(fd-status) t)) (defun dirvish-fd--ensure-fd (remote) "Return fd executable on REMOTE or localhost. @@ -79,46 +75,36 @@ Raise an error if fd executable is not available." (defun dirvish-fd--apply-switches () "Apply fd SWITCHES to current buffer." (interactive) - (let* ((args (transient-args transient-current-command)) - (switches (string-join args " "))) - (dirvish-prop :fd-switches switches) - (revert-buffer))) + (cl-loop with (re . args) = nil + for arg in (transient-args transient-current-command) + if (string-prefix-p "--and=" arg) do (push arg re) + else do (push arg args) + finally do (dirvish-fd--argparser re args)) + (revert-buffer)) (transient-define-infix dirvish-fd--extensions-switch () :description "Filter results by file extensions" :class 'transient-option :argument "--extension=" - :multi-value 'repeat - :prompt - (lambda (o) - (let* ((val (oref o value)) - (str (if val (format "(current: %s) " (mapconcat #'concat val ",")) ""))) - (format "%sFile exts separated with comma: " str)))) + :multi-value 'repeat) (transient-define-infix dirvish-fd--exclude-switch () :description "Exclude files/dirs that match the glob pattern" :class 'transient-option :argument "--exclude=" - :multi-value 'repeat - :prompt - (lambda (o) - (let* ((val (oref o value)) - (str (if val (format "(current: %s) " (mapconcat #'concat val ",")) ""))) - (format "%sGlob patterns (such as *.pyc) separated with comma: " str)))) + :multi-value 'repeat) (transient-define-infix dirvish-fd--search-pattern-infix () - "Change search pattern." - :description "Change search pattern" - :class 'transient-lisp-variable - :variable 'dirvish-fd--input - :reader (lambda (_prompt init hist) - (completing-read "Regex for fd: " nil nil nil init hist))) + :description "Change search patterns" + :class 'transient-option + :argument "--and=" + :multi-value 'repeat) ;;;###autoload (autoload 'dirvish-fd-switches-menu "dirvish-fd" nil t) (transient-define-prefix dirvish-fd-switches-menu () "Setup fd switches." - :init-value - (lambda (o) (oset o value (split-string (or (dirvish-prop :fd-switches) "")))) + :init-value (lambda (o) (let ((args (dirvish-prop :fd-info))) + (oset o value (append (cadr args) (cddr args))))) [:description (lambda () (dirvish--format-menu-heading "Setup FD Switches" @@ -171,35 +157,40 @@ Raise an error if fd executable is not available." "" "Actions" ("r" dirvish-fd--search-pattern-infix) - ("RET" "Apply switches" dirvish-fd--apply-switches)]]) + ("RET" "Rerun" dirvish-fd--apply-switches)]]) -(defun dirvish-fd--argparser (args) - "Parse fd args to a list of flags from ARGS." +(defun dirvish-fd--argparser (re args) + "Parse fd args to a list of flags from ARGS and search regexp RE." (let* ((globp (member "--glob" args)) (casep (member "--case-sensitive" args)) - (ign-range (cond ((member "--no-ignore" args) "no") - ((member "--no-ignore-vcs" args) "no_vcs") - (t "all"))) - types exts excludes) + (ign (cond ((member "--no-ignore" args) "no") + ((member "--no-ignore-vcs" args) "no_vcs") + (t "all"))) + (status (propertize " β " 'face 'dirvish-proc-running)) + comp types exts exc) (dolist (arg args) - (cond ((string-prefix-p "--type=" arg) (push (substring arg 8) types)) + (cond ((string-prefix-p "--type=" arg) (push (substring arg 7) types)) ((string-prefix-p "--extension=" arg) (push (substring arg 12) exts)) - ((string-prefix-p "--exclude=" arg) (push (substring arg 10) excludes)))) + ((string-prefix-p "--exclude=" arg) (push (substring arg 10) exc)))) + (dolist (r re) (push (substring r 6) comp)) (setq types (mapconcat #'concat types ",")) (setq exts (mapconcat #'concat exts ",")) - (setq excludes (mapconcat #'concat excludes ",")) - (dirvish-prop :fd-arglist (list globp casep ign-range types exts excludes)))) - -(dirvish-define-mode-line fd-switches - "Return a formatted string showing the DIRVISH-FD-ACTUAL-SWITCHES." - (pcase-let ((`(,globp ,casep ,ign-range ,types ,exts ,excludes) - (dirvish-prop :fd-arglist)) + (setq exc (mapconcat #'concat exc ",")) + (setq comp (mapconcat #'concat comp ",")) + (dirvish-prop :fd-info + (cons (list comp globp casep ign types exts exc status) (cons re args))))) + +(dirvish-define-mode-line fd-info + "Return a formatted string showing the actual fd command line arguments." + (pcase-let ((`(,re ,globp ,casep ,ign-range ,types ,exts ,excludes ,_) + (car (dirvish-prop :fd-info))) (face (if (dirvish--selected-p) 'dired-header 'dirvish-inactive))) - (format " π β π: %s [ %s \"%s\" | %s %s | %s %s | %s %s | %s %s | %s %s ]" + (format " π β %s [ %s \"%s\" | %s %s | %s %s | %s %s | %s %s | %s %s ]" (propertize - (abbreviate-file-name default-directory) 'face 'dired-directory) + (abbreviate-file-name (directory-file-name default-directory)) + 'face 'dired-directory) (propertize (if globp "glob:" "regex:") 'face face) - (propertize (or dirvish-fd--input "") + (propertize (or re "") 'face 'font-lock-regexp-grouping-construct) (propertize "type:" 'face face) (propertize (if (equal types "") "all" types) @@ -216,167 +207,135 @@ Raise an error if fd executable is not available." (propertize (if (equal excludes "") "none" excludes) 'face 'font-lock-string-face)))) -(dirvish-define-mode-line fd-took - "Time took by last fd search." - (or (dirvish-prop :fd-time) - (format "%s %s %s" - (propertize "Fd indexingβ¦ " 'face 'warning) - (substitute-command-keys "\\[kill-current-buffer]") - (propertize "to abort" 'face 'warning)))) - -;;;###autoload -(defun dirvish-fd-jump (&optional current-dir-p) - "Browse directories using `fd' command. -This command takes a while to index all the directories the first time -you run it. After the indexing, it fires up instantly except for those -huge directories such as root. It is recommended to setup your -.fdignore properly before using this command. - -If called with \\`C-u' or if CURRENT-DIR-P holds the value 4, -search for directories in the current directory. Otherwise, -search for directories in `dirvish-fd-default-dir'. - -If prefixed twice with \\`C-u' or if CURRENT-DIR-P holds the -value 16, let the user choose the root directory of their search." - (interactive "p") - (let* ((base-dir (cond - ((eq current-dir-p 4) default-directory) - ((eq current-dir-p 16) - (let ((dir (car (find-file-read-args - "Select root directory: " nil)))) - (if (file-directory-p dir) - (file-name-as-directory dir) - (dirvish--get-parent-path dir)))) - (t dirvish-fd-default-dir))) - (remote (file-remote-p base-dir)) - (fd-program (dirvish-fd--ensure-fd remote))) - (let* ((command (concat fd-program " -H -td --color=never -0 . " - (file-local-name base-dir))) - (default-directory base-dir) - (output (shell-command-to-string command)) - (files-raw (split-string output "\0" t)) - (files (dirvish--completion-table-with-metadata - files-raw '((category . file)))) - (file (completing-read "Go to: " files)) - (full-file (concat remote file))) - (dired-jump nil full-file)))) - -(defun dirvish-fd-proc-filter (proc string) - "Filter for output STRING of `dirvish-fd''s processes PROC." - (let ((buf (process-buffer proc))) - (if (not (buffer-name buf)) (delete-process proc) - (with-current-buffer buf - (save-excursion - (save-restriction - (widen) - (let ((data (dirvish-prop :fd-cache)) buffer-read-only last file) - (goto-char (point-max)) (insert string) - (goto-char (process-mark proc)) - (or (looking-at "^") (forward-line 1)) - ;; strip " ./" prefix and collect data on complete lines - (while-let ((fb (search-forward " ./" nil t)) - (le (line-end-position)) ((eq (char-after le) 10))) - (delete-region fb (- fb 2)) - (setq file (buffer-substring (- fb 2) (- le 2)) last le) - (beginning-of-line) (insert " ") - (puthash file (buffer-substring (- (point) 2) (1+ le)) data) - (forward-line 1)) - (when last (move-marker (process-mark proc) (1+ last)))))))))) - -(defsubst dirvish-fd-revert (&rest _) - "Revert buffer function for fd buffer." - (dirvish-fd default-directory (or dirvish-fd--input ""))) - -(defun dirvish-fd-proc-sentinel (proc _) - "Sentinel for `dirvish-fd' process PROC." - (when-let* ((buf (process-buffer proc)) - ((buffer-live-p buf)) - (status (process-exit-status proc)) - (took (float-time (time-since (process-get proc 'start))))) - (unless (eq status 0) (user-error "`fd' exited with status: %s" status)) - (if (< took 1.0) - (setq took (format "%s ms" (round took 0.001))) - (setq took (format "%s secs" (/ (round took 0.001) 1000.0)))) - (with-current-buffer buf - (dirvish-prop :fd-time - (format " %s %s " - (propertize "Took:" 'face 'font-lock-doc-face) - (propertize took 'face 'success))) +(dirvish-define-mode-line fd-status + "Status and time took by last fd search." + (car (last (car (dirvish-prop :fd-info))))) + +(defun dirvish-fd--proc-filter (proc string) + "Filter for output STRING of `dirvish-fd''s process PROC." + (when-let* (((buffer-name (process-buffer proc))) + (target (process-get proc 'target)) ((buffer-live-p target))) + (with-current-buffer target + (save-excursion + (save-restriction + (widen) + (goto-char (cdar dired-subdir-alist)) (goto-char (dired-subdir-max)) + (cl-loop + with buffer-read-only = nil + with (_ regexps case-fold-search) = (dirvish-prop :narrow-info) + with string = (concat (process-get proc 'tail) string) + with splits = (split-string string "\n" t) + with tail = (car (last splits)) + with comp? = (string-suffix-p "\n" string) + for file in (if comp? splits (butlast splits)) + for f-beg = (string-match " ./" file) + for f-name = (substring file (+ f-beg 3)) + for f-line = (concat " " (substring file 0 f-beg) " " f-name "\n") + do (if (not regexps) (insert f-line) + (cl-loop for re in regexps + unless (string-match re f-name) return nil + finally do (insert f-line))) + finally do (process-put proc 'tail (unless comp? tail)))))))) + +(defun dirvish-fd--proc-sentinel (proc status) + "Sentinel for `dirvish-fd' process PROC and its STATUS." + (when-let* (((buffer-live-p (process-buffer proc))) + (took (float-time (time-since (process-get proc 'start)))) + (target (process-get proc 'target)) ((buffer-live-p target))) + (setq took (if (< took 1.0) (format "%s ms" (round took 0.001)) + (format "%s secs" (/ (round took 0.001) 1000.0)))) + (with-current-buffer target + (setf (car (last (car (dirvish-prop :fd-info)))) + (cond ((string-prefix-p "killed" status) + (propertize " β " 'face 'dirvish-proc-failed)) + ((string-prefix-p "finished" status) + (propertize (format "%s β " took) + 'face 'dirvish-proc-finished)) + (t (propertize " β " 'face 'dirvish-proc-failed)))) (run-hooks 'dirvish-fd-setup-hook)) (force-mode-line-update t))) +(defun dirvish-fd--start-proc () + "Start fd process." + (let* ((remote (file-remote-p default-directory)) + (fd (dirvish-fd--ensure-fd remote)) + (ls (dirvish-fd--find-gnu-ls remote)) + (fd-args (dirvish-prop :fd-info)) + (buf (get-buffer-create "*dirvish-fd*")) + process-connection-type proc) + (when-let* ((op (get-buffer-process buf))) (delete-process op)) + (setq proc (apply #'start-file-process "fd" buf + `(,fd "--color=never" ,@(cddr fd-args) ,@(cadr fd-args) + "--exec-batch" ,ls + ,@(or (split-string dired-actual-switches) "") + "--quoting-style=literal" "--directory"))) + (set-process-filter proc #'dirvish-fd--proc-filter) + (set-process-sentinel proc #'dirvish-fd--proc-sentinel) + (set-process-query-on-exit-flag proc nil) + (process-put proc 'start (float-time)) + (process-put proc 'target (current-buffer)))) + ;;;###autoload -(defun dirvish-fd (dir pattern) +(defun dirvish-fd (dir re) "Run `fd' on DIR and go into Dired mode on a buffer of the output. The command run is essentially: - fd --color=never -0 `dirvish-fd-switches' PATTERN - --exec-batch `dirvish-fd-ls-program' `dired-listing-switches' --directory." + fd --color=never `dirvish-fd-switches' + --and RE [--and RE1 --and RE2 β¦ ] + --exec-batch `dirvish-fd-ls-program' `dired-listing-switches' --directory + +If called with \\`C-u', prompt for the target directory, +`default-directory' is used. If prefixed with \\`C-u' twice, also +prompt for the search regex RE as a comma separated list." (interactive (list (and current-prefix-arg (read-directory-name "Fd target directory: " nil "" t)) - nil)) + (and (equal current-prefix-arg '(16)) + (completing-read-multiple "Pattern: " nil)))) (setq dir (file-name-as-directory - (expand-file-name (or dir default-directory)))) + (expand-file-name (or dir default-directory))) + re (mapcan (lambda (x) `(,(format "--and=%s" x))) + (if (stringp re) (split-string re ",") re))) (or (file-directory-p dir) (user-error "'fd' requires a directory: %s" dir)) - (let* ((remote (file-remote-p dir)) - (fd-program (dirvish-fd--ensure-fd remote)) - (ls-program (dirvish-fd--find-gnu-ls remote)) - (dv (or (dirvish-curr) (dirvish--get-session) (dirvish--new))) - (fd-switches (or (dirvish-prop :fd-switches) dirvish-fd-switches "")) + (let* ((dv (or (dirvish-curr) (dirvish--get-session) (dirvish--new))) (ls-switches (or dired-actual-switches (dv-ls-switches dv))) - (buffer (get-buffer-create "*dirvish-fd*")) - (root (format dirvish-fd-bufname (or pattern "") + (root (format dirvish-fd-bufname (or re "") (file-name-nondirectory (directory-file-name dir)))) - (bname (concat root (dirvish--timestamp))) process-connection-type proc) - (with-current-buffer buffer + (buffer (get-buffer-create (concat root (dirvish--timestamp)))) + (fd (dirvish-prop :fd-info)) (re (or re (cadr fd))) + (switches (or (cddr fd) (split-string dirvish-fd-switches))) + remote) + (setf (dv-index dv) (cons root buffer)) + (cl-pushnew (cons root buffer) (dv-roots dv) :test #'equal) + (with-current-buffer buffer ; TODO: session independent? (let (buffer-read-only) (erase-buffer)) (insert " " dir ":" (make-string (dirvish--subdir-offset) ?\n)) (dired-mode dir ls-switches) (setq-local default-directory dir - dired-subdir-alist (list (cons dir (point-min-marker))) - dirvish-fd--input (or pattern "")) - (dirvish--setup-dired #'dirvish-fd-revert) - (dirvish-prop :fd-cache (dirvish--ht)) + dired-subdir-alist (list (cons dir (point-min-marker)))) + (dirvish--setup-dired + (lambda (&rest _) + (setq dired-subdir-alist (list (car (reverse dired-subdir-alist)))) + (let (buffer-read-only) + (buffer-disable-undo) + (delete-region (goto-char (dirvish-prop :content-begin)) (point-max))) + (buffer-enable-undo) + (dirvish-fd--start-proc))) + (dirvish-fd--argparser re switches) + (dirvish-prop :root root) (dirvish-prop :dv (dv-id dv)) - (dirvish-prop :gui (display-graphic-p)) - (dirvish-prop :fd-switches fd-switches) + (dirvish-prop :gui (display-graphic-p)) ; TODO: remove this (dirvish-prop :cus-header 'dirvish-fd-header) - (dirvish-prop :remote remote) + (dirvish-prop :remote (setq remote (file-remote-p dir))) (dirvish-prop :global-header t) (dirvish-prop :preview-dps (unless remote (dv-preview-dispatchers dv))) (dirvish-prop :attrs (dv-attributes dv)) (cl-loop for (k v) on dirvish--scopes by 'cddr do (dirvish-prop k (and (functionp v) (funcall v)))) - (dirvish-fd--argparser (split-string (or fd-switches ""))) (dirvish-save-dedication (switch-to-buffer buffer) (dirvish--build-layout dv)) - (setq proc (apply #'start-file-process "fd" buffer - `(,fd-program "--color=never" - ,@(or (split-string fd-switches) "") - ,(or pattern "") - "--exec-batch" ,ls-program - ,@(or (split-string ls-switches) "") - "--quoting-style=literal" "--directory"))) - (move-marker (process-mark proc) (point) buffer) - (set-process-filter proc #'dirvish-fd-proc-filter) - (set-process-sentinel proc #'dirvish-fd-proc-sentinel) - (set-process-query-on-exit-flag proc nil) - (process-put proc 'start (float-time)) - (setf (dv-index dv) (cons root buffer)) - (cl-pushnew (cons root buffer) (dv-roots dv) :test #'equal) - (cl-loop for (_ . b) in (dv-roots dv) - when (equal (with-current-buffer b (dirvish-prop :root)) root) - do (dirvish--kill-buffer b)) - (dirvish-prop :root root) - (rename-buffer bname)))) + (dirvish-fd--start-proc)))) -;;;###autoload -(defun dirvish-fd-ask (dir pattern) - "The same as `dirvish-fd' but ask initial DIR and PATTERN via prompt." - (interactive (list (and current-prefix-arg - (read-directory-name "Fd target directory: " nil "" t)) - (read-from-minibuffer "Pattern: "))) - (dirvish-fd dir pattern)) +(define-obsolete-function-alias 'dirvish-fd-ask #'dirvish-fd "Apr 4, 2025") (provide 'dirvish-fd) ;;; dirvish-fd.el ends here diff --git a/extensions/dirvish-narrow.el b/extensions/dirvish-narrow.el index 1bd50af655..b45ae1ee60 100644 --- a/extensions/dirvish-narrow.el +++ b/extensions/dirvish-narrow.el @@ -57,37 +57,18 @@ "Face for matches of components numbered 3 mod 4." :group 'dirvish) -(defun dirvish-narrow--compile-regex (string) - "Compile `completion-regexp-list' from STRING." - (if (fboundp 'orderless-compile) (cdr (orderless-compile string)) - (split-string string))) - -(defun dirvish-narrow--highlight (regexps ignore-case string) - "Destructively propertize STRING to highlight a match of each of the REGEXPS. -The search is case insensitive if IGNORE-CASE is non-nil." - (cl-loop with case-fold-search = ignore-case - with n = (length dirvish-narrow-match-faces) - for regexp in regexps and i from 0 - when (string-match regexp string) do - (cl-loop - for (x y) on (let ((m (match-data))) (or (cddr m) m)) by #'cddr - when x do (add-face-text-property - x y (aref dirvish-narrow-match-faces (mod i n)) - nil string))) - string) +(defface dirvish-narrow-split + '((t :inherit font-lock-negation-char-face)) + "Face used to highlight punctuation character." + :group 'dirvish) (defun dirvish-narrow--build-indices () "Update the Dirvish buffer based on the input of the minibuffer." - (declare-function dirvish-subtree--revert "dirvish-subtree") - (when (bound-and-true-p dirvish-subtree--overlays) - (dirvish-subtree--revert t)) (save-excursion (cl-loop - for (dir . beg) in dired-subdir-alist - if (and (equal dir (expand-file-name default-directory)) - (dirvish-prop :fd-arglist)) - do (puthash (md5 dir) (dirvish-prop :fd-cache) dirvish--dir-data) - else do (goto-char beg) + for (dir . beg) in dired-subdir-alist and idx from 0 + unless (and (eq idx 0) (dirvish-prop :fd-info)) + do (goto-char beg) (let ((end (dired-subdir-max)) (files (dirvish--ht))) (while (< (point) end) (when-let* ((f-beg (dired-move-to-filename)) @@ -100,64 +81,98 @@ The search is case insensitive if IGNORE-CASE is non-nil." (forward-line 1)) (puthash (md5 dir) files dirvish--dir-data))))) +(defun dirvish-narrow--compiler (s) + "Compile `completion-regexp-list' from string S." + (if (fboundp 'orderless-compile) (cdr (orderless-compile s)) (split-string s))) + ;; use a separate timer here, otherwise it would be overrided by the default one (defvar dirvish-narrow--delay-timer `(,(timer-create) ,(float-time) nil)) (defun dirvish-narrow-update-h () "Update the Dirvish buffer based on the input of the minibuffer." - (dirvish--run-with-delay - (minibuffer-contents-no-properties) dirvish-narrow--delay-timer - (lambda (action) - (with-current-buffer (window-buffer (minibuffer-selected-window)) - (save-excursion - (cl-loop with regs = (dirvish-narrow--compile-regex action) - for (dir . pos) in dired-subdir-alist and idx from 0 - do (dirvish-narrow--subdir dir pos regs idx))))))) - -(defun dirvish-narrow--subdir (dir pos regexs idx &optional all) - "Narrow subdir DIR at index IDX in POS with REGEXS." - (delete-region - (progn (goto-char pos) (forward-line (dirvish--subdir-offset)) (point)) - (- (dired-subdir-max) (if (eq idx 0) 0 1))) - (cl-loop with completion-regexp-list = regexs - with completion-ignore-case = - (cl-loop for re in (ensure-list regexs) - always (isearch-no-upper-case-p re t)) - with files = (gethash (md5 dir) dirvish--dir-data) - and fr-h = (+ (frame-height) 5) and count = 0 - with pred = (if all #'always (lambda (&rest _) (<= (cl-incf count) fr-h))) - for f in (all-completions "" files pred) - for l = (concat (gethash f files)) ; use copy, not reference - do (insert (if all l (dirvish-narrow--highlight ; lazy highlighting - regexs completion-ignore-case l))) - finally do (dirvish-prop :count count))) + (let* ((mc (minibuffer-contents-no-properties)) + (filter mc) async rel igc) + (save-match-data + (when-let* (((string-match "^#\\([^ #]*\\)\\(.*\\)" mc)) + (beg (minibuffer-prompt-end))) + (add-text-properties beg (1+ beg) '(rear-nonsticky t)) + (add-face-text-property beg (1+ beg) 'dirvish-narrow-split) + (setq async (match-string 1 mc) filter (match-string 2 mc)))) + (with-current-buffer (cdr (dv-index (dirvish-curr))) + (when (and async (dirvish-prop :fd-info)) + (dirvish-fd--argparser (mapcan (lambda (x) `(,(format "--and=%s" x))) + (split-string async "," t)) + (cddr (dirvish-prop :fd-info)))) + (setq rel (dirvish-narrow--compiler filter) + igc (cl-loop for re in (ensure-list rel) + always (isearch-no-upper-case-p re t))) + (dirvish-prop :narrow-info (list async rel igc))) + (dirvish--run-with-delay mc dirvish-narrow--delay-timer + (lambda (action) + (with-current-buffer (cdr (dv-index (dirvish-curr))) + (when (dirvish-prop :fd-info) (dirvish-fd--start-proc)) + (save-excursion + (cl-loop for (dir . pos) in dired-subdir-alist and idx from 0 + do (delete-region + (progn (goto-char pos) + (forward-line (dirvish--subdir-offset)) (point)) + (- (dired-subdir-max) (if (eq idx 0) 0 1))) + unless (and (eq idx 0) (dirvish-prop :fd-info)) + do (cl-loop with files = (gethash (md5 dir) dirvish--dir-data) + with completion-regexp-list = rel + with completion-ignore-case = igc + for f in (all-completions "" files) + do (insert (concat (gethash f files))))))) + (when (dv-curr-layout (dirvish-curr)) (force-mode-line-update t)))))) + +(dirvish-define-attribute narrow-match + "NARROW MATCH." + (cl-loop with (_ regexps case-fold-search) = (dirvish-prop :narrow-info) + with n = (length dirvish-narrow-match-faces) with ovs = nil + for regexp in regexps and i from 0 + when (string-match regexp f-str) do + (cl-loop + for (x y) on (let ((m (match-data))) (or (cddr m) m)) by #'cddr + when x do (let ((ov (make-overlay (+ f-beg x) (+ f-beg y))) + (face (aref dirvish-narrow-match-faces (mod i n)))) + (overlay-put ov 'face face) + (push ov ovs))) + finally return `(ovs . ,ovs))) ;;;###autoload (defun dirvish-narrow () "Narrow a Dirvish buffer to the files matching a regex." (interactive nil dired-mode) - (when (get-buffer-process (current-buffer)) - (user-error "Current buffer has unfinished jobs")) + (when (bound-and-true-p dirvish-subtree--overlays) + (declare-function dirvish-subtree--revert "dirvish-subtree") + (dirvish-subtree--revert t)) (require 'orderless nil t) (dirvish-narrow--build-indices) - (let ((dv (dirvish-prop :dv)) (restore (dirvish-prop :index)) - input buffer-read-only) - (font-lock-mode -1) (buffer-disable-undo) + (let ((dv (dirvish-prop :dv)) + (idx (dirvish-prop :index)) + (fd (dirvish-prop :fd-info)) + (attrs (mapcar #'car (dirvish-prop :attrs))) + buffer-read-only) + (when fd + (setq dired-subdir-alist (list (car (reverse dired-subdir-alist)))) + (delete-region (goto-char (dirvish-prop :content-begin)) (point-max))) + (dirvish-prop :attrs + (dirvish--attrs-expand (append '(narrow-match) attrs))) (minibuffer-with-setup-hook (lambda () (dirvish-prop :dv dv) (add-hook 'post-command-hook #'dirvish-narrow-update-h nil t)) (unwind-protect - (setq input (read-from-minibuffer "Focus on files: ")) - (save-excursion - (cl-loop with re = (dirvish-narrow--compile-regex (or input "")) - for (d . p) in dired-subdir-alist and i from 0 - do (dirvish-narrow--subdir d p re i (or input "")))) - (dirvish-prop :count nil) - (when restore (dired-goto-file restore)) + (read-from-minibuffer "Focus on files: " (if fd "#" "")) + (when idx (dired-goto-file idx)) + (dirvish-prop :attrs (dirvish--attrs-expand attrs)) + (when-let* (((not (eq (dv-type (dirvish-curr)) 'side))) + (query (caar (dirvish-prop :fd-info))) + (key (file-name-nondirectory + (directory-file-name default-directory)))) + (rename-buffer (concat key "π" query "π" (dv-id (dirvish-curr))))) (dirvish--run-with-delay 'reset) - (dirvish--run-with-delay 'reset dirvish-narrow--delay-timer) - (font-lock-mode 1) (buffer-enable-undo))))) + (dirvish--run-with-delay 'reset dirvish-narrow--delay-timer))))) (provide 'dirvish-narrow) ;;; dirvish-narrow.el ends here