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.

237 lines
8.6 KiB

  1. ;;; ibuffer-vc.el --- Group ibuffer's list by VC project, or show VC status -*- lexical-binding: t -*-
  2. ;;
  3. ;; Copyright (C) 2011-2014 Steve Purcell
  4. ;;
  5. ;; Author: Steve Purcell <steve@sanityinc.com>
  6. ;; Keywords: convenience
  7. ;; Package-Version: 0.11
  8. ;; Package-Commit: 1249c1e30cf11badfe032ac3b1058f24ba510ace
  9. ;; Package-Requires: ((emacs "24.1") (cl-lib "0.2"))
  10. ;; URL: http://github.com/purcell/ibuffer-vc
  11. ;; Version: DEV
  12. ;;
  13. ;; This program is free software; you can redistribute it and/or modify
  14. ;; it under the terms of the GNU General Public License as published by
  15. ;; the Free Software Foundation, either version 3 of the License, or
  16. ;; (at your option) any later version.
  17. ;;
  18. ;; This program is distributed in the hope that it will be useful,
  19. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. ;; GNU General Public License for more details.
  22. ;;
  23. ;; You should have received a copy of the GNU General Public License
  24. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. ;;
  26. ;;; Commentary:
  27. ;;
  28. ;; Adds functionality to ibuffer for grouping buffers by their parent
  29. ;; vc root directory, and for displaying and/or sorting by the vc
  30. ;; status of listed files.
  31. ;;
  32. ;;; Use:
  33. ;;
  34. ;; To group buffers by vc parent dir:
  35. ;;
  36. ;; M-x ibuffer-vc-set-filter-groups-by-vc-root
  37. ;;
  38. ;; or, make this the default:
  39. ;;
  40. ;; (add-hook 'ibuffer-hook
  41. ;; (lambda ()
  42. ;; (ibuffer-vc-set-filter-groups-by-vc-root)
  43. ;; (unless (eq ibuffer-sorting-mode 'alphabetic)
  44. ;; (ibuffer-do-sort-by-alphabetic))))
  45. ;;
  46. ;; Alternatively, use `ibuffer-vc-generate-filter-groups-by-vc-root'
  47. ;; to programmatically obtain a list of filter groups that you can
  48. ;; combine with your own custom groups.
  49. ;;
  50. ;; To include vc status info in the ibuffer list, add either
  51. ;; vc-status-mini or vc-status to `ibuffer-formats':
  52. ;;
  53. ;; (setq ibuffer-formats
  54. ;; '((mark modified read-only vc-status-mini " "
  55. ;; (name 18 18 :left :elide)
  56. ;; " "
  57. ;; (size 9 -1 :right)
  58. ;; " "
  59. ;; (mode 16 16 :left :elide)
  60. ;; " "
  61. ;; (vc-status 16 16 :left)
  62. ;; " "
  63. ;; vc-relative-file)))
  64. ;;
  65. ;; To sort by vc status, use `ibuffer-do-sort-by-vc-status', which can
  66. ;; also be selected by repeatedly executing
  67. ;; `ibuffer-toggle-sorting-mode' (bound to "," by default).
  68. ;;
  69. ;;; Code:
  70. ;; requires
  71. (require 'ibuffer)
  72. (require 'ibuf-ext)
  73. (require 'vc-hooks)
  74. (require 'cl-lib)
  75. (defgroup ibuffer-vc nil
  76. "Group ibuffer entries according to their version control status."
  77. :prefix "ibuffer-vc-"
  78. :group 'convenience)
  79. (defcustom ibuffer-vc-skip-if-remote t
  80. "If non-nil, don't query the VC status of remote files."
  81. :type 'boolean
  82. :group 'ibuffer-vc)
  83. (defcustom ibuffer-vc-include-function 'identity
  84. "A function which tells whether a given file should be grouped.
  85. The function is passed a filename, and should return non-nil if the file
  86. is to be grouped.
  87. This option can be used to exclude certain files from the grouping mechanism."
  88. :type 'function
  89. :group 'ibuffer-vc)
  90. ;;; Group and filter ibuffer entries by parent vc directory
  91. (defun ibuffer-vc--include-file-p (file)
  92. "Return t iff FILE should be included in ibuffer-vc's filtering."
  93. (and file
  94. (or (null ibuffer-vc-skip-if-remote)
  95. (not (file-remote-p file)))
  96. (funcall ibuffer-vc-include-function file)))
  97. (defun ibuffer-vc--deduce-backend (file)
  98. "Return the vc backend for FILE, or nil if not under VC supervision."
  99. (if (fboundp 'vc-responsible-backend)
  100. (ignore-errors (vc-responsible-backend file))
  101. (or (vc-backend file)
  102. (cl-loop for backend in vc-handled-backends
  103. when (vc-call-backend backend 'responsible-p file)
  104. return backend))))
  105. (defun ibuffer-vc-root (buf)
  106. "Return a cons cell (backend-name . root-dir) for BUF.
  107. If the file is not under version control, nil is returned instead."
  108. (let ((file-name (with-current-buffer buf
  109. (file-truename (or buffer-file-name
  110. default-directory)))))
  111. (when (ibuffer-vc--include-file-p file-name)
  112. (let ((backend (ibuffer-vc--deduce-backend file-name)))
  113. (when backend
  114. (let* ((root-fn-name (intern (format "vc-%s-root" (downcase (symbol-name backend)))))
  115. (root-dir
  116. (cond
  117. ((fboundp root-fn-name) (funcall root-fn-name file-name)) ; git, svn, hg, bzr (at least)
  118. ((memq backend '(darcs DARCS)) (vc-darcs-find-root file-name))
  119. ((memq backend '(cvs CVS)) (vc-find-root file-name "CVS"))
  120. ((memq backend '(rcs RCS)) (or (vc-find-root file-name "RCS")
  121. (concat file-name ",v")))
  122. ((memq backend '(src SRC)) (or (vc-find-root file-name ".src")
  123. (concat file-name ",v")))
  124. (t (error "ibuffer-vc: don't know how to find root for vc backend '%s' - please submit a bug report or patch" backend)))))
  125. (cons backend root-dir)))))))
  126. (defun ibuffer-vc-read-filter ()
  127. "Read a cons cell of (backend-name . root-dir)."
  128. (cons (car (read-from-string
  129. (completing-read "VC backend: " vc-handled-backends nil t)))
  130. (read-directory-name "Root directory: " nil nil t)))
  131. (define-ibuffer-filter vc-root
  132. "Toggle current view to buffers with vc root dir QUALIFIER."
  133. (:description "vc root dir"
  134. :reader (ibuffer-vc-read-filter))
  135. (ibuffer-awhen (ibuffer-vc-root buf)
  136. (equal qualifier it)))
  137. ;;;###autoload
  138. (defun ibuffer-vc-generate-filter-groups-by-vc-root ()
  139. "Create a set of ibuffer filter groups based on the vc root dirs of buffers."
  140. (let ((roots (ibuffer-remove-duplicates
  141. (delq nil (mapcar 'ibuffer-vc-root (buffer-list))))))
  142. (mapcar (lambda (vc-root)
  143. (cons (format "%s: %s" (car vc-root) (cdr vc-root))
  144. `((vc-root . ,vc-root))))
  145. roots)))
  146. ;;;###autoload
  147. (defun ibuffer-vc-set-filter-groups-by-vc-root ()
  148. "Set the current filter groups to filter by vc root dir."
  149. (interactive)
  150. (setq ibuffer-filter-groups (ibuffer-vc-generate-filter-groups-by-vc-root))
  151. (message "ibuffer-vc: groups set")
  152. (let ((ibuf (get-buffer "*Ibuffer*")))
  153. (when ibuf
  154. (with-current-buffer ibuf
  155. (pop-to-buffer ibuf)
  156. (ibuffer-update nil t)))))
  157. ;;; Display vc status info in the ibuffer list
  158. (defun ibuffer-vc--state (file)
  159. "Return the `vc-state' for FILE, or nil if unregistered."
  160. (ignore-errors (vc-state file)))
  161. (defun ibuffer-vc--status-string ()
  162. "Return a short string to represent the current buffer's status."
  163. (when (and buffer-file-name (ibuffer-vc--include-file-p buffer-file-name))
  164. (let ((state (ibuffer-vc--state buffer-file-name)))
  165. (if state
  166. (symbol-name state)
  167. "-"))))
  168. ;;;###autoload (autoload 'ibuffer-make-column-vc-status "ibuffer-vc")
  169. (define-ibuffer-column vc-status
  170. (:name "VC status")
  171. (ibuffer-vc--status-string))
  172. ;;;###autoload (autoload 'ibuffer-make-column-vc-relative-file "ibuffer-vc")
  173. (define-ibuffer-column vc-relative-file
  174. (:name "Filename")
  175. (if buffer-file-name
  176. (let ((root (cdr (ibuffer-vc-root buffer))))
  177. (if root
  178. (file-relative-name buffer-file-name root)
  179. (abbreviate-file-name buffer-file-name)))
  180. ""))
  181. ;;;###autoload (autoload 'ibuffer-make-column-vc-status-mini "ibuffer-vc")
  182. (define-ibuffer-column vc-status-mini
  183. (:name "V")
  184. (if (and buffer-file-name (ibuffer-vc--include-file-p buffer-file-name))
  185. (let ((state (ibuffer-vc--state buffer-file-name)))
  186. (cond
  187. ((eq 'added state) "A")
  188. ((eq 'removed state) "D")
  189. ((eq 'up-to-date state) "=")
  190. ((eq 'edited state) "*")
  191. ((eq 'needs-update state) "O")
  192. ((memq state '(conflict needs-merge unlocked-changes)) "!")
  193. ((eq 'ignored state) "I")
  194. ((memq state '(() unregistered missing)) "?")))
  195. " "))
  196. ;;;###autoload (autoload 'ibuffer-do-sort-by-vc-status "ibuffer-vc")
  197. (define-ibuffer-sorter vc-status
  198. "Sort the buffers by their vc status."
  199. (:description "vc status")
  200. (let ((file1 (with-current-buffer (car a)
  201. buffer-file-name))
  202. (file2 (with-current-buffer (car b)
  203. buffer-file-name)))
  204. (if (and file1 file2)
  205. (string-lessp (with-current-buffer (car a)
  206. (ibuffer-vc--status-string))
  207. (with-current-buffer (car b)
  208. (ibuffer-vc--status-string)))
  209. (not (null file1)))))
  210. (provide 'ibuffer-vc)
  211. ;;; ibuffer-vc.el ends here