From bdf9a71ab7baa2b1de9abcfd5df1a9107a55d141 Mon Sep 17 00:00:00 2001 From: Jack Jamison Date: Sun, 5 Jul 2026 01:19:30 -0400 Subject: add a bunch of emacs packages HELP --- .../emacs/lisp/dired-hacks/dired-hacks-utils.el | 275 +++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 .config/emacs/lisp/dired-hacks/dired-hacks-utils.el (limited to '.config/emacs/lisp/dired-hacks/dired-hacks-utils.el') diff --git a/.config/emacs/lisp/dired-hacks/dired-hacks-utils.el b/.config/emacs/lisp/dired-hacks/dired-hacks-utils.el new file mode 100644 index 0000000..b492f76 --- /dev/null +++ b/.config/emacs/lisp/dired-hacks/dired-hacks-utils.el @@ -0,0 +1,275 @@ +;;; dired-hacks-utils.el --- Utilities and helpers for dired-hacks collection + +;; Copyright (C) 2014-2015 Matúš Goljer + +;; Author: Matúš Goljer +;; Maintainer: Matúš Goljer +;; Keywords: files +;; Version: 0.0.1 +;; Created: 14th February 2014 +;; Package-Requires: ((dash "2.5.0") (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 . + +;;; Commentary: + +;; Utilities and helpers for `dired-hacks' collection of dired +;; improvements. + +;; This package also provides these interactive functions: +;; * `dired-hacks-next-file' - go to next file, skipping empty and non-file lines +;; * `dired-hacks-previous-file' - go to previous file, skipping empty +;; and non-file lines +;; * `dired-utils-format-information-line-mode' - Format the information +;; (summary) line file sizes to be human readable (e.g. 1GB instead of 1048576). + + +;; See https://github.com/Fuco1/dired-hacks for the entire collection + +;;; Code: + +(require 'dash) +(require 'dired) +(require 'dired-aux) ;; for dired-dwim-target-directory + +(defgroup dired-hacks () + "Collection of useful dired additions." + :group 'dired + :prefix "dired-hacks-") + +(defcustom dired-hacks-file-size-formatter #'file-size-human-readable + "The function used to format file sizes. + +See `dired-utils-format-file-sizes'." + :type 'function + :group 'dired-hacks) + +(defcustom dired-hacks-datetime-regexp + "\\sw\\sw\\sw....\\(?:[0-9][0-9]:[0-9][0-9]\\|.[0-9]\\{4\\}\\)" + "A regexp matching the date/time in the dired listing. + +It is used to determine where the filename starts. It should +*not* match any characters after the last character of the +timestamp. It is assumed that the timestamp is preceded and +followed by at least one space character. You should only use +shy groups (prefixed with ?:) because the first group is used by +the font-lock to determine what portion of the name should be +colored." + :type 'regexp + :group 'dired-hacks) + +(defalias 'dired-utils--string-trim + (if (and (require 'subr-x nil t) + (fboundp 'string-trim)) + #'string-trim + (lambda (string) + (let ((s string)) + (when (string-match "\\`[ \t\n\r]+" s) + (setq s (replace-match "" t t s))) + (when (string-match "[ \t\n\r]+\\'" s) + (setq s (replace-match "" t t s))) + s))) + "Trim STRING of trailing whitespace. + +\(fn STRING)") + +(defun dired-utils-get-filename (&optional localp) + "Like `dired-get-filename' but never signal an error. + +Optional arg LOCALP with value `no-dir' means don't include +directory name in result." + (dired-get-filename localp t)) + +(defun dired-utils-get-all-files (&optional localp) + "Return all files in this dired buffer as a list. + +LOCALP has same semantics as in `dired-get-filename'." + (save-excursion + (goto-char (point-min)) + (let (r) + (while (= 0 (forward-line)) + (--when-let (dired-utils-get-filename localp) + (push it r))) + (nreverse r)))) + +(defconst dired-utils-file-attributes-keywords + '(:isdir :nlinks :uid :gid :atime :mtime :ctime :size :modes :gidchg :inode :devnum) + "List of keywords to map with `file-attributes'.") + +(defconst dired-utils-info-keywords + `(:name :issym :target ,@dired-utils-file-attributes-keywords) + "List of keywords available for `dired-utils-get-info'.") + +(defun dired-utils--get-keyword-info (keyword) + "Get file information about KEYWORD." + (let ((filename (dired-utils-get-filename))) + (cl-case keyword + (:name filename) + (:isdir (file-directory-p filename)) + (:issym (and (file-symlink-p filename) t)) + (:target (file-symlink-p filename)) + (t + (nth (-elem-index keyword dired-utils-file-attributes-keywords) + (file-attributes filename)))))) + +(defun dired-utils-get-info (&rest keywords) + "Query for info about the file at point. + +KEYWORDS is a list of attributes to query. + +When querying for one attribute, its value is returned. When +querying for more than one, a list of results is returned. + +The available keywords are listed in +`dired-utils-info-keywords'." + (let ((attributes (mapcar 'dired-utils--get-keyword-info keywords))) + (if (> (length attributes) 1) + attributes + (car attributes)))) + +(defun dired-utils-goto-line (filename) + "Go to line describing FILENAME in listing. + +Should be absolute file name matched against +`dired-get-filename'." + (goto-char (point-min)) + (let (stop) + (while (and (not stop) + (= (forward-line) 0)) + (when (equal filename (dired-utils-get-filename)) + (setq stop t) + (dired-move-to-filename))) + stop)) + +(defun dired-utils-match-filename-regexp (filename alist) + "Match FILENAME against each car in ALIST and return first matched cons. + +Each car in ALIST is a regular expression. + +The matching is done using `string-match-p'." + (let (match) + (--each-while alist (not match) + (when (string-match-p (car it) filename) + (setq match it))) + match)) + +(defun dired-utils-match-filename-extension (filename alist) + "Match FILENAME against each car in ALIST and return first matched cons. + +Each car in ALIST is a string representing file extension +*without* the delimiting dot." + (let (done) + (--each-while alist (not done) + (when (string-match-p (concat "\\." (regexp-quote (car it)) "\\'") filename) + (setq done it))) + done)) + +(defun dired-utils-format-information-line () + "Format the disk space on the Dired information line." + (save-excursion + (goto-char (point-min)) + (forward-line) + (let ((inhibit-read-only t) + (limit (line-end-position))) + (while (re-search-forward "\\(?:directory\\|available\\) \\(\\<[0-9]+$\\>\\)" nil t) + (replace-match + (save-match-data + (propertize (dired-utils--string-trim + (funcall dired-hacks-file-size-formatter + (* 1024 (string-to-number (match-string 1))) t)) + 'invisible 'dired-hide-details-information)) + t nil nil 1))))) + + +;;; Predicates +(defun dired-utils-is-file-p () + "Return non-nil if the line at point is a file or a directory." + (dired-utils-get-filename 'no-dir)) + +(defun dired-utils-is-dir-p () + "Return non-nil if the line at point is a directory." + (--when-let (dired-utils-get-filename) + (file-directory-p it))) + + +;;; Interactive +;; TODO: add wrap-around option +(defun dired-hacks-next-file (&optional arg) + "Move point to the next file. + +Optional prefix ARG says how many lines to move; default is one +line." + (interactive "p") + (unless arg (setq arg 1)) + (if (< arg 0) + (dired-hacks-previous-file (- arg)) + (--dotimes arg + (forward-line) + (while (and (or (not (dired-utils-is-file-p)) + (get-text-property (point) 'invisible)) + (= (forward-line) 0)))) + (if (not (= (point) (point-max))) + (dired-move-to-filename) + (forward-line -1) + (dired-move-to-filename) + nil))) + +(defun dired-hacks-previous-file (&optional arg) + "Move point to the previous file. + +Optional prefix ARG says how many lines to move; default is one +line." + (interactive "p") + (unless arg (setq arg 1)) + (if (< arg 0) + (dired-hacks-next-file (- arg)) + (--dotimes arg + (forward-line -1) + (while (and (or (not (dired-utils-is-file-p)) + (get-text-property (point) 'invisible)) + (= (forward-line -1) 0)))) + (if (not (= (point) (point-min))) + (dired-move-to-filename) + (dired-hacks-next-file) + nil))) + +(defun dired-hacks-compare-files (file-a file-b) + "Test if two files FILE-A and FILE-B are the (probably) the same." + (interactive (let ((other-dir (dired-dwim-target-directory))) + (list (read-file-name "File A: " default-directory (car (dired-get-marked-files)) t) + (read-file-name "File B: " other-dir (with-current-buffer (cdr (assoc other-dir dired-buffers)) + (car (dired-get-marked-files))) t)))) + (let ((md5-a (with-temp-buffer + (shell-command (format "md5sum %s" file-a) (current-buffer)) + (buffer-string))) + (md5-b (with-temp-buffer + (shell-command (format "md5sum %s" file-b) (current-buffer)) + (buffer-string)))) + (message "%s%sFiles are %s." md5-a md5-b + (if (equal (car (split-string md5-a)) + (car (split-string md5-b))) + "probably the same" "different")))) + +(define-minor-mode dired-utils-format-information-line-mode + "Toggle formatting of disk space in the Dired information line." + :group 'dired-utils + :lighter "" + (if dired-utils-format-information-line-mode + (add-hook 'dired-after-readin-hook #'dired-utils-format-information-line) + (remove-hook 'dired-after-readin-hook #'dired-utils-format-information-line))) + +(provide 'dired-hacks-utils) + +;;; dired-hacks-utils.el ends here -- cgit v1.2.3