Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
9.4 KiB

  1. ;;; dired-hacks-utils.el --- Utilities and helpers for dired-hacks collection
  2. ;; Copyright (C) 2014-2015 Matúš Goljer
  3. ;; Author: Matúš Goljer <matus.goljer@gmail.com>
  4. ;; Maintainer: Matúš Goljer <matus.goljer@gmail.com>
  5. ;; Keywords: files
  6. ;; Package-Version: 20201005.2318
  7. ;; Package-Commit: d6d4d1930969bbc22fd0551d5195887bf92cab3e
  8. ;; Version: 0.0.1
  9. ;; Created: 14th February 2014
  10. ;; Package-requires: ((dash "2.5.0"))
  11. ;; This program is free software; you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation, either version 3 of the License, or
  14. ;; (at your option) any later version.
  15. ;; This program is distributed in the hope that it will be useful,
  16. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;; GNU General Public License for more details.
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. ;;; Commentary:
  22. ;; Utilities and helpers for `dired-hacks' collection of dired
  23. ;; improvements.
  24. ;; This package also provides these interactive functions:
  25. ;; * `dired-hacks-next-file' - go to next file, skipping empty and non-file lines
  26. ;; * `dired-hacks-previous-file' - go to previous file, skipping empty
  27. ;; and non-file lines
  28. ;; * `dired-utils-format-information-line-mode' - Format the information
  29. ;; (summary) line file sizes to be human readable (e.g. 1GB instead of 1048576).
  30. ;; See https://github.com/Fuco1/dired-hacks for the entire collection
  31. ;;; Code:
  32. (require 'dash)
  33. (require 'dired)
  34. (defgroup dired-hacks ()
  35. "Collection of useful dired additions."
  36. :group 'dired
  37. :prefix "dired-hacks-")
  38. (defcustom dired-hacks-file-size-formatter 'file-size-human-readable
  39. "The function used to format file sizes.
  40. See `dired-utils-format-file-sizes'."
  41. :type 'symbol
  42. :group 'dired-hacks)
  43. (defcustom dired-hacks-datetime-regexp
  44. "\\sw\\sw\\sw....\\(?:[0-9][0-9]:[0-9][0-9]\\|.[0-9]\\{4\\}\\)"
  45. "A regexp matching the date/time in the dired listing.
  46. It is used to determine where the filename starts. It should
  47. *not* match any characters after the last character of the
  48. timestamp. It is assumed that the timestamp is preceded and
  49. followed by at least one space character. You should only use
  50. shy groups (prefixed with ?:) because the first group is used by
  51. the font-lock to determine what portion of the name should be
  52. colored."
  53. :type 'string
  54. :group 'dired-hacks)
  55. (defalias 'dired-utils--string-trim
  56. (if (and (require 'subr-x nil t)
  57. (fboundp 'string-trim))
  58. #'string-trim
  59. (lambda (string)
  60. (let ((s string))
  61. (when (string-match "\\`[ \t\n\r]+" s)
  62. (setq s (replace-match "" t t s)))
  63. (when (string-match "[ \t\n\r]+\\'" s)
  64. (setq s (replace-match "" t t s)))
  65. s)))
  66. "Trim STRING of trailing whitespace.
  67. \(fn STRING)")
  68. (defun dired-utils-get-filename (&optional localp)
  69. "Like `dired-get-filename' but never signal an error.
  70. Optional arg LOCALP with value `no-dir' means don't include
  71. directory name in result."
  72. (dired-get-filename localp t))
  73. (defun dired-utils-get-all-files (&optional localp)
  74. "Return all files in this dired buffer as a list.
  75. LOCALP has same semantics as in `dired-get-filename'."
  76. (save-excursion
  77. (goto-char (point-min))
  78. (let (r)
  79. (while (= 0 (forward-line))
  80. (--when-let (dired-utils-get-filename localp)
  81. (push it r)))
  82. (nreverse r))))
  83. (defconst dired-utils-file-attributes-keywords
  84. '(:isdir :nlinks :uid :gid :atime :mtime :ctime :size :modes :gidchg :inode :devnum)
  85. "List of keywords to map with `file-attributes'.")
  86. (defconst dired-utils-info-keywords
  87. `(:name :issym :target ,@dired-utils-file-attributes-keywords)
  88. "List of keywords available for `dired-utils-get-info'.")
  89. (defun dired-utils--get-keyword-info (keyword)
  90. "Get file information about KEYWORD."
  91. (let ((filename (dired-utils-get-filename)))
  92. (cl-case keyword
  93. (:name filename)
  94. (:isdir (file-directory-p filename))
  95. (:issym (and (file-symlink-p filename) t))
  96. (:target (file-symlink-p filename))
  97. (t
  98. (nth (-elem-index keyword dired-utils-file-attributes-keywords)
  99. (file-attributes filename))))))
  100. (defun dired-utils-get-info (&rest keywords)
  101. "Query for info about the file at point.
  102. KEYWORDS is a list of attributes to query.
  103. When querying for one attribute, its value is returned. When
  104. querying for more than one, a list of results is returned.
  105. The available keywords are listed in
  106. `dired-utils-info-keywords'."
  107. (let ((attributes (mapcar 'dired-utils--get-keyword-info keywords)))
  108. (if (> (length attributes) 1)
  109. attributes
  110. (car attributes))))
  111. (defun dired-utils-goto-line (filename)
  112. "Go to line describing FILENAME in listing.
  113. Should be absolute file name matched against
  114. `dired-get-filename'."
  115. (goto-char (point-min))
  116. (let (stop)
  117. (while (and (not stop)
  118. (= (forward-line) 0))
  119. (when (equal filename (dired-utils-get-filename))
  120. (setq stop t)
  121. (dired-move-to-filename)))
  122. stop))
  123. (defun dired-utils-match-filename-regexp (filename alist)
  124. "Match FILENAME against each car in ALIST and return first matched cons.
  125. Each car in ALIST is a regular expression.
  126. The matching is done using `string-match-p'."
  127. (let (match)
  128. (--each-while alist (not match)
  129. (when (string-match-p (car it) filename)
  130. (setq match it)))
  131. match))
  132. (defun dired-utils-match-filename-extension (filename alist)
  133. "Match FILENAME against each car in ALIST and return first matched cons.
  134. Each car in ALIST is a string representing file extension
  135. *without* the delimiting dot."
  136. (let (done)
  137. (--each-while alist (not done)
  138. (when (string-match-p (concat "\\." (regexp-quote (car it)) "\\'") filename)
  139. (setq done it)))
  140. done))
  141. (defun dired-utils-format-information-line ()
  142. "Format the disk space on the Dired information line."
  143. (save-excursion
  144. (goto-char (point-min))
  145. (forward-line)
  146. (let ((inhibit-read-only t)
  147. (limit (line-end-position)))
  148. (while (re-search-forward "\\(?:directory\\|available\\) \\(\\<[0-9]+$\\>\\)" nil t)
  149. (replace-match
  150. (save-match-data
  151. (propertize (dired-utils--string-trim
  152. (funcall dired-hacks-file-size-formatter
  153. (* 1024 (string-to-number (match-string 1))) t))
  154. 'invisible 'dired-hide-details-information))
  155. t nil nil 1)))))
  156. ;;; Predicates
  157. (defun dired-utils-is-file-p ()
  158. "Return non-nil if the line at point is a file or a directory."
  159. (dired-utils-get-filename 'no-dir))
  160. (defun dired-utils-is-dir-p ()
  161. "Return non-nil if the line at point is a directory."
  162. (--when-let (dired-utils-get-filename)
  163. (file-directory-p it)))
  164. ;;; Interactive
  165. ;; TODO: add wrap-around option
  166. (defun dired-hacks-next-file (&optional arg)
  167. "Move point to the next file.
  168. Optional prefix ARG says how many lines to move; default is one
  169. line."
  170. (interactive "p")
  171. (unless arg (setq arg 1))
  172. (if (< arg 0)
  173. (dired-hacks-previous-file (- arg))
  174. (--dotimes arg
  175. (forward-line)
  176. (while (and (or (not (dired-utils-is-file-p))
  177. (get-text-property (point) 'invisible))
  178. (= (forward-line) 0))))
  179. (if (not (= (point) (point-max)))
  180. (dired-move-to-filename)
  181. (forward-line -1)
  182. (dired-move-to-filename)
  183. nil)))
  184. (defun dired-hacks-previous-file (&optional arg)
  185. "Move point to the previous file.
  186. Optional prefix ARG says how many lines to move; default is one
  187. line."
  188. (interactive "p")
  189. (unless arg (setq arg 1))
  190. (if (< arg 0)
  191. (dired-hacks-next-file (- arg))
  192. (--dotimes arg
  193. (forward-line -1)
  194. (while (and (or (not (dired-utils-is-file-p))
  195. (get-text-property (point) 'invisible))
  196. (= (forward-line -1) 0))))
  197. (if (not (= (point) (point-min)))
  198. (dired-move-to-filename)
  199. (dired-hacks-next-file)
  200. nil)))
  201. (defun dired-hacks-compare-files (file-a file-b)
  202. "Test if two files FILE-A and FILE-B are the (probably) the same."
  203. (interactive (let ((other-dir (dired-dwim-target-directory)))
  204. (list (read-file-name "File A: " default-directory (car (dired-get-marked-files)) t)
  205. (read-file-name "File B: " other-dir (with-current-buffer (cdr (assoc other-dir dired-buffers))
  206. (car (dired-get-marked-files))) t))))
  207. (let ((md5-a (with-temp-buffer
  208. (shell-command (format "md5sum %s" file-a) (current-buffer))
  209. (buffer-string)))
  210. (md5-b (with-temp-buffer
  211. (shell-command (format "md5sum %s" file-b) (current-buffer))
  212. (buffer-string))))
  213. (message "%s%sFiles are %s." md5-a md5-b
  214. (if (equal (car (split-string md5-a))
  215. (car (split-string md5-b)))
  216. "probably the same" "different"))))
  217. (define-minor-mode dired-utils-format-information-line-mode
  218. "Toggle formatting of disk space in the Dired information line."
  219. :group 'dired-utils
  220. :lighter ""
  221. (if dired-utils-format-information-line-mode
  222. (add-hook 'dired-after-readin-hook #'dired-utils-format-information-line)
  223. (remove-hook 'dired-after-readin-hook #'dired-utils-format-information-line)))
  224. (provide 'dired-hacks-utils)
  225. ;;; dired-hacks-utils.el ends here