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.

270 lines
11 KiB

  1. ;;; pacfiles-mode.el --- pacnew and pacsave merging tool -*- lexical-binding: t; -*-
  2. ;;
  3. ;; Copyright (C) 2018 Carlos G. Cordero
  4. ;;
  5. ;; Author: Carlos G. Cordero <http://github/UndeadKernel>
  6. ;; Maintainer: Carlos G. Cordero <pacfiles@binarycharly.com>
  7. ;; Created: Oct 11, 2018
  8. ;; Modified: Oct 11, 2018
  9. ;; Version: 1.0
  10. ;; Keywords: files pacman arch pacnew pacsave update linux
  11. ;; URL: https://github.com/UndeadKernel/pacfiles-mode
  12. ;; Package-Requires: ((emacs "26") (cl-lib "0.5"))
  13. ;;
  14. ;; This file is not part of GNU Emacs.
  15. ;;
  16. ;;; Commentary:
  17. ;;
  18. ;; `pacfiles-mode' is an Emacs major mode to manage `.pacnew` and `.pacsave`
  19. ;; files left by Arch's pacman. To merge files, *pacfiles-mode* automatically
  20. ;; creates an Ediff merge session that a user can interact with. After finishing
  21. ;; the Ediff merge session, *pacfiles-mode* cleans up the mess that Ediff leaves
  22. ;; behind. *pacfiles-mode* also takes care of keeping the correct permissions of
  23. ;; merged files, and requests passwords (with TRAMP) to act as root when needed.
  24. ;;
  25. ;; Start the major mode using the command `pacfiles' or `pacfiles/start'.
  26. ;;
  27. ;;; Code:
  28. (require 'pacfiles-buttons)
  29. (require 'pacfiles-utils)
  30. (require 'pacfiles-win)
  31. (require 'cl-seq)
  32. (require 'ediff)
  33. (require 'outline)
  34. (require 'time-date)
  35. (defgroup pacfiles nil "Faces for the buttons used in pacfiles-mode."
  36. :group 'tools)
  37. (defvar pacfiles-updates-search-command "find /etc -name '*.pacnew' -o -name '*.pacsave' 2>/dev/null"
  38. "Command to find .pacnew files.")
  39. (defvar pacfiles--merge-search-command
  40. (concat "find " pacfiles-merge-file-tmp-location " -name '*.pacmerge' 2>/dev/null")
  41. "Command to search for temporarily merged files.")
  42. (defvar pacfiles--ediff-conf '()
  43. "Alist that stores ediff variables and its values.")
  44. ;;;###autoload
  45. (defalias 'pacfiles 'pacfiles-start)
  46. ;;;###autoload
  47. (defun pacfiles-start ()
  48. "Find and manage pacman backup files in an Arch-based GNU/Linux system."
  49. (interactive)
  50. ;; Save the current window configuration so that it can be restored when we are finished.
  51. (pacfiles--push-window-conf)
  52. ;; Save ediff varaibles that we modify to later restore them to the uers's value.
  53. (pacfiles--save-ediff-conf)
  54. (let ((buffer (get-buffer-create pacfiles--files-buffer-name)))
  55. (display-buffer buffer '(pacfiles--display-buffer-fullscreen))
  56. (with-current-buffer buffer
  57. (pacfiles-mode)
  58. (pacfiles-revert-buffer t t))))
  59. (defun pacfiles-quit ()
  60. "Quit ‘pacfiles-mode’ and restore the previous window and ediff configuration."
  61. (interactive)
  62. (pacfiles--restore-ediff-conf)
  63. ;; Kill buffers we create which start with '*pacfiles:'
  64. (kill-matching-buffers "^\\*pacfiles:.*" t t)
  65. (pacfiles--pop-window-conf))
  66. ;; Main function that displays the contents of the PACFILES buffer.
  67. (defun pacfiles-revert-buffer (&optional _ignore-auto noconfirm)
  68. "Populate the ‘pacfiles-mode’ buffer with .pacnew and .pacsave files.
  69. Ignore IGNORE-AUTO but take into account NOCONFIRM."
  70. (interactive)
  71. (with-current-buffer (get-buffer-create pacfiles--files-buffer-name)
  72. (when (or noconfirm
  73. (y-or-n-p (format "Reload list of backup pacman files? ")))
  74. (run-hooks 'before-revert-hook)
  75. ;; The actual revert mechanism starts here
  76. (let ((inhibit-read-only t)
  77. (files (split-string (shell-command-to-string pacfiles-updates-search-command) "\n" t))
  78. (merged-files (split-string (shell-command-to-string pacfiles--merge-search-command) "\n" t))
  79. (pacnew-alist (list))
  80. (pacsave-alist (list)))
  81. (delete-region (point-min) (point-max))
  82. (insert "* PACFILES MODE" "\n")
  83. ;; Split .pacnew and .pacsave files
  84. (dolist (file files)
  85. ;; Associate each FILE in FILES with a file to hold the merge
  86. (let ((merge-file (pacfiles--calculate-merge-file file pacfiles-merge-file-tmp-location)))
  87. (cond
  88. ((string-match-p ".pacnew" file)
  89. (push (cons file merge-file) pacnew-alist))
  90. ((string-match-p ".pacsave" file)
  91. (push (cons file merge-file) pacsave-alist))
  92. (t (user-error (format "Cannot process file %s" file))))))
  93. ;; --- Process .pacnew files ---
  94. (insert "\n\n" "** PACNEW files" "\n")
  95. (insert "\n" "*** pending" "\n")
  96. ;; Display the .pacnew files that need merging
  97. (pacfiles--insert-pending-files pacnew-alist merged-files)
  98. (insert "\n" "*** merged" "\n")
  99. ;; Display .pacnew files that have an associated merge file.
  100. (pacfiles--insert-merged-files pacnew-alist merged-files)
  101. ;; --- Process .pacsave files ---
  102. (insert "\n\n" "** PACSAVE files" "\n")
  103. (insert "\n" "*** pending" "\n")
  104. ;; Display the .pacsave files that need merging
  105. (pacfiles--insert-pending-files pacsave-alist merged-files)
  106. (insert "\n" "*** merged" "\n")
  107. (pacfiles--insert-merged-files pacsave-alist merged-files)
  108. (insert "\n\n")
  109. (pacfiles--insert-footer-buttons))))
  110. (goto-char 0))
  111. ;;;###autoload
  112. (defun pacfiles-revert-buffer-no-confirm ()
  113. "Revert the pacfiles list buffer without asking for confirmation."
  114. (interactive)
  115. (pacfiles-revert-buffer t t))
  116. (defun pacfiles--insert-pending-files (files-alist merged-files)
  117. "Insert files in FILES-ALIST if their `cdr' is not in MERGED-FILES.
  118. The FILE-TYPE specifies which type of update file we are processing."
  119. ;; Keep files in FILES-ALIST which don't have a cdr in MERGED-FILES.
  120. (let ((pending-alist (cl-remove-if (lambda (i) (member (cdr i) merged-files)) files-alist)))
  121. (if (null pending-alist)
  122. (insert (propertize "--- no pending files ---\n" 'font-lock-face 'font-lock-comment-face))
  123. (dolist (file-pair pending-alist)
  124. (pacfiles--insert-merge-button file-pair)
  125. (pacfiles--insert-diff-button (car file-pair))
  126. (pacfiles--insert-delete-button file-pair)
  127. (insert (car file-pair) " ")
  128. (pacfiles--insert-days-old (car file-pair))
  129. (insert "\n")))))
  130. (defun pacfiles--insert-merged-files (files-alist merged-files)
  131. "Insert files in FILES-ALIST that have an associated file in MERGED-FILES."
  132. (let ((merged-alist (cl-remove-if-not (lambda (i) (member (cdr i) merged-files)) files-alist)))
  133. (if (null merged-alist)
  134. (insert (propertize "--- no merged files ---\n" 'font-lock-face 'font-lock-comment-face))
  135. (dolist (file-pair merged-alist)
  136. (pacfiles--insert-apply-button file-pair)
  137. (pacfiles--insert-view-merge-button file-pair)
  138. (pacfiles--insert-discard-button file-pair)
  139. (insert (car file-pair) " ")
  140. ;; calculate how many days old is the merged file
  141. (pacfiles--insert-days-created (cdr file-pair))
  142. (insert "\n")))))
  143. (defun pacfiles--insert-days-old (file)
  144. "Insert how many days passed between FILE and FILE without its extension.
  145. If REVERSE-ORDER is non-nil, calculate the time difference as
  146. \(FILE without extension\) - FILE."
  147. (let* ((base-file (file-name-sans-extension file))
  148. (time-base-file
  149. (time-to-seconds (file-attribute-modification-time (file-attributes base-file))))
  150. (time-file
  151. (time-to-seconds (file-attribute-modification-time (file-attributes file))))
  152. (reverse-time (< time-file time-base-file)))
  153. (when (file-exists-p base-file)
  154. (insert
  155. (propertize
  156. (format "(%.1f %s)"
  157. (time-to-number-of-days
  158. (cond (reverse-time (time-subtract time-base-file time-file))
  159. (t (time-subtract time-file time-base-file))))
  160. (if reverse-time "day[s] old" "day[s] ahead"))
  161. 'font-lock-face 'font-lock-warning-face)))))
  162. (defun pacfiles--insert-days-created (file)
  163. "Insert the number of days since FILE was created."
  164. (if (file-exists-p file)
  165. (let ((time-file (file-attribute-modification-time (file-attributes file))))
  166. (insert
  167. (propertize
  168. (format "(%.1f day[s] since created)" (time-to-number-of-days (time-since time-file)))
  169. 'font-lock-face 'font-lock-string-face)))
  170. (error "File '%s' dosn't exist" file)))
  171. (defun pacfiles--save-ediff-conf ()
  172. "Save ediff variables we modify with the user's current values.
  173. We restore the saved variables after pacfiles-mode quits."
  174. (require 'ediff)
  175. (let ((vars-to-save
  176. '(ediff-autostore-merges ediff-keep-variants ediff-window-setup-function
  177. ediff-before-setup-hook ediff-quit-hook ediff-cleanup-hook ediff-quit-merge-hook
  178. ediff-quit-hook ediff-split-window-function)))
  179. (dolist (var vars-to-save)
  180. (push (pacfiles--var-to-cons var) pacfiles--ediff-conf))))
  181. (defun pacfiles--change-ediff-conf ()
  182. "Change ediff's configuration variables to fit ‘pacfiles-mode’."
  183. (setq ediff-autostore-merges nil
  184. ediff-keep-variants t
  185. ediff-window-setup-function #'ediff-setup-windows-plain
  186. ediff-split-window-function #'split-window-horizontally)
  187. (add-hook 'ediff-before-setup-hook #'pacfiles--push-window-conf)
  188. (add-hook 'ediff-quit-hook #'pacfiles--pop-window-conf t)
  189. (add-hook 'ediff-cleanup-hook #'pacfiles--clean-after-ediff)
  190. (remove-hook 'ediff-quit-merge-hook #'ediff-maybe-save-and-delete-merge)
  191. (add-hook 'ediff-quit-hook (lambda () (pacfiles-revert-buffer t t))))
  192. (defun pacfiles--restore-ediff-conf ()
  193. "Restore the ediff variables saved by `pacfiles--save-ediff-conf'."
  194. (dolist (pair pacfiles--ediff-conf)
  195. (pacfiles--cons-to-var pair))
  196. (setq pacfiles--ediff-conf '()))
  197. (defvar pacfiles-mode-map
  198. (let ((map (make-sparse-keymap)))
  199. (define-key map (kbd "q") #'pacfiles-quit)
  200. (define-key map (kbd "g") #'pacfiles-revert-buffer-no-confirm)
  201. (define-key map (kbd "r") #'pacfiles-revert-buffer-no-confirm)
  202. (define-key map (kbd "TAB") #'outline-toggle-children)
  203. (define-key map (kbd "C-c C-p") #'outline-previous-heading)
  204. (define-key map (kbd "C-c C-n") #'outline-next-heading)
  205. (define-key map (kbd "n") #'forward-button)
  206. (define-key map (kbd "p") #'backward-button)
  207. map)
  208. "Keymap for ‘pacfiles-mode’.")
  209. ;; Tell emacs that, when creating new buffers, pacfiles-mode should not be used
  210. ;; ... as the major mode.
  211. (put 'pacfiles-mode 'mode-class 'special)
  212. ;;;###autoload
  213. (define-derived-mode pacfiles-mode outline-mode "pacfiles"
  214. :syntax-table nil
  215. :abbrev-table nil
  216. "Major mode for managing .pacnew and .pacsave files."
  217. ;; If the buffer is not the one we create, do nothing and error out.
  218. (unless (string= (buffer-name) pacfiles--files-buffer-name)
  219. (user-error "Use the command `pacfiles' instead of `pacfiles-mode' to start pacfiles-mode"))
  220. ;; The buffer shall not be edited.
  221. (read-only-mode)
  222. ;; No edits... no undo.
  223. (buffer-disable-undo)
  224. ;; Disable showing parents locally by letting the mode think it's disabled.
  225. (setq-local show-paren-mode nil)
  226. (setq show-trailing-whitespace nil)
  227. ;; Disable lines numbers.
  228. (when (bound-and-true-p global-linum-mode)
  229. (linum-mode -1))
  230. (when (and (fboundp 'nlinum-mode)
  231. (bound-and-true-p global-nlinum-mode))
  232. (nlinum-mode -1))
  233. (when (and (fboundp 'display-line-numbers-mode)
  234. (bound-and-true-p global-display-line-numbers-mode))
  235. (display-line-numbers-mode -1))
  236. ;; Set the function used when reverting pacfile-mode buffers.
  237. (setq-local revert-buffer-function #'pacfiles-revert-buffer)
  238. ;; configure ediff
  239. (pacfiles--change-ediff-conf)
  240. ;; configure outline-mode
  241. (setq-local outline-blank-line t))
  242. (provide 'pacfiles-mode)
  243. ;;; pacfiles-mode.el ends here