aboutsummaryrefslogtreecommitdiff
path: root/.config/emacs/lisp/dired-hacks/dired-subtree.el
diff options
context:
space:
mode:
authorJack Jamison <jackqjamison@gmail.com>2026-07-05 01:19:30 -0400
committerJack Jamison <jackqjamison@gmail.com>2026-07-05 01:19:30 -0400
commitbdf9a71ab7baa2b1de9abcfd5df1a9107a55d141 (patch)
treec4524e6c41aa5107211103401dabfaefc81aa882 /.config/emacs/lisp/dired-hacks/dired-subtree.el
parentfe3984f541bd32bdfa418afb305b614176b55ca0 (diff)
add a bunch of emacs packages HELP
Diffstat (limited to '.config/emacs/lisp/dired-hacks/dired-subtree.el')
-rw-r--r--.config/emacs/lisp/dired-hacks/dired-subtree.el785
1 files changed, 785 insertions, 0 deletions
diff --git a/.config/emacs/lisp/dired-hacks/dired-subtree.el b/.config/emacs/lisp/dired-hacks/dired-subtree.el
new file mode 100644
index 0000000..d50ab96
--- /dev/null
+++ b/.config/emacs/lisp/dired-hacks/dired-subtree.el
@@ -0,0 +1,785 @@
+;;; dired-subtree.el --- Insert subdirectories in a tree-like fashion
+
+;; Copyright (C) 2014-2015 Matúš Goljer
+
+;; Author: Matúš Goljer <matus.goljer@gmail.com>
+;; Maintainer: Matúš Goljer <matus.goljer@gmail.com>
+;; Keywords: files
+;; Version: 0.0.1
+;; Created: 25th February 2014
+;; Package-Requires: ((dash "2.5.0") (dired-hacks-utils "0.0.1") (emacs "24.3"))
+;; URL: https://github.com/Fuco1/dired-hacks
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Introduction
+;; ------------
+
+;; The basic command to work with subdirectories in dired is `i',
+;; which inserts the subdirectory as a separate listing in the active
+;; dired buffer.
+
+;; This package defines function `dired-subtree-insert' which instead
+;; inserts the subdirectory directly below its line in the original
+;; listing, and indent the listing of subdirectory to resemble a
+;; tree-like structure (somewhat similar to tree(1) except the pretty
+;; graphics). The tree display is somewhat more intuitive than the
+;; default "flat" subdirectory manipulation provided by `i'.
+
+;; There are several presentation options and faces you can customize
+;; to change the way subtrees are displayed.
+
+;; You can further remove the unwanted lines from the subtree by using
+;; `k' command or some of the built-in "focusing" functions, such as
+;; `dired-subtree-only-*' (see list below).
+
+;; If you have the package `dired-filter', you can additionally filter
+;; the subtrees with global or local filters.
+
+;; A demo of basic functionality is available on youtube:
+;; https://www.youtube.com/watch?v=z26b8HKFsNE
+
+;; Interactive functions
+;; ---------------------
+
+;; Here's a list of available interactive functions. You can read
+;; more about each one by using the built-in documentation facilities
+;; of Emacs. It is adviced to place bindings for these into a
+;; convenient prefix key map, for example C-,
+
+;; * `dired-subtree-insert'
+;; * `dired-subtree-remove'
+;; * `dired-subtree-toggle'
+;; * `dired-subtree-cycle'
+;; * `dired-subtree-revert'
+;; * `dired-subtree-narrow'
+;; * `dired-subtree-up'
+;; * `dired-subtree-down'
+;; * `dired-subtree-next-sibling'
+;; * `dired-subtree-previous-sibling'
+;; * `dired-subtree-beginning'
+;; * `dired-subtree-end'
+;; * `dired-subtree-mark-subtree'
+;; * `dired-subtree-unmark-subtree'
+;; * `dired-subtree-only-this-file'
+;; * `dired-subtree-only-this-directory'
+
+;; If you have package `dired-filter', additional command
+;; `dired-subtree-apply-filter' is available.
+
+;; See https://github.com/Fuco1/dired-hacks for the entire collection.
+
+;;; Code:
+
+(require 'dired-hacks-utils)
+(require 'dash)
+(require 'cl-lib)
+
+(defgroup dired-subtree ()
+ "Insert subdirectories in a tree-like fashion."
+ :group 'dired-hacks
+ :prefix "dired-subtree-")
+
+(defcustom dired-subtree-line-prefix " "
+ "A prefix put into each nested subtree.
+
+The prefix is repeated \"depth\" times.
+
+Alternatively, it can be a function taking one argument---the
+depth---that creates the prefix."
+ :type '(choice string function)
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-line-prefix-face 'parents
+ "Specifies how the prefix is fontified."
+ :type '(radio
+ (const :tag "No face applied" nil)
+ (const :tag "Inherit from current subtree" subtree)
+ (const :tag "Inherit from all parents" parents))
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-use-backgrounds t
+ "When non-nil, add a background face to a subtree listing."
+ :type 'boolean
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-after-insert-hook ()
+ "Hook run at the end of `dired-subtree-insert'."
+ :type 'hook
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-after-remove-hook ()
+ "Hook run at the end of `dired-subtree-remove'."
+ :type 'hook
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-cycle-depth 3
+ "Default depth expanded by `dired-subtree-cycle'."
+ :type 'natnum
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-ignored-regexp
+ (concat "^" (regexp-opt vc-directory-exclusion-list) "$")
+ "Matching directories will not be expanded in `dired-subtree-cycle'."
+ :type 'regexp
+ :group 'dired-subtree)
+
+(defgroup dired-subtree-faces ()
+ "Faces used in `dired-subtree'."
+ :group 'dired-subtree)
+
+(defface dired-subtree-depth-1-face
+ '((t (:background "#252e30")))
+ "Background for depth 1 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-2-face
+ '((t (:background "#232a2b")))
+ "Background for depth 2 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-3-face
+ '((t (:background "#212627")))
+ "Background for depth 3 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-4-face
+ '((t (:background "#1e2223")))
+ "Background for depth 4 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-5-face
+ '((t (:background "#1c1d1e")))
+ "Background for depth 5 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-6-face
+ '((t (:background "#1a191a")))
+ "Background for depth 6 subtrees"
+ :group 'dired-subtree-faces)
+
+(defvar dired-subtree-overlays nil
+ "Subtree overlays in this buffer.")
+(make-variable-buffer-local 'dired-subtree-overlays)
+
+
+;;; Overlay manipulation
+;; Maybe we should abstract the overlay-foo into some subtree
+;; functions instead!!!
+
+(defun dired-subtree--remove-overlay (ov)
+ "Remove dired-subtree overlay OV."
+ (setq dired-subtree-overlays
+ (--remove (equal it ov) dired-subtree-overlays))
+ (delete-overlay ov))
+
+(defun dired-subtree--remove-overlays (ovs)
+ "Remove dired-subtree overlays OVS."
+ (mapc 'dired-subtree--remove-overlay ovs))
+
+(defun dired-subtree--cleanup-overlays ()
+ "Remove the nil values from `dired-subtree-overlays'."
+ (setq dired-subtree-overlays
+ (--remove (not (overlay-buffer it)) dired-subtree-overlays)))
+
+(defun dired-subtree--get-all-ovs ()
+ "Get all dired-subtree overlays in this buffer."
+ (--filter (overlay-get it 'dired-subtree-depth) (overlays-in (point-min) (point-max))))
+
+(defun dired-subtree--get-all-ovs-at-point (&optional p)
+ "Get all dired-subtree overlays at point P."
+ (setq p (or p (point)))
+ (--filter (overlay-get it 'dired-subtree-depth) (overlays-at (point))))
+
+(defun dired-subtree--get-ovs-in (&optional beg end)
+ "Get all dired-subtree overlays between BEG and END.
+
+BEG and END default to the region spanned by overlay at point."
+ (when (not beg)
+ (let ((ov (dired-subtree--get-ov)))
+ (setq beg (overlay-start ov))
+ (setq end (overlay-end ov))))
+ (--filter (and (overlay-get it 'dired-subtree-depth)
+ (>= (overlay-start it) beg)
+ (<= (overlay-end it) end))
+ (overlays-in (point-min) (point-max))))
+
+(defun dired-subtree--get-ov (&optional p)
+ "Get the parent subtree overlay at point."
+ (setq p (or p (point)))
+ (car (--sort (> (overlay-get it 'dired-subtree-depth)
+ (overlay-get other 'dired-subtree-depth))
+ (dired-subtree--get-all-ovs-at-point p))))
+
+(defun dired-subtree--get-depth (ov)
+ "Get subtree depth."
+ (or (and ov (overlay-get ov 'dired-subtree-depth)) 0))
+
+
+
+;;; helpers
+(defvar dired-subtree-preserve-properties '(dired-subtree-filter)
+ "Properties that should be preserved between read-ins.")
+
+(defun dired-subtree--after-readin (&optional subtrees)
+ "Insert the SUBTREES again after dired buffer has been reverted.
+
+If no SUBTREES are specified, use `dired-subtree-overlays'."
+ (-when-let (subtrees-to-process (or subtrees dired-subtree-overlays))
+ (let* ((ovs-by-depth (--sort (< (car it) (car other))
+ (--group-by (overlay-get it 'dired-subtree-depth)
+ subtrees-to-process)))
+ (sorted-ovs (--map (cons (car it)
+ (--map (-cons* it
+ (overlay-get it 'dired-subtree-name)
+ (-map (lambda (x) (cons x (overlay-get it x)))
+ dired-subtree-preserve-properties)) (cdr it)))
+ ovs-by-depth)))
+ ;; (depth (path1 ov1 (prop1 . value1) (prop2 . value2)) (path2 ...))
+ (--each sorted-ovs
+ (--each (cdr it)
+ (when (dired-utils-goto-line (cadr it))
+ (dired-subtree--remove-overlay (car it))
+ (dired-subtree-insert)
+ (let ((ov (dired-subtree--get-ov)))
+ (--each (cddr it)
+ (overlay-put ov (car it) (cdr it)))
+ (dired-subtree--filter-subtree ov))))))))
+
+(defun dired-subtree--after-insert ()
+ "After inserting the subtree, setup dired-details/dired-hide-details-mode."
+ (if (fboundp 'dired-insert-set-properties)
+ (let ((inhibit-read-only t)
+ (ov (dired-subtree--get-ov)))
+ (dired-insert-set-properties (overlay-start ov) (overlay-end ov)))
+ (when (featurep 'dired-details)
+ (dired-details-delete-overlays)
+ (dired-details-activate))))
+
+(add-hook 'dired-after-readin-hook 'dired-subtree--after-readin)
+
+(add-hook 'dired-subtree-after-insert-hook 'dired-subtree--after-insert)
+
+(defun dired-subtree--unmark ()
+ "Unmark a file without moving point."
+ (save-excursion (dired-unmark 1)))
+
+(defun dired-subtree--dired-line-is-directory-or-link-p ()
+ "Return non-nil if line under point is a directory or symlink."
+ ;; We've replaced `file-directory-p' with the regexp test to
+ ;; speed up filters over TRAMP. So long as dired/ls format
+ ;; doesn't change, we're good.
+ ;; 'd' for directories, 'l' for potential symlinks to directories.
+ (save-excursion (beginning-of-line) (looking-at "..[dl]")))
+
+(defun dired-subtree--is-expanded-p ()
+ "Return non-nil if directory under point is expanded."
+ (save-excursion
+ (when (dired-utils-get-filename)
+ (let ((depth (dired-subtree--get-depth (dired-subtree--get-ov))))
+ (dired-next-line 1)
+ (< depth (dired-subtree--get-depth (dired-subtree--get-ov)))))))
+
+(defmacro dired-subtree-with-subtree (&rest forms)
+ "Run FORMS on each file in this subtree."
+ (declare (debug (body)))
+ `(save-excursion
+ (dired-subtree-beginning)
+ ,@forms
+ (while (dired-subtree-next-sibling)
+ ,@forms)))
+
+
+;;;; Interactive
+
+;;;###autoload
+(defun dired-subtree-narrow ()
+ "Narrow the buffer to this subtree."
+ (interactive)
+ (-when-let (ov (dired-subtree--get-ov))
+ (narrow-to-region (overlay-start ov)
+ (overlay-end ov))))
+
+;;; Navigation
+
+;; make the arguments actually do something
+;;;###autoload
+(defun dired-subtree-up (&optional arg)
+ "Jump up one directory."
+ (interactive "p")
+ (-when-let (ov (dired-subtree--get-ov))
+ (goto-char (overlay-start ov))
+ (dired-previous-line 1)))
+
+;;;###autoload
+(defun dired-subtree-down (&optional arg)
+ "Jump down one directory."
+ (interactive "p")
+ (-when-let* ((p (point))
+ (ov (car (--sort
+ (< (overlay-start it)
+ (overlay-start other))
+ (--remove
+ (< (overlay-start it) p)
+ (dired-subtree--get-all-ovs))))))
+ (goto-char (overlay-start ov))
+ (dired-move-to-filename)))
+
+;;;###autoload
+(defun dired-subtree-next-sibling (&optional arg)
+ "Go to the next sibling."
+ (interactive "p")
+ (let ((current-ov (dired-subtree--get-ov)))
+ (dired-next-line 1)
+ (let ((new-ov (dired-subtree--get-ov)))
+ (cond
+ ((not (dired-utils-is-file-p))
+ nil)
+ ((< (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ (goto-char (overlay-end new-ov))
+ (dired-move-to-filename)
+ t)
+ ((> (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ ;; add option to either go to top or stay at the end
+ (dired-previous-line 1)
+ nil)
+ (t t)))))
+
+;;;###autoload
+(defun dired-subtree-previous-sibling (&optional arg)
+ "Go to the previous sibling."
+ (interactive "p")
+ (let ((current-ov (dired-subtree--get-ov)))
+ (dired-previous-line 1)
+ (let ((new-ov (dired-subtree--get-ov)))
+ (cond
+ ;; this will need better handlign if we have inserted
+ ;; subdirectories
+ ((not (dired-utils-is-file-p))
+ nil)
+ ((< (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ (goto-char (overlay-start new-ov))
+ (dired-previous-line 1)
+ t)
+ ((> (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ ;; add option to either go to top or stay at the end
+ (dired-next-line 1)
+ nil)
+ (t t)))))
+
+;;;###autoload
+(defun dired-subtree-beginning ()
+ "Go to the first file in this subtree."
+ (interactive)
+ (let ((ov (dired-subtree--get-ov)))
+ (if (not ov)
+ ;; do something when not in subtree
+ t
+ (goto-char (overlay-start ov))
+ (dired-move-to-filename))))
+
+;;;###autoload
+(defun dired-subtree-end ()
+ "Go to the first file in this subtree."
+ (interactive)
+ (let ((ov (dired-subtree--get-ov)))
+ (if (not ov)
+ ;; do something when not in subtree
+ t
+ (goto-char (overlay-end ov))
+ (dired-previous-line 1))))
+
+;;; Marking
+
+;;;###autoload
+(defun dired-subtree-mark-subtree (&optional all)
+ "Mark all files in this subtree.
+
+With prefix argument mark all the files in subdirectories
+recursively."
+ (interactive "P")
+ (save-excursion
+ (if all
+ (let ((beg (save-excursion
+ (dired-subtree-beginning)
+ (point)))
+ (end (save-excursion
+ (dired-subtree-end)
+ (point))))
+ (dired-mark-files-in-region
+ (progn (goto-char beg) (line-beginning-position))
+ (progn (goto-char end) (line-end-position))))
+ (dired-subtree-beginning)
+ (save-excursion (dired-mark 1))
+ (while (dired-subtree-next-sibling)
+ (save-excursion (dired-mark 1))))))
+
+;;;###autoload
+(defun dired-subtree-unmark-subtree (&optional all)
+ "Unmark all files in this subtree.
+
+With prefix argument unmark all the files in subdirectories
+recursively."
+ (interactive)
+ (let ((dired-marker-char ? ))
+ (dired-subtree-mark-subtree all)))
+
+;;; Insertion/deletion
+;;;###autoload
+(defun dired-subtree-revert ()
+ "Revert the subtree.
+
+This means reinserting the content of this subtree and all its
+children."
+ (interactive)
+ (let ((inhibit-read-only t)
+ (file-name (dired-utils-get-filename)))
+ (-when-let* ((ov (dired-subtree--get-ov))
+ (ovs (dired-subtree--get-ovs-in)))
+ (dired-subtree-up)
+ (delete-region (overlay-start ov) (overlay-end ov))
+ (dired-subtree--after-readin ovs)
+ (when file-name
+ (dired-utils-goto-line file-name)))))
+
+(defun dired-subtree--readin (dir-name)
+ "Read in the directory.
+
+Return a string suitable for insertion in `dired' buffer."
+ (with-temp-buffer
+ (insert-directory dir-name dired-listing-switches nil t)
+ (delete-char -1)
+ (goto-char (point-min))
+ (delete-region
+ (progn (beginning-of-line) (point))
+ (progn (forward-line
+ (if (save-excursion
+ (forward-line 1)
+ (end-of-line)
+ (looking-back "\\."))
+ 3 1)) (point)))
+ (insert " ")
+ (while (= (forward-line) 0)
+ (insert " "))
+ (delete-char -2)
+ (buffer-string)))
+
+;;;###autoload
+(defun dired-subtree-insert ()
+ "Insert subtree under this directory."
+ (interactive)
+ (when (and (dired-subtree--dired-line-is-directory-or-link-p)
+ (not (dired-subtree--is-expanded-p)))
+ (let* ((dir-name (dired-get-filename nil))
+ (listing (dired-subtree--readin (file-name-as-directory dir-name)))
+ beg end)
+ (read-only-mode -1)
+ (move-end-of-line 1)
+ ;; this is pretty ugly, I'm sure it can be done better
+ (save-excursion
+ (insert listing)
+ (setq end (+ (point) 2)))
+ (insert "\n")
+ (setq beg (point))
+ (let ((inhibit-read-only t))
+ (remove-text-properties (1- beg) beg '(dired-filename)))
+ (let* ((ov (make-overlay beg end))
+ (parent (dired-subtree--get-ov (1- beg)))
+ (depth (or (and parent (1+ (overlay-get parent 'dired-subtree-depth)))
+ 1))
+ (face (intern (format "dired-subtree-depth-%d-face" depth))))
+ (when dired-subtree-use-backgrounds
+ (overlay-put ov 'face face))
+ ;; refactor this to some function
+ (overlay-put ov 'line-prefix
+ (if (stringp dired-subtree-line-prefix)
+ (if (not dired-subtree-use-backgrounds)
+ (apply 'concat (-repeat depth dired-subtree-line-prefix))
+ (cond
+ ((eq nil dired-subtree-line-prefix-face)
+ (apply 'concat
+ (-repeat depth dired-subtree-line-prefix)))
+ ((eq 'subtree dired-subtree-line-prefix-face)
+ (concat
+ dired-subtree-line-prefix
+ (propertize
+ (apply 'concat
+ (-repeat (1- depth) dired-subtree-line-prefix))
+ 'face face)))
+ ((eq 'parents dired-subtree-line-prefix-face)
+ (concat
+ dired-subtree-line-prefix
+ (apply 'concat
+ (--map
+ (propertize dired-subtree-line-prefix
+ 'face
+ (intern (format "dired-subtree-depth-%d-face" it)))
+ (number-sequence 1 (1- depth))))))))
+ (funcall dired-subtree-line-prefix depth)))
+ (overlay-put ov 'dired-subtree-name dir-name)
+ (overlay-put ov 'dired-subtree-parent parent)
+ (overlay-put ov 'dired-subtree-depth depth)
+ (overlay-put ov 'evaporate t)
+ (push ov dired-subtree-overlays))
+ (goto-char beg)
+ (dired-move-to-filename)
+ (read-only-mode 1)
+ (when (bound-and-true-p dired-filter-mode) (dired-filter-mode 1))
+ (run-hooks 'dired-subtree-after-insert-hook))))
+
+;;;###autoload
+(defun dired-subtree-remove ()
+ "Remove subtree at point."
+ (interactive)
+ (-when-let* ((ov (dired-subtree--get-ov))
+ (ovs (dired-subtree--get-ovs-in
+ (overlay-start ov)
+ (overlay-end ov))))
+ (let ((inhibit-read-only t))
+ (dired-subtree-up)
+ (delete-region (overlay-start ov)
+ (overlay-end ov))
+ (dired-subtree--remove-overlays ovs)))
+ (run-hooks 'dired-subtree-after-remove-hook))
+
+;;;###autoload
+(defun dired-subtree-toggle ()
+ "Insert subtree at point or remove it if it was not present."
+ (interactive)
+ (if (dired-subtree--is-expanded-p)
+ (progn
+ (dired-next-line 1)
+ (dired-subtree-remove)
+ ;; #175 fixes the case of the first line in dired when the
+ ;; cursor jumps to the header in dired rather then to the
+ ;; first file in buffer
+ (when (bobp)
+ (dired-next-line 1)))
+ (save-excursion (dired-subtree-insert))))
+
+(defun dired-subtree--insert-recursive (depth max-depth)
+ "Insert full subtree at point."
+ (save-excursion
+ (let ((name (dired-get-filename nil t)))
+ (when (and name (file-directory-p name)
+ (<= depth (or max-depth depth))
+ (or (= 1 depth)
+ (not (string-match-p dired-subtree-ignored-regexp
+ (file-name-nondirectory name)))))
+ (if (dired-subtree--is-expanded-p)
+ (dired-next-line 1)
+ (dired-subtree-insert))
+ (dired-subtree-end)
+ (dired-subtree--insert-recursive (1+ depth) max-depth)
+ (while (dired-subtree-previous-sibling)
+ (dired-subtree--insert-recursive (1+ depth) max-depth))))))
+
+(defvar dired-subtree--cycle-previous nil
+ "Remember previous action for `dired-subtree-cycle'")
+
+;;;###autoload
+(defun dired-subtree-cycle (&optional max-depth)
+ "Org-mode like cycle visibility:
+
+1) Show subtree
+2) Show subtree recursively (if previous command was cycle)
+3) Remove subtree
+
+Numeric prefix will set max depth"
+ (interactive "P")
+ (save-excursion
+ (cond
+ ;; prefix - show subtrees up to max-depth
+ (max-depth
+ (when (dired-subtree--is-expanded-p)
+ (dired-next-line 1)
+ (dired-subtree-remove))
+ (dired-subtree--insert-recursive 1 (if (integerp max-depth) max-depth nil))
+ (setq dired-subtree--cycle-previous :full))
+ ;; if directory is not expanded, expand one level
+ ((not (dired-subtree--is-expanded-p))
+ (dired-subtree-insert)
+ (setq dired-subtree--cycle-previous :insert))
+ ;; hide if previous command was not cycle or tree was fully expanded
+ ((or (not (eq last-command 'dired-subtree-cycle))
+ (eq dired-subtree--cycle-previous :full))
+ (dired-next-line 1)
+ (dired-subtree-remove)
+ (setq dired-subtree--cycle-previous :remove))
+ (t
+ (dired-subtree--insert-recursive 1 dired-subtree-cycle-depth)
+ (setq dired-subtree--cycle-previous :full)))))
+
+(defun dired-subtree--filter-up (keep-dir kill-siblings)
+ (save-excursion
+ (let (ov)
+ (save-excursion
+ (while (dired-subtree-up))
+ (dired-next-line 1)
+ (dired-subtree-mark-subtree t))
+ (if keep-dir
+ (dired-subtree-unmark-subtree)
+ (dired-subtree--unmark))
+ (while (and (dired-subtree-up)
+ (> (dired-subtree--get-depth (dired-subtree--get-ov)) 0))
+ (if (not kill-siblings)
+ (dired-subtree--unmark)
+ (dired-subtree--unmark)
+ (let ((here (point)))
+ (dired-subtree-with-subtree
+ (when (and (dired-subtree--is-expanded-p)
+ (/= (point) here))
+ (dired-subtree--unmark)
+ (save-excursion
+ (dired-next-line 1)
+ (dired-subtree-unmark-subtree t)))))))
+ (dired-do-kill-lines)
+ (dired-subtree--cleanup-overlays))))
+
+;;;###autoload
+(defun dired-subtree-only-this-file (&optional arg)
+ "Remove all the siblings on the route from this file to the top-most directory.
+
+With ARG non-nil, do not remove expanded directories in parents."
+ (interactive "P")
+ (dired-subtree--filter-up nil arg))
+
+;;;###autoload
+(defun dired-subtree-only-this-directory (&optional arg)
+ "Remove all the siblings on the route from this directory to the top-most directory.
+
+With ARG non-nil, do not remove expanded directories in parents."
+ (interactive "P")
+ (dired-subtree--filter-up t arg))
+
+;;; filtering
+(defun dired-subtree--filter-update-bs (ov)
+ "Update the local filter list.
+
+This function assumes that `dired-filter-stack' is dynamically
+bound to relevant value."
+ (let* ((filt (dired-filter--describe-filters))
+ (before-str (if (equal filt "") nil (concat " Local filters: " filt "\n"))))
+ (overlay-put ov 'before-string before-str)))
+
+(defun dired-subtree--filter-subtree (ov)
+ "Run the filter for this subtree.
+
+It is only safe to call this from readin.
+
+This depends on `dired-filter' package."
+ (when (featurep 'dired-filter)
+ (let ((dired-filter-stack (overlay-get ov 'dired-subtree-filter)))
+ (save-restriction
+ (widen)
+ (dired-subtree-narrow)
+ (dired-filter--expunge)
+ (dired-subtree--filter-update-bs ov)))))
+
+;;;###autoload
+(defun dired-subtree-apply-filter ()
+ "Push a local filter for this subtree.
+
+This depends on `dired-filter' package.
+
+It works exactly the same as global dired filters, only
+restricted to a subtree. The global filter is also applied to
+the subtree. The filter action is read from `dired-filter-map'."
+ (interactive)
+ (when (featurep 'dired-filter)
+ (-when-let (ov (dired-subtree--get-ov))
+ (let ((dired-filter-stack (overlay-get ov 'dired-subtree-filter))
+ (glob (current-global-map))
+ (loc (current-local-map))
+ cmd)
+ (cl-flet ((dired-filter--update
+ ()
+ (save-restriction
+ (overlay-put ov 'dired-subtree-filter dired-filter-stack)
+ (widen)
+ (dired-subtree-revert)
+ (dired-subtree--filter-update-bs ov))))
+ (unwind-protect
+ (progn
+ (use-global-map dired-filter-map)
+ (use-local-map nil)
+ (setq cmd (key-binding (read-key-sequence "Choose filter action: "))))
+ (use-global-map glob)
+ (use-local-map loc))
+ (let ((p (point))
+ (beg (overlay-start ov))
+ (current-file (dired-utils-get-filename)))
+ (unwind-protect
+ (call-interactively cmd)
+ (unless (dired-utils-goto-line current-file)
+ (goto-char beg)
+ (forward-line)
+ (goto-char (min p (1- (overlay-end (dired-subtree--get-ov)))))
+ (dired-move-to-filename)))))))))
+
+
+;;; Here we redefine a couple of functions from dired.el to make them
+;;; subtree-aware
+
+;; If the point is in a subtree, we need to provide a proper
+;; directory, not the one that would come from `dired-subdir-alist'.
+(defun dired-current-directory (&optional localp)
+ "Return the name of the subdirectory to which this line belongs.
+This returns a string with trailing slash, like `default-directory'.
+Optional argument means return a file name relative to `default-directory'."
+ (let ((here (point))
+ (alist (or dired-subdir-alist
+ ;; probably because called in a non-dired buffer
+ (error "No subdir-alist in %s" (current-buffer))))
+ elt dir)
+ (while alist
+ (setq elt (car alist)
+ dir (car elt)
+ ;; use `<=' (not `<') as subdir line is part of subdir
+ alist (if (<= (dired-get-subdir-min elt) here)
+ nil ; found
+ (cdr alist))))
+ ;; dired-subdir: modify dir here if we are in a "subtree" view
+ (-when-let (parent (dired-subtree--get-ov))
+ (setq dir (concat (overlay-get parent 'dired-subtree-name) "/")))
+ ;; end
+ (if localp
+ (dired-make-relative dir default-directory)
+ dir)))
+
+;; Since the tree-inserted directory is not in the dired-subdir-alist,
+;; we need to guard against nil.
+(defun dired-get-subdir ()
+ "Return the subdir name on this line, or nil if not on a headerline."
+ ;; Look up in the alist whether this is a headerline.
+ (save-excursion
+ (let ((cur-dir (dired-current-directory)))
+ (beginning-of-line) ; alist stores b-o-l positions
+ (and (zerop (- (point)
+ (or (dired-get-subdir-min
+ (assoc cur-dir
+ dired-subdir-alist))
+ 0))) ;; dired-subtree: return zero if current
+ ;; dir is not in `dired-subdir-alist'.
+ cur-dir))))
+
+(provide 'dired-subtree)
+
+;;; dired-subtree.el ends here