diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
| commit | 092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch) | |
| tree | ea81999b8442246c978b364dd90e8c752af50db5 /modules/dirvish-config.el | |
changing repositories
Diffstat (limited to 'modules/dirvish-config.el')
| -rw-r--r-- | modules/dirvish-config.el | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el new file mode 100644 index 00000000..82b44008 --- /dev/null +++ b/modules/dirvish-config.el @@ -0,0 +1,403 @@ +;;; dirvish-config.el --- Dired/Dirvish Configuration -*- lexical-binding: t; coding: utf-8; -*- +;; author: Craig Jennings <c@cjennings.net> + +;;; Commentary: + +;; DIRVISH NOTES: +;; access the quick access directories by pressing 'g' (for "go") + +;;; Code: + +(require 'user-constants) +(require 'system-utils) + +;;; ----------------------------- Dired Ediff Files ----------------------------- + +(defun cj/dired-ediff-files () + "Ediff two selected files within Dired." + (interactive) + (let ((files (dired-get-marked-files)) + (wnd (current-window-configuration))) + (if (<= (length files) 2) + (let ((file1 (car files)) + (file2 (if (cdr files) + (cadr files) + (read-file-name + "file: " + (dired-dwim-target-directory))))) + (if (file-newer-than-file-p file1 file2) + (ediff-files file2 file1) + (ediff-files file1 file2)) + (add-hook 'ediff-after-quit-hook-internal + (lambda () + (setq ediff-after-quit-hook-internal nil) + (set-window-configuration wnd)))) + (error "No more than 2 files should be marked")))) + +;; ------------------------ Create Playlist From Marked ------------------------ + +(defvar cj/audio-file-extensions + '("mp3" "flac" "m4a" "wav" "ogg" "aac" "opus" "aiff" "alac" "wma") + "List of audio file extensions (lowercase, no dot). +Used to filter files for M3U playlists.") + +(defun cj/dired-create-playlist-from-marked () + "Create an .m3u playlist file from marked files in Dired (or Dirvish). + +Filters for audio files, prompts for the playlist name, and saves the resulting +.m3u in the directory specified by =music-dir=. Interactive use only." + (interactive) + (let* ((marked-files (dired-get-marked-files)) + (audio-files + (cl-remove-if-not + (lambda (f) + (let ((ext (file-name-extension f))) + (and ext + (member (downcase ext) cj/audio-file-extensions)))) + marked-files)) + (count (length audio-files))) + (if (zerop count) + (user-error "No audio files marked (extensions: %s)" + (string-join cj/audio-file-extensions ", ")) + (let ((base-name nil) + (playlist-path nil) + (done nil)) + (while (not done) + (setq base-name (read-string + (format "Playlist name (without .m3u): "))) + ;; Sanitize: strip any trailing .m3u + (setq base-name (replace-regexp-in-string "\\.m3u\\'" "" base-name)) + (setq playlist-path (expand-file-name (concat base-name ".m3u") music-dir)) + (cond + ((not (file-exists-p playlist-path)) + ;; Safe to write + (setq done t)) + (t + (let ((choice (read-char-choice + (format "Playlist '%s' exists. [o]verwrite, [c]ancel, [r]ename? " + (file-name-nondirectory playlist-path)) + '(?o ?c ?r)))) + (cl-case choice + (?o (setq done t)) + (?c (user-error "Cancelled playlist creation")) + (?r (setq done nil))))))) + ;; Actually write the file + (with-temp-file playlist-path + (dolist (af audio-files) + (insert af "\n"))) + (message "Wrote playlist %s with %d tracks" (file-name-nondirectory playlist-path) count))))) + +;;; ----------------------------------- Dired ----------------------------------- + +(use-package dired + :ensure nil ;; built-in + :defer t + :bind + (:map dired-mode-map + ([remap dired-summary] . which-key-show-major-mode) + ("E" . wdired-change-to-wdired-mode) ;; edit names and properties in buffer + ("e" . cj/dired-ediff-files)) ;; ediff files + :custom + (dired-use-ls-dired nil) ;; non GNU FreeBSD doesn't support a "--dired" switch + :config + (setq dired-listing-switches "-l --almost-all --human-readable --group-directories-first") + (setq dired-dwim-target t) + (setq dired-clean-up-buffers-too t) ;; offer to kill buffers associated deleted files and dirs + (setq dired-clean-confirm-killing-deleted-buffers t) ;; don't ask; just kill buffers associated with deleted files + (setq dired-recursive-copies (quote always)) ;; “always” means no asking + (setq dired-recursive-deletes (quote top))) ;; “top” means ask once + +;; note: disabled as it prevents marking and moving files to another directory +;; (setq dired-kill-when-opening-new-dired-buffer t) ;; don't litter by leaving buffers when navigating directories + +(add-hook 'dired-mode-hook 'auto-revert-mode) ;; auto revert dired when files change + +;;; --------------------------- Dired Open HTML In EWW -------------------------- + +(defun cj/dirvish-open-html-in-eww () + "Open HTML file at point in dired/dirvish using eww." + (interactive) + (let ((file (dired-get-file-for-visit))) + (if (string-match-p "\\.html?\\'" file) + (eww-open-file file) + (message "Not an HTML file: %s" file)))) + +;;; ------------------------ Dired Mark All Visible Files ----------------------- + +(defun cj/dired-mark-all-visible-files () + "Mark all visible files in Dired mode." + (interactive) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (if (not (looking-at "^. d")) + (dired-mark 1)) + (forward-line 1)))) + +;;; ----------------------- Dirvish Open File Manager Here ---------------------- + +(defun cj/dirvish-open-file-manager-here () + "Open system's default file manager in the current dired/dirvish directory. + +Always opens the file manager in the directory currently being displayed, +regardless of what file or subdirectory the point is on." + (interactive) + (let ((current-dir (dired-current-directory))) + (if (and current-dir (file-exists-p current-dir)) + (progn + (message "Opening file manager in %s..." current-dir) + ;; Use shell-command with & to run asynchronously and detached + (let ((process-connection-type nil)) ; Use pipe instead of pty + (cond + ;; Linux/Unix with xdg-open + ((executable-find "xdg-open") + (call-process "xdg-open" nil 0 nil current-dir)) + ;; macOS + ((eq system-type 'darwin) + (call-process "open" nil 0 nil current-dir)) + ;; Windows + ((eq system-type 'windows-nt) + (call-process "explorer" nil 0 nil current-dir)) + ;; Fallback to shell-command + (t + (shell-command (format "xdg-open %s &" + (shell-quote-argument current-dir))))))) + (message "Could not determine current directory.")))) + +;;; ---------------------------------- Dirvish ---------------------------------- + +(use-package dirvish + :defer 1 + :init + (dirvish-override-dired-mode) + :custom + ;; This MUST be in :custom section, not :config + (dirvish-quick-access-entries + `(("h" "~/" "home") + ("cx" ,code-dir "code directory") + ("ex" ,user-emacs-directory "emacs home") + ("es" ,sounds-dir "notification sounds") + ("ra" ,video-recordings-dir "video recordings") + ("rv" ,audio-recordings-dir "audio recordings") + ("dl" ,dl-dir "downloads") + ("dr" ,(concat sync-dir "/drill/") "drill files") + ("dt" ,(concat dl-dir "/torrents/complete/") "torrents") + ("dx" "~/documents/" "documents") + ("lx" "~/lectures/" "lectures") + ("mb" "/media/backup/" "backup directory") + ("mx" "~/music/" "music") + ("pD" "~/projects/documents/" "project documents") + ("pd" "~/projects/danneel/" "project danneel") + ("pl" "~/projects/elibrary/" "project elibrary") + ("pf" "~/projects/finances/" "project finances") + ("pjr" "~/projects/jr-estate/" "project jr-estate") + ("ps" ,(concat pix-dir "/screenshots/") "pictures screenshots") + ("pw" ,(concat pix-dir "/wallpaper/") "pictures wallpaper") + ("px" ,pix-dir "pictures directory") + ("rcj" "/sshx:cjennings@cjennings.net:~" "remote cjennings.net") + ("rsb" "/sshx:cjennings@wolf.usbx.me:/home/cjennings/" "remote seedbox") + ("sx" ,sync-dir "sync directory") + ("so" "~/sync/org" "org directory") + ("sv" "~/sync/videos/" "sync/videos directory") + ("tg" ,(concat sync-dir "/text.games") "text games") + ("vr" ,video-recordings-dir "video recordings directory") + ("vx" ,videos-dir "videos"))) + :config + ;; Add the extensions directory to load-path + (let ((extensions-dir (expand-file-name "extensions" + (file-name-directory (locate-library "dirvish"))))) + (when (file-directory-p extensions-dir) + (add-to-list 'load-path extensions-dir))) + + ;; Load dirvish modules with error checking + (let ((dirvish-modules '(dirvish-emerge + dirvish-subtree + dirvish-narrow + dirvish-history + dirvish-ls + dirvish-yank + dirvish-quick-access + dirvish-collapse + dirvish-rsync + dirvish-vc + dirvish-icons + dirvish-side + dirvish-peek))) + (dolist (module dirvish-modules) + (condition-case err + (require module) + (error + (message "Failed to load %s: %s" module (error-message-string err)))))) + + ;; Enable peek mode with error checking + (condition-case err + (dirvish-peek-mode 1) + (error (message "Failed to enable dirvish-peek-mode: %s" (error-message-string err)))) + + ;; Enable side-follow mode with error checking + (condition-case err + (dirvish-side-follow-mode 1) + (error (message "Failed to enable dirvish-side-follow-mode: %s" + (error-message-string err)))) + + ;; Your other configuration settings + (setq dirvish-attributes '(nerd-icons file-size)) + (setq dirvish-preview-dispatchers '(image gif video audio epub pdf archive)) + (setq dirvish-use-mode-line nil) + (setq dirvish-use-header-line nil) + :bind + (("C-x d" . dirvish) + ("C-x C-d" . dirvish) + ("C-x D" . dirvish) + ("<f11>" . dirvish-side) + :map dirvish-mode-map + ("bg" . (lambda () (interactive) + (shell-command + (concat "nitrogen --save --set-zoom-fill " + (dired-file-name-at-point) " >>/dev/null 2>&1")))) + ("/" . dirvish-narrow) + ("<left>" . dired-up-directory) + ("<right>" . dired-find-file) + ("C-," . dirvish-history-go-backward) + ("C-." . dirvish-history-go-forward) + ("F" . dirvish-file-info-menu) + ("G" . revert-buffer) + ("l" . (lambda () (interactive) (cj/dired-copy-path-as-kill))) ;; overwrites dired-do-redisplay + ("h" . cj/dirvish-open-html-in-eww) ;; it does what it says it does + ("M" . cj/dired-mark-all-visible-files) + ("M-e" . dirvish-emerge-menu) + ("M-l" . dirvish-ls-switches-menu) + ("M-m" . dirvish-mark-menu) + ("M-p" . dirvish-peek-toggle) + ("M-s" . dirvish-setup-menu) + ("TAB" . dirvish-subtree-toggle) + ("d". dired-flag-file-deletion) + ("f" . cj/dirvish-open-file-manager-here) + ("g" . dirvish-quick-access) + ("o" . cj/xdg-open) + ("O" . cj/open-file-with-command) ; Prompts for command to run + ("r" . dirvish-rsync) + ("p" . cj/dired-create-playlist-from-marked) + ("s" . dirvish-quicksort) + ("v" . dirvish-vc-menu) + ("y" . dirvish-yank-menu))) + +;;; -------------------------------- Nerd Icons ------------------------------- + +(use-package nerd-icons + :defer .5) + +(use-package nerd-icons-dired + :commands (nerd-icons-dired-mode)) + +;;; ---------------------------- Dired Hide Dotfiles ---------------------------- + +(use-package dired-hide-dotfiles + :after dired + :hook + ;; Auto-hide dotfiles when entering dired/dirvish + ((dired-mode . dired-hide-dotfiles-mode) + (dirvish-mode . dired-hide-dotfiles-mode)) + :bind + (:map dired-mode-map + ("." . dired-hide-dotfiles-mode))) + +;;; ------------------------------- Dired Sidebar ------------------------------- + +(use-package dired-sidebar + :after (dired projectile) + :bind (("<f11>" . dired-sidebar-toggle-sidebar)) + :commands (dired-sidebar-toggle-sidebar) + :init + (add-hook 'dired-sidebar-mode-hook + (lambda () + (unless (file-remote-p default-directory) + (auto-revert-mode)))) + :config + (push 'toggle-window-split dired-sidebar-toggle-hidden-commands) ;; disallow splitting dired window when it's showing + (push 'rotate-windows dired-sidebar-toggle-hidden-commands) ;; disallow rotating windows when sidebar is showing + (setq dired-sidebar-subtree-line-prefix " ") ;; two spaces give simple and aesthetic indentation + (setq dired-sidebar-no-delete-other-windows t) ;; don't close when calling 'delete other windows' + (setq dired-sidebar-theme 'nerd-icons) ;; gimme fancy icons, please + (setq dired-sidebar-use-custom-font 'nil) ;; keep the same font as the rest of Emacs + (setq dired-sidebar-delay-auto-revert-updates 'nil) ;; don't delay auto-reverting + (setq dired-sidebar-pop-to-sidebar-on-toggle-open 'nil)) ;; don't jump to sidebar when it's toggled on + +;; --------------------------------- Copy Path --------------------------------- + +(defun cj/dired-copy-path-as-kill (&optional as-org-link) + "Copy path of file at point in Dired/Dirvish. +Copies relative path from project root if in a project, otherwise from home +directory (with ~ prefix) if applicable, otherwise the absolute path. + +With prefix arg or when AS-ORG-LINK is non-nil, format as \='org-mode\=' link." + (interactive "P") + (unless (derived-mode-p 'dired-mode) + (user-error "Not in a Dired buffer")) + + (let* ((file (dired-get-filename nil t)) + (file-name (file-name-nondirectory file)) + (project-root (cj/get-project-root)) + (home-dir (expand-file-name "~")) + path path-type) + + (unless file + (user-error "No file at point")) + + (cond + ;; Project-relative path + (project-root + (setq path (file-relative-name file project-root) + path-type "project-relative")) + + ;; Home-relative path + ((string-prefix-p home-dir file) + (let ((relative-from-home (file-relative-name file home-dir))) + (setq path (if (string= relative-from-home ".") + "~" + (concat "~/" relative-from-home)) + path-type "home-relative"))) + + ;; Absolute path + (t + (setq path file + path-type "absolute"))) + + ;; Format as org-link if requested + (when as-org-link + (setq path (format "[[file:%s][%s]]" path file-name))) + + ;; Copy to kill-ring and clipboard + (kill-new path) + + ;; Provide feedback + (message "Copied %s path%s: %s" + path-type + (if as-org-link " as org-link" "") + (if (> (length path) 60) + (concat (substring path 0 57) "...") + path)))) + +(defun cj/get-project-root () + "Get project root using projectile or project.el. +Returns nil if not in a project." + (cond + ;; Try projectile first if available + ((and (fboundp 'projectile-project-root) + (ignore-errors (projectile-project-root)))) + + ;; Fallback to project.el + ((and (fboundp 'project-current) + (project-current)) + (let ((proj (project-current))) + (if (fboundp 'project-root) + (project-root proj) + ;; Compatibility with older versions + (car (project-roots proj))))) + + ;; No project found + (t nil))) + + +(provide 'dirvish-config) +;;; dirvish-config.el ends here. |
