1196 lines
44 KiB
EmacsLisp
1196 lines
44 KiB
EmacsLisp
|
;;; dired-sidebar.el --- Tree browser leveraging dired -*- lexical-binding: t -*-
|
||
|
|
||
|
;; Copyright (C) 2017 James Nguyen
|
||
|
|
||
|
;; Author: James Nguyen <james@jojojames.com>
|
||
|
;; Maintainer: James Nguyen <james@jojojames.com>
|
||
|
;; URL: https://github.com/jojojames/dired-sidebar
|
||
|
;; Version: 0.0.1
|
||
|
;; Package-Requires: ((emacs "25.1") (dired-subtree "0.0.1"))
|
||
|
;; Keywords: dired, files, tools
|
||
|
;; HomePage: https://github.com/jojojames/dired-sidebar
|
||
|
|
||
|
;; 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:
|
||
|
;; This package provides a tree browser similar to `neotree' or `treemacs'
|
||
|
;; but leverages `dired' to do the job of display.
|
||
|
|
||
|
;; (use-package dired-sidebar
|
||
|
;; :bind (("C-x C-n" . dired-sidebar-toggle-sidebar))
|
||
|
;; :ensure nil
|
||
|
;; :commands (dired-sidebar-toggle-sidebar))
|
||
|
|
||
|
;;; Code:
|
||
|
|
||
|
(require 'dired)
|
||
|
(require 'dired-subtree)
|
||
|
(require 'face-remap)
|
||
|
(eval-when-compile (require 'subr-x)) ; `if-let*' and `when-let*'
|
||
|
|
||
|
;; Compatibility
|
||
|
|
||
|
(eval-and-compile
|
||
|
(with-no-warnings
|
||
|
(if (version< emacs-version "26")
|
||
|
(progn
|
||
|
(defalias 'dired-sidebar-if-let* #'if-let)
|
||
|
(defalias 'dired-sidebar-when-let* #'when-let)
|
||
|
(function-put #'dired-sidebar-if-let* 'lisp-indent-function 2)
|
||
|
(function-put #'dired-sidebar-when-let* 'lisp-indent-function 1))
|
||
|
(defalias 'dired-sidebar-if-let* #'if-let*)
|
||
|
(defalias 'dired-sidebar-when-let* #'when-let*))))
|
||
|
|
||
|
;; Customizations
|
||
|
|
||
|
(defgroup dired-sidebar nil
|
||
|
"A major mode leveraging `dired-mode' to display a filesystem in a tree
|
||
|
layout."
|
||
|
:group 'files)
|
||
|
|
||
|
(defcustom dired-sidebar-use-custom-font nil
|
||
|
"Show `dired-sidebar' with custom font.
|
||
|
|
||
|
This face can be customized using `dired-sidebar-face'."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-face nil
|
||
|
"Face used by `dired-sidebar' for custom font.
|
||
|
|
||
|
This only takes effect if `dired-sidebar-use-custom-font' is true."
|
||
|
:type 'list
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-use-custom-modeline t
|
||
|
"Show `dired-sidebar' with custom modeline.
|
||
|
|
||
|
This uses format specified by `dired-sidebar-mode-line-format'."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-mode-line-format
|
||
|
'("%e" mode-line-front-space
|
||
|
mode-line-buffer-identification
|
||
|
" " mode-line-end-spaces)
|
||
|
"Mode line format for `dired-sidebar'."
|
||
|
:type 'list
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-theme 'icons
|
||
|
"*The tree style to display.
|
||
|
`ascii' is the simplest style, it will use +/- to display the fold state,
|
||
|
it is suitable for terminal.
|
||
|
`icons' use `all-the-icons'.
|
||
|
`nerd' use the nerdtree indentation mode and arrow.
|
||
|
`none' use no theme.
|
||
|
`vscode' use `vscode' icons.
|
||
|
|
||
|
This only takes effect if on a local connection. (e.g. Not Tramp)"
|
||
|
:group 'dired-sidebar
|
||
|
:type '(choice (const ascii)
|
||
|
(const icons)
|
||
|
(const nerd)
|
||
|
(const none)
|
||
|
(const vscode)))
|
||
|
|
||
|
(defcustom dired-sidebar-width 35
|
||
|
"Width of the `dired-sidebar' buffer."
|
||
|
:type 'integer
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-refresh-on-projectile-switch t
|
||
|
"Refresh sidebar when `projectile' changes projects."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-should-follow-file nil
|
||
|
"Refresh sidebar to match current file."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-skip-subtree-parent t
|
||
|
"Whether to skip subtree parent directory when jumping up."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-pop-to-sidebar-on-toggle-open t
|
||
|
"Whether to jump to sidebar upon toggling open.
|
||
|
|
||
|
This is used in conjunction with `dired-sidebar-toggle-sidebar'."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-follow-file-at-point-on-toggle-open t
|
||
|
"Whether to recursively cycle the subtree and put point on file.
|
||
|
|
||
|
Similar to `dired-jump'. This moves point inside sidebar buffer
|
||
|
to where current-buffer-file is \(if it exists\) but does not necessarily
|
||
|
select the sidebar window."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
|
||
|
|
||
|
(defcustom dired-sidebar-use-magit-integration t
|
||
|
"Whether to integrate with `magit-mode'.
|
||
|
|
||
|
When true:
|
||
|
|
||
|
When finding file to point at for
|
||
|
`dired-sidebar-follow-file-at-point-on-toggle-open', use file at point
|
||
|
in `magit' buffer.
|
||
|
|
||
|
When finding root directory for sidebar, use directory specified by `magit'."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-use-term-integration nil
|
||
|
"Whether to integrate with `term-mode'.
|
||
|
|
||
|
When true:
|
||
|
|
||
|
When finding root directory for sidebar, use PWD of `term-mode'. This is turned
|
||
|
off by default due to the experimental nature of getting the PWD from the
|
||
|
terminal.
|
||
|
|
||
|
Look at `dired-sidebar-term-get-pwd' for implementation."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-use-wdired-integration t
|
||
|
"Whether to integrate with `wdired'."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-cycle-subtree-on-click t
|
||
|
"Whether to cycle subtree on click."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-delay-auto-revert-updates t
|
||
|
"Whether to delay automatically reverting buffer.
|
||
|
|
||
|
When true, only allow function `auto-revert-mode' to update every
|
||
|
`dird-sidebar-stale-buffer-time-idle-delay' seconds."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-stale-buffer-time-idle-delay 1.5
|
||
|
"The time in idle seconds to wait before checking if buffer is stale."
|
||
|
:type 'number
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-follow-file-idle-delay 2
|
||
|
"The time in idle seconds to wait before checking if sidebar should
|
||
|
follow file."
|
||
|
:type 'number
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-tui-update-delay 0.02
|
||
|
"The time in idle seconds to wait before updating tui interface.
|
||
|
|
||
|
This only takes effect if `all-the-icons-dired' is disabled."
|
||
|
:type 'number
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-refresh-on-special-commands t
|
||
|
"Whether or not to trigger auto-revert after certain functions.
|
||
|
|
||
|
Warning: This is implemented by advising specific dired functions."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-disable-dired-collapse t
|
||
|
"Whether or not to disable `dired-collapse' if it's enabled."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-special-refresh-commands
|
||
|
'(dired-do-delete
|
||
|
dired-do-rename
|
||
|
dired-do-copy
|
||
|
dired-do-flagged-delete
|
||
|
dired-create-directory
|
||
|
(delete-file . 5)
|
||
|
(save-buffer . 5)
|
||
|
magit-format-patch)
|
||
|
"A list of commands that will trigger a refresh of the sidebar.
|
||
|
|
||
|
The command can be an alist with the CDR of the alist being the amount of time
|
||
|
to wait to refresh the sidebar after the CAR of the alist is called.
|
||
|
|
||
|
Set this to nil or set `dired-sidebar-refresh-on-special-commands' to nil
|
||
|
to disable automatic refresh when a special command is triggered."
|
||
|
:type 'list
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-toggle-hidden-commands
|
||
|
'(balance-windows)
|
||
|
"A list of commands that won't work when `dired-sidebar' is visible.
|
||
|
|
||
|
When the command is triggered, `dired-sidebar' will hide temporarily until
|
||
|
command is completed.
|
||
|
|
||
|
This functionality is implemented using advice.
|
||
|
|
||
|
Set this to nil to disable this advice."
|
||
|
:type 'list
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-alternate-select-window-function
|
||
|
#'dired-sidebar-default-alternate-select-window
|
||
|
"Function to call when using alternative window selection.
|
||
|
|
||
|
Alternative window selection is used when `dired-sidebar-find-file' is called
|
||
|
with a prefix arg or when `dired-sidebar-find-file-alt' is called."
|
||
|
:type 'function
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-recenter-cursor-on-follow-file t
|
||
|
"Whether or not to center cursor when pointing at file."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-recenter-cursor-on-tui-update nil
|
||
|
"Whether or not to center cursor when updating tui interface."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-display-autorevert-messages nil
|
||
|
"Whether or not to display `autorevert' messages."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-open-file-in-most-recently-used-window t
|
||
|
"Whether or not to open files in most recently used window."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-subtree-line-prefix dired-subtree-line-prefix
|
||
|
"The line prefix to use when subtree is cycled."
|
||
|
:type 'string
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-display-alist '((side . left) (slot . -1))
|
||
|
"Alist used in `display-buffer-in-side-window'.
|
||
|
|
||
|
e.g. (display-buffer-in-side-window buffer '((side . left) (slot . -1)))"
|
||
|
:type 'alist
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-close-sidebar-on-file-open nil
|
||
|
"Whether or not to close sidebar when `dired-sidebar-find-file' is called.
|
||
|
|
||
|
This behavior only triggers if `dired-sidebar-find-file' is triggered on
|
||
|
a file."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-icon-scale .18
|
||
|
"The scale of icons \(currently only applies to vscode theme.\)."
|
||
|
:type 'number
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-no-delete-other-windows nil
|
||
|
"Whether or not to add `no-delete-other-window' parameter to window.
|
||
|
|
||
|
If this is true, when calling `delete-other-windows', `dired-sidebar' window
|
||
|
will continue showing.
|
||
|
|
||
|
For more information, look up `delete-other-windows'."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
(defcustom dired-sidebar-one-instance-p nil
|
||
|
"Only show one buffer instance for dired-sidebar for each frame."
|
||
|
:type 'boolean
|
||
|
:group 'dired-sidebar)
|
||
|
|
||
|
;; Internal
|
||
|
|
||
|
(defvar dired-sidebar-basedir (file-name-directory load-file-name)
|
||
|
"Store the directory dired-sidebar.el was loaded from.")
|
||
|
|
||
|
(defvar dired-sidebar-icons-dir (format "%sicons/" dired-sidebar-basedir)
|
||
|
"Store the icons directory of `dired-sidebar'.")
|
||
|
|
||
|
(defvar dired-sidebar-alist '()
|
||
|
"An alist that maps from frame to currently opened `dired-sidebar' buffer.")
|
||
|
|
||
|
(defvar-local dired-sidebar-stale-buffer-timer nil
|
||
|
"Timer used for setting `dired-sidebar-check-for-stale-buffer-p'.
|
||
|
|
||
|
This is buffer local.")
|
||
|
|
||
|
(defvar-local dired-sidebar-follow-file-timer nil
|
||
|
"Timer used when `dired-sidebar-should-follow-file' is true.")
|
||
|
|
||
|
(defvar-local dired-sidebar-check-for-stale-buffer-p nil
|
||
|
"Whether to check if buffer is stale.
|
||
|
|
||
|
When this is true `dired-sidebar-buffer-stale-p'
|
||
|
will check if buffer is stale through `auto-revert-mode'.")
|
||
|
|
||
|
;; Mode
|
||
|
|
||
|
(defmacro dired-sidebar-with-no-dedication (&rest body)
|
||
|
"Run BODY after undedicating window."
|
||
|
(declare (debug (&rest form)))
|
||
|
`(progn
|
||
|
(let ((window (get-buffer-window (current-buffer))))
|
||
|
(set-window-dedicated-p window nil)
|
||
|
,@body
|
||
|
(set-window-dedicated-p window t))))
|
||
|
|
||
|
(defvar dired-sidebar-mode-map
|
||
|
(let ((map (make-sparse-keymap)))
|
||
|
(define-key map (kbd "TAB") 'dired-sidebar-subtree-toggle)
|
||
|
(define-key map [tab] 'dired-sidebar-subtree-toggle)
|
||
|
(define-key map (kbd "C-m") 'dired-sidebar-find-file)
|
||
|
(define-key map (kbd "RET") 'dired-sidebar-find-file)
|
||
|
(define-key map (kbd "<return>") 'dired-sidebar-find-file)
|
||
|
(define-key map "^" 'dired-sidebar-up-directory)
|
||
|
(define-key map "-" 'dired-sidebar-up-directory)
|
||
|
(define-key map (kbd "C-o") 'dired-sidebar-find-file-alt)
|
||
|
(define-key map [mouse-2] 'dired-sidebar-mouse-subtree-cycle-or-find-file)
|
||
|
map)
|
||
|
"Keymap used for symbol `dired-sidebar-mode'.")
|
||
|
|
||
|
(define-derived-mode dired-sidebar-mode dired-mode
|
||
|
"Dired-sidebar"
|
||
|
"A major mode that puts `dired' in a sidebar."
|
||
|
:group 'dired-sidebar
|
||
|
|
||
|
;; Hack for https://github.com/jojojames/dired-sidebar/issues/18.
|
||
|
;; Would be open to a better fix...
|
||
|
;; `dired-remember-hidden' in Emacs 25 (terminal?) seems to throw
|
||
|
;; an error upon calling `goto-char'.
|
||
|
(when (<= emacs-major-version 25)
|
||
|
(defun dired-sidebar-remember-hidden-hack (f &rest args)
|
||
|
"Return nil for `dired-remember-hidden'.
|
||
|
|
||
|
Works around marker pointing to wrong buffer in Emacs 25."
|
||
|
(if (eq major-mode 'dired-sidebar-mode)
|
||
|
nil
|
||
|
(apply f args)))
|
||
|
(advice-remove 'dired-remember-hidden 'dired-sidebar-remember-hidden-hack)
|
||
|
(advice-add 'dired-remember-hidden :around 'dired-sidebar-remember-hidden-hack))
|
||
|
|
||
|
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32392
|
||
|
(when dired-sidebar-use-wdired-integration
|
||
|
(advice-remove 'wdired-change-to-dired-mode
|
||
|
'dired-sidebar-wdired-change-to-dired-mode-advice)
|
||
|
(advice-remove 'wdired-change-to-wdired-mode
|
||
|
'dired-sidebar-wdired-change-to-wdired-mode-advice)
|
||
|
|
||
|
(advice-add 'wdired-change-to-dired-mode
|
||
|
:around 'dired-sidebar-wdired-change-to-dired-mode-advice)
|
||
|
(advice-add 'wdired-change-to-wdired-mode
|
||
|
:around 'dired-sidebar-wdired-change-to-wdired-mode-advice))
|
||
|
|
||
|
(setq window-size-fixed 'width)
|
||
|
|
||
|
;; Match backgrounds.
|
||
|
(setq-local dired-subtree-use-backgrounds nil)
|
||
|
|
||
|
;; `dired-subtree''s line prefix is determined by `dired-sidebar'.
|
||
|
(setq-local dired-subtree-line-prefix dired-sidebar-subtree-line-prefix)
|
||
|
|
||
|
;; https://github.com/jojojames/dired-sidebar/issues/7
|
||
|
;; Symlinks are displayed incorrectly when these three things happen.
|
||
|
;; 1. `dired-hide-details-mode' is on.
|
||
|
;; 2. `dired-subtree' toggles a symlink folder via `dired-subtree-toggle'.
|
||
|
;; 3. `dired-hide-details-hide-symlink-targets' is set to true.
|
||
|
;; Since we use both 1 & 2, disable 3 to avoid the issue.
|
||
|
;; This needs to be set to nil before `dired-hide-details-mode' is called.
|
||
|
(setq-local dired-hide-details-hide-symlink-targets nil)
|
||
|
|
||
|
;; Use `dired-sidebar-revert' instead that wraps `dired-revert'.
|
||
|
(setq-local revert-buffer-function 'dired-sidebar-revert)
|
||
|
|
||
|
;; We don't want extra details in the sidebar.
|
||
|
(dired-hide-details-mode)
|
||
|
|
||
|
(when (and dired-sidebar-disable-dired-collapse
|
||
|
(fboundp 'dired-collapse-mode))
|
||
|
(add-hook 'dired-mode-hook
|
||
|
(lambda ()
|
||
|
(when (bound-and-true-p dired-collapse-mode)
|
||
|
(dired-collapse-mode -1)))
|
||
|
:append :local))
|
||
|
|
||
|
(when (and
|
||
|
(not dired-sidebar-display-autorevert-messages)
|
||
|
(boundp 'auto-revert-verbose))
|
||
|
(setq-local auto-revert-verbose nil))
|
||
|
|
||
|
(when dired-sidebar-delay-auto-revert-updates
|
||
|
(setq-local buffer-stale-function #'dired-sidebar-buffer-stale-p)
|
||
|
(let ((current-buffer (current-buffer)))
|
||
|
(setq dired-sidebar-stale-buffer-timer
|
||
|
(run-with-idle-timer
|
||
|
dired-sidebar-stale-buffer-time-idle-delay
|
||
|
t (lambda ()
|
||
|
;; Only do a check if `dired-sidebar' buffer is in the foreground.
|
||
|
(when (get-buffer-window current-buffer)
|
||
|
(with-current-buffer current-buffer
|
||
|
(setq dired-sidebar-check-for-stale-buffer-p t))))))
|
||
|
|
||
|
(add-hook 'kill-buffer-hook
|
||
|
(lambda ()
|
||
|
(when (timerp dired-sidebar-stale-buffer-timer)
|
||
|
(cancel-timer dired-sidebar-stale-buffer-timer)))
|
||
|
nil t)))
|
||
|
|
||
|
(when dired-sidebar-refresh-on-special-commands
|
||
|
(mapc
|
||
|
(lambda (x)
|
||
|
(if (consp x)
|
||
|
(let ((command (car x))
|
||
|
(delay (cdr x)))
|
||
|
(advice-add
|
||
|
command
|
||
|
:after
|
||
|
(defalias (intern (format "dired-sidebar-refresh-after-%S" command))
|
||
|
(function
|
||
|
(lambda (&rest _)
|
||
|
(let ((timer-symbol
|
||
|
(intern
|
||
|
(format
|
||
|
"dired-sidebar-refresh-%S-timer" command))))
|
||
|
(when (and (boundp timer-symbol)
|
||
|
(timerp (symbol-value timer-symbol)))
|
||
|
(cancel-timer (symbol-value timer-symbol)))
|
||
|
(setf
|
||
|
(symbol-value timer-symbol)
|
||
|
(run-with-idle-timer
|
||
|
delay
|
||
|
nil
|
||
|
#'dired-sidebar-refresh-buffer))))))))
|
||
|
(advice-add x :after #'dired-sidebar-refresh-buffer)))
|
||
|
dired-sidebar-special-refresh-commands))
|
||
|
|
||
|
(when dired-sidebar-toggle-hidden-commands
|
||
|
(mapc
|
||
|
(lambda (x)
|
||
|
(advice-add x :around #'dired-sidebar-advice-hide-temporarily))
|
||
|
dired-sidebar-toggle-hidden-commands))
|
||
|
|
||
|
(when dired-sidebar-use-custom-font
|
||
|
(dired-sidebar-set-font))
|
||
|
|
||
|
(when dired-sidebar-use-custom-modeline
|
||
|
(dired-sidebar-set-mode-line))
|
||
|
|
||
|
(when dired-sidebar-refresh-on-projectile-switch
|
||
|
(add-hook 'projectile-after-switch-project-hook
|
||
|
#'dired-sidebar-follow-file))
|
||
|
|
||
|
(when dired-sidebar-should-follow-file
|
||
|
(setq dired-sidebar-follow-file-timer
|
||
|
(run-with-idle-timer
|
||
|
dired-sidebar-follow-file-idle-delay
|
||
|
t #'dired-sidebar-follow-file)))
|
||
|
|
||
|
;; This comment is taken from `dired-readin'.
|
||
|
;; Begin --- Copied comment from dired.el.
|
||
|
;; Must first make alist buffer local and set it to nil because
|
||
|
;; dired-build-subdir-alist will call dired-clear-alist first
|
||
|
;; End --- Copied comment from dired.el.
|
||
|
(setq-local dired-subdir-alist nil)
|
||
|
(dired-build-subdir-alist)
|
||
|
|
||
|
(dired-unadvertise (dired-current-directory))
|
||
|
(dired-sidebar-update-buffer-name)
|
||
|
(dired-sidebar-update-state (current-buffer))
|
||
|
|
||
|
;; Move setting theme until the end after `dired-sidebar' has set up
|
||
|
;; its directory structure.
|
||
|
;; https://github.com/jojojames/dired-sidebar/issues/29
|
||
|
(unless (file-remote-p default-directory)
|
||
|
(cond
|
||
|
((dired-sidebar-using-tui-p)
|
||
|
(dired-sidebar-setup-tui))
|
||
|
((and (eq dired-sidebar-theme 'icons)
|
||
|
(display-graphic-p)
|
||
|
(or
|
||
|
(fboundp 'all-the-icons-dired-mode)
|
||
|
(autoloadp (symbol-function 'all-the-icons-dired-mode))))
|
||
|
(with-no-warnings
|
||
|
(all-the-icons-dired-mode)))
|
||
|
(:default :no-theme))))
|
||
|
|
||
|
;; User Interface
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun dired-sidebar-toggle-sidebar (&optional dir)
|
||
|
"Toggle the project explorer window.
|
||
|
Optional argument DIR Use DIR as sidebar root if available.
|
||
|
|
||
|
With universal argument, use current directory."
|
||
|
(interactive)
|
||
|
(if (dired-sidebar-showing-sidebar-p)
|
||
|
(dired-sidebar-hide-sidebar)
|
||
|
(let* ((old-buffer (dired-sidebar-buffer (selected-frame)))
|
||
|
(file-to-show (dired-sidebar-get-file-to-show))
|
||
|
(dir-to-show (or dir
|
||
|
(when current-prefix-arg
|
||
|
(expand-file-name default-directory))
|
||
|
(dired-sidebar-get-dir-to-show)))
|
||
|
(sidebar-buffer (dired-sidebar-get-or-create-buffer dir-to-show)))
|
||
|
(dired-sidebar-show-sidebar sidebar-buffer)
|
||
|
(when (and dired-sidebar-one-instance-p old-buffer (not (eq sidebar-buffer old-buffer)))
|
||
|
(kill-buffer old-buffer))
|
||
|
(if (and dired-sidebar-follow-file-at-point-on-toggle-open
|
||
|
file-to-show)
|
||
|
(if dired-sidebar-pop-to-sidebar-on-toggle-open
|
||
|
(dired-sidebar-point-at-file file-to-show dir-to-show)
|
||
|
(with-selected-window (selected-window)
|
||
|
(dired-sidebar-point-at-file file-to-show dir-to-show)))
|
||
|
(when dired-sidebar-pop-to-sidebar-on-toggle-open
|
||
|
(pop-to-buffer (dired-sidebar-buffer)))))))
|
||
|
|
||
|
(defun dired-sidebar-point-at-file (name root)
|
||
|
"Try to point at NAME from sidebar.
|
||
|
|
||
|
Keep `dired' pointed at ROOT while cycling directories until
|
||
|
NAME is found in ROOT path.
|
||
|
|
||
|
This is dependent on `dired-subtree-cycle'."
|
||
|
(let ((sidebar (dired-sidebar-buffer)))
|
||
|
(pop-to-buffer sidebar)
|
||
|
(when (and name
|
||
|
;; Checking for a private method. *shrug*
|
||
|
(fboundp 'dired-subtree--is-expanded-p))
|
||
|
(pop-to-buffer sidebar)
|
||
|
(goto-char 0)
|
||
|
(let* ((path root)
|
||
|
;; Imagine root is /root/var/ and name is
|
||
|
;; /root/var/a/b/c.
|
||
|
;; This will return a list of '\("a" "b" "c"\).
|
||
|
(dirs (when (cadr (split-string name root))
|
||
|
(split-string (cadr (split-string name root)) "/"))))
|
||
|
(dolist (dir dirs)
|
||
|
(let ((path-regex (concat "^.*[[:space:]]" (regexp-quote dir))))
|
||
|
(setq path (concat path dir))
|
||
|
(if (file-regular-p path)
|
||
|
;; Try to use `dired-goto-file' to go to the correct
|
||
|
;; file. If that fails, just search for the text.
|
||
|
(let ((default-directory (file-name-directory path)))
|
||
|
(unless (dired-goto-file path)
|
||
|
(condition-case nil
|
||
|
;; It's hard to get this right so just using a
|
||
|
;; heuristic will get 90% of the way there.
|
||
|
;; Making sure there's a space in front of the name
|
||
|
;; skips matches that contains the name as a
|
||
|
;; substring which is probably good enough...
|
||
|
(re-search-forward path-regex)
|
||
|
;; Sometimes `dired' gets out of sync with the file.
|
||
|
;; Refresh the buffer and try the search again.
|
||
|
;; One way to reproduce this:
|
||
|
;; 1. Open file A as buffer B.
|
||
|
;; 2. Delete file A in `dired'.
|
||
|
;; 3. Hide `dired-sidebar'.
|
||
|
;; 4. Save buffer B.
|
||
|
;; 5. Re-open `dired-sidebar'.
|
||
|
(error
|
||
|
(revert-buffer)
|
||
|
(re-search-forward path-regex nil :no-error)))))
|
||
|
(re-search-forward path-regex)
|
||
|
;; Check if subtree has already been expanded.
|
||
|
;; Basically, we're using `dired-subtree-cycle' more
|
||
|
;; like dired-subtree-expand.
|
||
|
(when (not (dired-subtree--is-expanded-p))
|
||
|
;; This will probably throw an error when trying to expand
|
||
|
;; directories that have been collapsed by `dired-collapse'.
|
||
|
(dired-subtree-cycle))
|
||
|
(setq path (concat path "/"))))))
|
||
|
(when dired-sidebar-recenter-cursor-on-follow-file
|
||
|
(recenter nil))
|
||
|
(dired-sidebar-redisplay-icons))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun dired-sidebar-toggle-with-current-directory ()
|
||
|
"Like `dired-sidebar-toggle-sidebar' but use current-directory."
|
||
|
(interactive)
|
||
|
(let ((current-prefix-arg '(4))) ; C-u
|
||
|
(call-interactively #'dired-sidebar-toggle-sidebar)))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun dired-sidebar-show-sidebar (&optional b)
|
||
|
"Show sidebar displaying buffer B."
|
||
|
(interactive)
|
||
|
(let ((buffer (or b
|
||
|
;; Only expect this to be hit when called interactively.
|
||
|
(dired-sidebar-get-or-create-buffer
|
||
|
(dired-sidebar-get-dir-to-show)))))
|
||
|
(display-buffer-in-side-window buffer dired-sidebar-display-alist)
|
||
|
(let ((window (get-buffer-window buffer)))
|
||
|
(when dired-sidebar-no-delete-other-windows
|
||
|
(set-window-parameter window 'no-delete-other-windows t))
|
||
|
(set-window-dedicated-p window t)
|
||
|
(with-selected-window window
|
||
|
(let ((window-size-fixed))
|
||
|
(dired-sidebar-set-width dired-sidebar-width))))
|
||
|
(with-current-buffer buffer
|
||
|
(if (eq major-mode 'dired-sidebar-mode)
|
||
|
(dired-build-subdir-alist)
|
||
|
(dired-sidebar-mode)))
|
||
|
(dired-sidebar-update-state buffer)))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun dired-sidebar-hide-sidebar ()
|
||
|
"Hide the sidebar in the selected frame."
|
||
|
(interactive)
|
||
|
(dired-sidebar-when-let* ((buffer (dired-sidebar-buffer)))
|
||
|
(delete-window (get-buffer-window buffer))
|
||
|
(dired-sidebar-update-state nil)))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun dired-sidebar-jump-to-sidebar ()
|
||
|
"Jump to `dired-sidebar' buffer if it is showing.
|
||
|
|
||
|
If it's not showing, act as `dired-sidebar-toggle-sidebar'."
|
||
|
(interactive)
|
||
|
(if (dired-sidebar-showing-sidebar-p)
|
||
|
(select-window
|
||
|
(get-buffer-window (dired-sidebar-buffer (selected-frame))))
|
||
|
(call-interactively #'dired-sidebar-toggle-sidebar)))
|
||
|
|
||
|
(defun dired-sidebar-find-file (&optional dir)
|
||
|
"Wrapper over `dired-find-file'.
|
||
|
Optional argument DIR Fine file using DIR of available.
|
||
|
|
||
|
With prefix argument, use `dired-sidebar-alternate-select-window-function' for
|
||
|
window selection."
|
||
|
(interactive)
|
||
|
(let ((find-file-run-dired t)
|
||
|
(dired-file-name (or dir (dired-get-file-for-visit)))
|
||
|
(select-with-alt-window-function current-prefix-arg))
|
||
|
(if (and (file-directory-p dired-file-name)
|
||
|
;; For "." open a full-blown dired buffer, since the directory is
|
||
|
;; already open in the sidebar.
|
||
|
(not (string= (file-name-nondirectory dired-file-name)
|
||
|
".")))
|
||
|
(dired-sidebar-with-no-dedication
|
||
|
(let ((buf-name (dired-sidebar-buffer-name
|
||
|
dired-file-name)))
|
||
|
(if (dired-sidebar-buffer-exists-p buf-name)
|
||
|
(progn
|
||
|
(switch-to-buffer buf-name)
|
||
|
(dired-sidebar-update-state (current-buffer)))
|
||
|
(if (and dired-sidebar-one-instance-p (file-directory-p dired-file-name))
|
||
|
(find-alternate-file dired-file-name)
|
||
|
;; Copied from `dired-find-file'.
|
||
|
(find-file dired-file-name))
|
||
|
(dired-sidebar-mode)
|
||
|
(dired-sidebar-update-state (current-buffer)))))
|
||
|
;; Select the sidebar window so that `next-window' is consistent
|
||
|
;; in picking the window next to the sidebar.
|
||
|
;; This is useful for when `dired-sidebar-find-file' is called
|
||
|
;; from a buffer that is not already in the sidebar buffer.
|
||
|
;; e.g. A mouse click event.
|
||
|
(switch-to-buffer (dired-sidebar-buffer))
|
||
|
(select-window
|
||
|
(if select-with-alt-window-function
|
||
|
(funcall dired-sidebar-alternate-select-window-function)
|
||
|
(if dired-sidebar-open-file-in-most-recently-used-window
|
||
|
(get-mru-window nil nil t)
|
||
|
(next-window))))
|
||
|
(find-file dired-file-name)
|
||
|
(when dired-sidebar-close-sidebar-on-file-open
|
||
|
(dired-sidebar-hide-sidebar)))))
|
||
|
|
||
|
(defun dired-sidebar-find-file-alt ()
|
||
|
"Like `dired-sidebar-find-file' but select window with alterate method.
|
||
|
|
||
|
Select alternate window using `dired-sidebar-alternate-select-window-function'."
|
||
|
(interactive)
|
||
|
(let ((current-prefix-arg '(4))) ; C-u
|
||
|
(call-interactively 'dired-sidebar-find-file)))
|
||
|
|
||
|
(defun dired-sidebar-up-directory ()
|
||
|
"Wrapper over `dired-up-directory'."
|
||
|
(interactive)
|
||
|
(dired-sidebar-with-no-dedication
|
||
|
;; If `dired-subtree' is used, `dired-current-directory' is redefined.
|
||
|
;; So move point to the top of the buffer to get the actual directory and
|
||
|
;; not the one at point.
|
||
|
(when dired-sidebar-skip-subtree-parent
|
||
|
(goto-char (point-min)))
|
||
|
(let* ((dir (dired-current-directory))
|
||
|
(up (file-name-directory (directory-file-name dir)))
|
||
|
(up-name (dired-sidebar-buffer-name up)))
|
||
|
(if (dired-sidebar-buffer-exists-p up-name)
|
||
|
(progn
|
||
|
(switch-to-buffer up-name)
|
||
|
(dired-sidebar-update-state (current-buffer)))
|
||
|
(if dired-sidebar-one-instance-p
|
||
|
(find-alternate-file "..")
|
||
|
(dired-up-directory))
|
||
|
(dired-sidebar-mode)
|
||
|
(dired-sidebar-update-state (current-buffer)))
|
||
|
(let ((default-directory up))
|
||
|
(dired-goto-file dir)))))
|
||
|
|
||
|
(defun dired-sidebar-mouse-subtree-cycle-or-find-file (event)
|
||
|
"Handle a mouse click EVENT in `dired-sidebar'.
|
||
|
|
||
|
For directories, if `dired-sidebar-cycle-subtree-on-click' is true,
|
||
|
cycle the directory.
|
||
|
|
||
|
Otherwise, behaves the same as if user clicked on a file.
|
||
|
|
||
|
For files, use `dired-sidebar-find-file'.
|
||
|
|
||
|
This uses the same code as `dired-mouse-find-file-other-window' to find
|
||
|
the relevant file-directory clicked on by the mouse."
|
||
|
(interactive "e")
|
||
|
(let (window pos file)
|
||
|
(save-excursion
|
||
|
(setq window (posn-window (event-end event))
|
||
|
pos (posn-point (event-end event)))
|
||
|
(if (not (windowp window))
|
||
|
(error "No file chosen"))
|
||
|
(set-buffer (window-buffer window))
|
||
|
(goto-char pos)
|
||
|
(setq file (dired-get-file-for-visit)))
|
||
|
;; There's a flicker doing this but it doesn't seem like
|
||
|
;; `dired-subtree-cycle' works without first selecting the window.
|
||
|
(with-selected-window window
|
||
|
(if (and dired-sidebar-cycle-subtree-on-click
|
||
|
(file-directory-p file)
|
||
|
(not (string-suffix-p "." file)))
|
||
|
(dired-subtree-cycle)
|
||
|
(dired-sidebar-find-file file)))))
|
||
|
|
||
|
;; Helpers
|
||
|
|
||
|
(defun dired-sidebar-buffer-exists-p (buffer-name)
|
||
|
"Check if a `dired-sidebar' buffer exists for BUFFER-NAME."
|
||
|
(get-buffer buffer-name))
|
||
|
|
||
|
(defun dired-sidebar-sidebar-root ()
|
||
|
"Return directory using `projectile', `project' or current directory."
|
||
|
(if (featurep 'projectile)
|
||
|
(condition-case nil
|
||
|
(if (fboundp 'projectile-project-root)
|
||
|
(or (projectile-project-root) default-directory)
|
||
|
default-directory)
|
||
|
(error default-directory))
|
||
|
;; Use `project' if `projectile' is not loaded yet.
|
||
|
;; `projectile' is a big package and takes a while to load so it's better
|
||
|
;; to defer loading it as long as possible (until the user chooses).
|
||
|
(dired-sidebar-if-let* ((project (project-current)))
|
||
|
(cdr project)
|
||
|
default-directory)))
|
||
|
|
||
|
(defun dired-sidebar-buffer-name (dir)
|
||
|
"Return name of `dired-sidebar' buffer given DIR."
|
||
|
(let ((b (cond
|
||
|
((string-suffix-p ".." dir)
|
||
|
;; ~/.emacs.d/elpa/.. -> ~/.emacs.d/
|
||
|
(file-name-directory (substring dir 0 (- (length dir) 3))))
|
||
|
((not (string-suffix-p "/" dir))
|
||
|
(concat dir "/"))
|
||
|
(:default
|
||
|
dir))))
|
||
|
(concat ":" (abbreviate-file-name b))))
|
||
|
|
||
|
(defun dired-sidebar-get-or-create-buffer (root)
|
||
|
"Get or create a `dired-sidebar' buffer matching ROOT."
|
||
|
(interactive)
|
||
|
(let ((name (dired-sidebar-buffer-name root)))
|
||
|
(dired-sidebar-if-let* ((existing-buffer (get-buffer name)))
|
||
|
existing-buffer
|
||
|
(let ((buffer (dired-noselect root)))
|
||
|
;; When opening a sidebar while in a dired buffer that matches
|
||
|
;; the sidebar's root directory.
|
||
|
(if (eq (current-buffer) buffer)
|
||
|
;; https://github.com/Fuco1/dired-hacks/issues/102
|
||
|
(if (member 'dired-collapse-mode dired-mode-hook)
|
||
|
(progn
|
||
|
(remove-hook 'dired-mode-hook 'dired-collapse-mode)
|
||
|
(let ((clone (clone-buffer)))
|
||
|
(add-hook 'dired-mode-hook 'dired-collapse-mode)
|
||
|
clone))
|
||
|
(clone-buffer))
|
||
|
;; Rename the buffer generated by `dired-noselect'.
|
||
|
(when (not (string-equal (buffer-name buffer) name))
|
||
|
(with-current-buffer buffer
|
||
|
(rename-buffer name)))
|
||
|
buffer)))))
|
||
|
|
||
|
(defun dired-sidebar-set-font ()
|
||
|
"Customize font in `dired-sidebar'.
|
||
|
|
||
|
Set font to a variable width (proportional) in the current buffer."
|
||
|
(interactive)
|
||
|
(setq-local buffer-face-mode-face dired-sidebar-face)
|
||
|
(buffer-face-mode))
|
||
|
|
||
|
(defun dired-sidebar-set-mode-line ()
|
||
|
"Customize modeline in `dired-sidebar'."
|
||
|
(setq mode-line-format dired-sidebar-mode-line-format))
|
||
|
|
||
|
(defun dired-sidebar-set-width (width)
|
||
|
"Set the width of the buffer to WIDTH when it is created."
|
||
|
;; Copied from `treemacs--set-width' as well as `neotree'.
|
||
|
(unless (one-window-p)
|
||
|
(let ((window-size-fixed)
|
||
|
(w (max width window-min-width)))
|
||
|
(cond
|
||
|
((> (window-width) w)
|
||
|
(shrink-window-horizontally (- (window-width) w)))
|
||
|
((< (window-width) w)
|
||
|
(enlarge-window-horizontally (- w (window-width))))))))
|
||
|
|
||
|
(defun dired-sidebar-update-buffer-name ()
|
||
|
"Change buffer name to avoid collision with regular `dired' buffers."
|
||
|
(rename-buffer
|
||
|
(dired-sidebar-buffer-name (dired-current-directory))))
|
||
|
|
||
|
(defun dired-sidebar-update-state (buffer &optional f)
|
||
|
"Update current state with BUFFER for sidebar in F or selected frame."
|
||
|
(let ((frame (or f (selected-frame))))
|
||
|
(if (assq frame dired-sidebar-alist)
|
||
|
(setcdr (assq frame dired-sidebar-alist) buffer)
|
||
|
(push `(,frame . ,buffer) dired-sidebar-alist))))
|
||
|
|
||
|
(defun dired-sidebar-showing-sidebar-p (&optional f)
|
||
|
"Whether F or selected frame is showing a sidebar.
|
||
|
|
||
|
Check if F or selected frame contains a sidebar and return
|
||
|
corresponding buffer if buffer has a window attached to it.
|
||
|
|
||
|
Return buffer if so."
|
||
|
(dired-sidebar-when-let* ((buffer (dired-sidebar-buffer f)))
|
||
|
(get-buffer-window buffer)))
|
||
|
|
||
|
(defun dired-sidebar-buffer (&optional f)
|
||
|
"Return the current sidebar buffer in F or selected frame.
|
||
|
|
||
|
This can return nil if the buffer has been killed."
|
||
|
(let* ((frame (or f (selected-frame)))
|
||
|
(buffer (alist-get frame dired-sidebar-alist)))
|
||
|
;; The buffer can be killed for a variety of reasons.
|
||
|
;; This side effect is kind of messy but it's the simplest place
|
||
|
;; to put the clean up code for `dired-sidebar-alist'.
|
||
|
(if (buffer-live-p buffer)
|
||
|
buffer
|
||
|
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Association-Lists.html
|
||
|
;; Documentation for `assq-delete-all'.
|
||
|
;; What kind of API is this?? :()
|
||
|
;; Why does it only modify 'often' and not 'always'? ¯\_(ツ)_/¯
|
||
|
;; It returns the shortened alist, and often modifies the original list
|
||
|
;; structure of alist.
|
||
|
;; For correct results, use the return value of assq-delete-all rather
|
||
|
;; than looking at the saved value of alist.
|
||
|
(setq dired-sidebar-alist
|
||
|
(assq-delete-all frame dired-sidebar-alist))
|
||
|
nil)))
|
||
|
|
||
|
(defun dired-sidebar-switch-to-dir (dir)
|
||
|
"Update buffer with DIR as root."
|
||
|
(when (dired-sidebar-showing-sidebar-p)
|
||
|
(let ((buffer (dired-sidebar-get-or-create-buffer dir)))
|
||
|
(dired-sidebar-show-sidebar buffer))))
|
||
|
|
||
|
(defun dired-sidebar-buffer-stale-p (&optional noconfirm)
|
||
|
"Wrapper over `dired-buffer-stale-p'.
|
||
|
|
||
|
Check if buffer is stale only if `dired-sidebar-stale-buffer-time-idle-delay'
|
||
|
|
||
|
has elapsed.
|
||
|
|
||
|
Optional argument NOCONFIRM Pass NOCONFIRM on to `dired-buffer-stale-p'."
|
||
|
(when dired-sidebar-check-for-stale-buffer-p
|
||
|
(setq dired-sidebar-check-for-stale-buffer-p nil)
|
||
|
(dired-buffer-stale-p noconfirm)))
|
||
|
|
||
|
(defun dired-sidebar-refresh-buffer (&rest _)
|
||
|
"Refresh sidebar buffer."
|
||
|
(dired-sidebar-when-let* ((sidebar (dired-sidebar-buffer)))
|
||
|
(with-current-buffer sidebar
|
||
|
(let ((auto-revert-verbose nil))
|
||
|
(ignore auto-revert-verbose) ;; Make byte compiler happy.
|
||
|
(revert-buffer)))))
|
||
|
|
||
|
(defun dired-sidebar-follow-file ()
|
||
|
"Follow new file.
|
||
|
|
||
|
The root of the sidebar will be determined by `dired-sidebar-get-dir-to-show'
|
||
|
and the file followed is will be determined by `dired-sidebar-get-file-to-show',
|
||
|
|
||
|
both accounting for the currently selected window."
|
||
|
(when (dired-sidebar-showing-sidebar-p)
|
||
|
;; Wrap in `with-selected-window' because we don't want to pop to
|
||
|
;; the sidebar buffer.
|
||
|
;; We also need to pick the correct selected-window so that
|
||
|
;; `dired-sidebar-get-dir-to-show' can get the correct root to change to.
|
||
|
(with-selected-window (selected-window)
|
||
|
(let ((root (dired-sidebar-get-dir-to-show)))
|
||
|
(dired-sidebar-switch-to-dir root)
|
||
|
(when dired-sidebar-follow-file-at-point-on-toggle-open
|
||
|
(dired-sidebar-when-let* ((file (dired-sidebar-get-file-to-show)))
|
||
|
(dired-sidebar-point-at-file file root)))))))
|
||
|
|
||
|
(defun dired-sidebar-default-alternate-select-window ()
|
||
|
"Default function for `dired-sidebar-alternate-select-window-function'."
|
||
|
(if (fboundp 'aw-select)
|
||
|
(aw-select "Select Window")
|
||
|
(next-window)))
|
||
|
|
||
|
(defun dired-sidebar-get-dir-to-show ()
|
||
|
"Return the directory `dired-sidebar' should open to."
|
||
|
(expand-file-name
|
||
|
(cond
|
||
|
((and (derived-mode-p 'magit-mode)
|
||
|
dired-sidebar-use-magit-integration
|
||
|
(fboundp 'magit-toplevel))
|
||
|
(magit-toplevel))
|
||
|
((and (eq major-mode 'term-mode)
|
||
|
dired-sidebar-use-term-integration)
|
||
|
(dired-sidebar-term-get-pwd))
|
||
|
((and (eq major-mode 'dired-mode))
|
||
|
default-directory)
|
||
|
((and (eq major-mode 'ibuffer-mode)
|
||
|
(fboundp 'ibuffer-current-buffer)
|
||
|
(ibuffer-current-buffer))
|
||
|
(let ((buffer-at-point (ibuffer-current-buffer)))
|
||
|
(if (fboundp 'ibuffer-projectile-root)
|
||
|
(dired-sidebar-if-let* ((ibuffer-projectile-root
|
||
|
(ibuffer-projectile-root buffer-at-point)))
|
||
|
(cdr ibuffer-projectile-root)
|
||
|
(with-current-buffer buffer-at-point
|
||
|
default-directory))
|
||
|
(with-current-buffer buffer-at-point
|
||
|
default-directory))))
|
||
|
(:default
|
||
|
(dired-sidebar-sidebar-root)))))
|
||
|
|
||
|
(defun dired-sidebar-get-file-to-show ()
|
||
|
"Return the file `dired-sidebar' should open to.
|
||
|
|
||
|
This may return nil if there's no suitable file to show."
|
||
|
(cond
|
||
|
((and dired-sidebar-use-magit-integration
|
||
|
(derived-mode-p 'magit-mode)
|
||
|
(fboundp 'magit-file-at-point)
|
||
|
(magit-file-at-point))
|
||
|
(expand-file-name (magit-file-at-point)))
|
||
|
((and (eq major-mode 'dired-mode))
|
||
|
;; Not sure if `dired-get-filename' is more appropriate.
|
||
|
(condition-case nil
|
||
|
(dired-get-file-for-visit)
|
||
|
(error nil)))
|
||
|
((and (eq major-mode 'ibuffer-mode)
|
||
|
(fboundp 'ibuffer-current-buffer))
|
||
|
(let ((bf-name (buffer-file-name (ibuffer-current-buffer))))
|
||
|
(and bf-name (file-exists-p bf-name) bf-name)))
|
||
|
(:default
|
||
|
(and buffer-file-name (file-exists-p buffer-file-name) buffer-file-name))))
|
||
|
|
||
|
(defun dired-sidebar-term-get-pwd ()
|
||
|
"Get current directory of `term-mode'.
|
||
|
|
||
|
This is somewhat experimental/hacky."
|
||
|
(interactive)
|
||
|
(condition-case nil
|
||
|
(progn
|
||
|
(forward-paragraph)
|
||
|
(when (fboundp 'term-previous-prompt)
|
||
|
(term-previous-prompt 1))
|
||
|
(when (fboundp 'term-simple-send)
|
||
|
(term-simple-send (get-buffer-process (current-buffer)) "pwd"))
|
||
|
(sleep-for 0 50)
|
||
|
(forward-line 1)
|
||
|
(let ((result (string-trim (thing-at-point 'line))))
|
||
|
(kill-whole-line)
|
||
|
(forward-line -1)
|
||
|
(kill-whole-line)
|
||
|
result))
|
||
|
(error
|
||
|
default-directory)))
|
||
|
|
||
|
(defun dired-sidebar-subtree-toggle ()
|
||
|
"Wrapper over `dired-subtree-toggle' that accounts for `all-the-icons-dired'."
|
||
|
(interactive)
|
||
|
(dired-subtree-toggle)
|
||
|
(dired-sidebar-redisplay-icons))
|
||
|
|
||
|
(defun dired-sidebar-redisplay-icons ()
|
||
|
"Redisplay icon themes unless over TRAMP."
|
||
|
(unless (file-remote-p default-directory)
|
||
|
(when (and (eq dired-sidebar-theme 'icons)
|
||
|
(fboundp 'all-the-icons-dired--refresh))
|
||
|
;; Refresh `all-the-icons-dired'.
|
||
|
(dired-sidebar-revert)
|
||
|
(all-the-icons-dired--refresh))
|
||
|
(when (dired-sidebar-using-tui-p)
|
||
|
(dired-sidebar-tui-update-with-delay))))
|
||
|
|
||
|
(defun dired-sidebar-advice-hide-temporarily (f &rest args)
|
||
|
"A function meant to be used with advice to temporarily hide itself.
|
||
|
|
||
|
This function hides the sidebar before executing F and then reshows itself after."
|
||
|
(if (not (dired-sidebar-showing-sidebar-p))
|
||
|
(apply f args)
|
||
|
(let ((sidebar (dired-sidebar-buffer)))
|
||
|
(dired-sidebar-hide-sidebar)
|
||
|
(apply f args)
|
||
|
(dired-sidebar-show-sidebar sidebar))))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Text User Interface ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defvar-local dired-sidebar-tui-dired-displayed nil
|
||
|
"Flags whether icons have been added.")
|
||
|
|
||
|
(defun dired-sidebar-tui-dired-reset (&optional _arg _noconfirm)
|
||
|
"Function used as advice when redisplaying buffer."
|
||
|
(setq-local dired-sidebar-tui-dired-displayed nil))
|
||
|
|
||
|
(defun dired-sidebar-tui-dired-display ()
|
||
|
"Display the icons of files in a dired buffer."
|
||
|
(interactive)
|
||
|
(when (or t (and (not dired-sidebar-tui-dired-displayed) dired-subdir-alist))
|
||
|
(setq-local dired-sidebar-tui-dired-displayed t)
|
||
|
(let ((inhibit-read-only t)
|
||
|
(collapsible-icon (if (eq dired-sidebar-theme 'nerd) "▾" "-"))
|
||
|
(expandable-icon (if (eq dired-sidebar-theme 'nerd) "▸" "+")))
|
||
|
(save-excursion
|
||
|
(goto-char (point-min))
|
||
|
(while (not (eobp))
|
||
|
(when (dired-move-to-filename nil)
|
||
|
(dired-move-to-filename)
|
||
|
(let ((file (dired-get-filename 'verbatim t)))
|
||
|
(unless (member file '("." ".."))
|
||
|
(let ((filename (dired-get-filename nil t)))
|
||
|
(if (eq dired-sidebar-theme 'vscode)
|
||
|
(progn
|
||
|
(require 'vscode-icon)
|
||
|
(when (fboundp 'vscode-icon-for-file)
|
||
|
(insert-image
|
||
|
(vscode-icon-for-file filename) " "))
|
||
|
(insert " "))
|
||
|
(if (file-directory-p filename)
|
||
|
(if (dired-subtree--is-expanded-p)
|
||
|
(insert (concat collapsible-icon " "))
|
||
|
(insert (concat expandable-icon " ")))
|
||
|
(insert "")))))))
|
||
|
(forward-line 1))))))
|
||
|
|
||
|
(defun dired-sidebar-tui-update-with-delay (&rest _)
|
||
|
"Update tui interface after a delay."
|
||
|
(run-with-idle-timer
|
||
|
dired-sidebar-tui-update-delay nil
|
||
|
#'dired-sidebar-tui-update))
|
||
|
|
||
|
(defun dired-sidebar-tui-update ()
|
||
|
"Workhorse function to update tui interface."
|
||
|
(dired-sidebar-when-let* ((buffer (dired-sidebar-buffer)))
|
||
|
(with-current-buffer buffer
|
||
|
(dired-sidebar-revert)
|
||
|
(when dired-sidebar-recenter-cursor-on-tui-update
|
||
|
(recenter)))))
|
||
|
|
||
|
(defun dired-sidebar-revert (&rest _)
|
||
|
"Wrapper around `dired-revert' but saves window position."
|
||
|
(dired-sidebar-when-let* ((window (get-buffer-window
|
||
|
(dired-sidebar-buffer))))
|
||
|
(with-selected-window window
|
||
|
(let ((old-window-start (window-start)))
|
||
|
(when (dired-sidebar-using-tui-p)
|
||
|
(dired-sidebar-tui-reset-in-sidebar))
|
||
|
(dired-revert)
|
||
|
(set-window-start window old-window-start)))))
|
||
|
|
||
|
(defun dired-sidebar-tui-reset-in-sidebar (&rest _)
|
||
|
"Runs `dired-sidebar-tui-dired-reset' in current `dired-sidebar' buffer."
|
||
|
(dired-sidebar-when-let* ((buffer (dired-sidebar-buffer)))
|
||
|
(with-current-buffer buffer
|
||
|
(dired-sidebar-tui-dired-reset))))
|
||
|
|
||
|
(defun dired-sidebar-setup-tui ()
|
||
|
"Sets up text user interface for `dired-sidebar'.
|
||
|
|
||
|
This is used in place of `all-the-icons' to add directory indicators.
|
||
|
|
||
|
e.g. + and -."
|
||
|
(add-hook 'dired-after-readin-hook
|
||
|
'dired-sidebar-tui-dired-display :append :local)
|
||
|
(setq-local dired-subtree-line-prefix " ")
|
||
|
(dired-build-subdir-alist)
|
||
|
(dired-sidebar-revert))
|
||
|
|
||
|
(defun dired-sidebar-using-tui-p ()
|
||
|
"Return t if `dired-sidebar-theme' is using tui code path."
|
||
|
(or
|
||
|
(eq dired-sidebar-theme 'ascii)
|
||
|
(eq dired-sidebar-theme 'nerd)
|
||
|
(eq dired-sidebar-theme 'vscode)))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Text User Interface ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; `wdired' Hack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32392
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; `wdired' Hack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defvar-local dired-sidebar-wdired-tracking-major-mode nil
|
||
|
"Track current `major-mode' when toggling to `wdired'.")
|
||
|
|
||
|
(defun dired-sidebar-wdired-change-to-dired-mode-advice (f &rest args)
|
||
|
"Advice for `wdired-change-to-dired-mode'."
|
||
|
(if (eq dired-sidebar-wdired-tracking-major-mode 'dired-sidebar-mode)
|
||
|
(dired-sidebar-wdired-change-to-dired-mode)
|
||
|
(apply f args)))
|
||
|
|
||
|
(defun dired-sidebar-wdired-change-to-dired-mode ()
|
||
|
"Change the mode back to dired-sidebar.
|
||
|
|
||
|
This is an exact copy of `wdired-change-to-dired-mode' but changes the
|
||
|
`major-mode' to `dired-sidebar-mode' instead of `dired-mode'."
|
||
|
(let ((inhibit-read-only t))
|
||
|
(remove-text-properties
|
||
|
(point-min) (point-max)
|
||
|
'(front-sticky nil rear-nonsticky nil read-only nil keymap nil)))
|
||
|
(use-local-map dired-mode-map)
|
||
|
(force-mode-line-update)
|
||
|
(setq buffer-read-only t)
|
||
|
(setq major-mode 'dired-sidebar-mode)
|
||
|
(setq mode-name "Dired-sidebar")
|
||
|
(dired-advertise)
|
||
|
(remove-hook 'kill-buffer-hook 'wdired-check-kill-buffer t)
|
||
|
(set (make-local-variable 'revert-buffer-function) 'dired-sidebar-revert))
|
||
|
|
||
|
(defun dired-sidebar-wdired-change-to-wdired-mode-advice (f &rest args)
|
||
|
"Forward to `wdired-change-to-wdired-mode'.
|
||
|
|
||
|
`wdired' expected the `major-mode' to be `dired-mode' first.
|
||
|
|
||
|
Track the current `major-mode' and revert to that upon exiting `wdired'."
|
||
|
(setq dired-sidebar-wdired-tracking-major-mode major-mode)
|
||
|
(if (eq major-mode 'dired-mode)
|
||
|
(apply f args)
|
||
|
(let ((major-mode 'dired-mode))
|
||
|
(apply f args))))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; `wdired' Hack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(provide 'dired-sidebar)
|
||
|
;;; dired-sidebar.el ends here
|