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.

234 lines
8.4 KiB

  1. ;;; paradox-commit-list.el --- listing commits for a package's repository -*- lexical-binding:t -*-
  2. ;; Copyright (C) 2014-2015 Artur Malabarba <bruce.connor.am@gmail.com>
  3. ;; Author: Artur Malabarba <bruce.connor.am@gmail.com>
  4. ;; Prefix: paradox
  5. ;; Separator: -
  6. ;;; License:
  7. ;;
  8. ;; This file is NOT part of GNU Emacs.
  9. ;;
  10. ;; This program is free software; you can redistribute it and/or
  11. ;; modify it under the terms of the GNU General Public License
  12. ;; as published by the Free Software Foundation; either version 2
  13. ;; of the License, or (at your option) any later version.
  14. ;;
  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. ;;
  20. ;;; Code:
  21. (require 'subr-x)
  22. (require 'cl-lib)
  23. (require 'package)
  24. (require 'paradox-github)
  25. (defgroup paradox-commit-list nil
  26. "Buffer used by paradox to list commits for a package."
  27. :prefix "paradox-"
  28. :package-version '(paradox . "2.0")
  29. :group 'paradox)
  30. ;;; Variables
  31. (defcustom paradox-commit-list-query-max-pages 1
  32. "Max number of pages we read from github when fetching the commit-list.
  33. Each page lists 100 commits, so 1 page should be more than enough
  34. for most repositories.
  35. Increasing this number consequently multiplies the time it takes
  36. to load the commit list on repos which actually use that many
  37. pages."
  38. :type 'integer
  39. :group 'paradox-commit-list
  40. :package-version '(paradox . "1.2.3"))
  41. (defcustom paradox-date-format "%Y-%m-%d"
  42. "Format used for the date displayed on the commit list.
  43. See `format-time-string' for more information.
  44. Set it to \"%x\" for a more \"human\" date format."
  45. :type 'string
  46. :group 'paradox-commit-list
  47. :package-version '(paradox . "1.2.3"))
  48. (defface paradox-commit-tag-face
  49. '((t :foreground "goldenrod4"
  50. :background "LemonChiffon1"
  51. :box 1))
  52. "Face used for tags on the commit list."
  53. :group 'paradox-commit-list)
  54. ;;; Variables
  55. (defvar paradox--commit-message-face nil
  56. "Face currently being used on commit messages.
  57. Gets dynamically changed to `font-lock-comment-face' on old commits.
  58. nil means `default'.")
  59. (defvar-local paradox--package-repo nil
  60. "Repo of the package in a commit-list buffer.")
  61. (defvar-local paradox--package-name nil
  62. "Name of the package in a commit-list buffer.")
  63. (defvar-local paradox--package-version nil
  64. "Installed version of the package in a commit-list buffer.")
  65. (defvar-local paradox--package-tag-commit-alist nil
  66. "Alist of (COMMIT-SHA . TAG) for this package's repo.")
  67. ;;; Functions
  68. (defun paradox--get-tag-commit-alist (repo)
  69. "Get REPO's tag list and associate them to commit hashes."
  70. (require 'json)
  71. (mapcar
  72. (lambda (x)
  73. (cons
  74. (cdr (assoc 'sha (cdr (assoc 'commit x))))
  75. (cdr (assoc 'name x))))
  76. (let ((json-array-type 'list))
  77. (paradox--github-action
  78. (format "repos/%s/tags?per_page=100" repo)
  79. :reader #'json-read
  80. :max-pages paradox-commit-list-query-max-pages))))
  81. (defun paradox--get-installed-version (pkg)
  82. "Return the installed version of PKG.
  83. - If PKG isn't installed, return '(0).
  84. - If it has a Melpa-like version (YYYYMMDD HHMM), return it as a
  85. time value.
  86. - If it has a regular version number, return it as a string."
  87. (let ((desc (cadr (assoc pkg package-alist))))
  88. (if desc
  89. (let ((version (package-desc-version desc)))
  90. (if (> (car version) 19000000)
  91. (date-to-time
  92. (format "%8dT%02d:%02d"
  93. (car version)
  94. (/ (cadr version) 100)
  95. (% (cadr version) 100)))
  96. ;; Regular version numbers.
  97. (mapconcat 'int-to-string version ".")))
  98. '(0 0))))
  99. (defun paradox--commit-tabulated-list (repo)
  100. "Return the tabulated list for REPO's commit list."
  101. (require 'json)
  102. (let* ((paradox--commit-message-face nil)
  103. (json-array-type 'list)
  104. (feed (paradox--github-action
  105. (format "repos/%s/commits?per_page=100" repo)
  106. :reader #'json-read
  107. :max-pages paradox-commit-list-query-max-pages)))
  108. (apply 'append (mapcar 'paradox--commit-print-info feed))))
  109. (defun paradox--commit-print-info (x)
  110. "Parse json in X into a tabulated list entry."
  111. (let* ((commit (cdr (assoc 'commit x)))
  112. (date (date-to-time (cdr (assoc 'date (cdr (assoc 'committer commit))))))
  113. (title (split-string (cdr (assoc 'message commit)) "[\n\r][ \t]*" t))
  114. ;; (url (cdr (assoc 'html_url commit)))
  115. (cc (cdr (assoc 'comment_count commit)))
  116. (sha (cdr (assoc 'sha x)))
  117. (tag (cdr (assoc-string sha paradox--package-tag-commit-alist))))
  118. ;; Have we already crossed the installed commit, or is it not even installed?
  119. (unless (or paradox--commit-message-face
  120. (equal '(0) paradox--package-version))
  121. ;; Is this where we cross to old commits?
  122. (when (paradox--version<= date tag)
  123. (setq paradox--commit-message-face 'paradox-comment-face)))
  124. ;; Return the tabulated list entry.
  125. (cons
  126. ;; The ID
  127. (list `((is-old . ,(null paradox--commit-message-face))
  128. (lisp-date . ,date)
  129. ,@x)
  130. ;; The actual displayed data
  131. (vector
  132. (propertize (format-time-string paradox-date-format date)
  133. 'button t
  134. 'follow-link t
  135. 'action 'paradox-commit-list-visit-commit
  136. 'face (or paradox--commit-message-face 'link))
  137. (concat (if (> cc 0)
  138. (propertize (format "(%s comments) " cc)
  139. 'face 'font-lock-function-name-face)
  140. "")
  141. (if (stringp tag)
  142. (propertize tag 'face 'paradox-commit-tag-face)
  143. "")
  144. (if (stringp tag) " " "")
  145. (propertize (or (car-safe title) "")
  146. 'face paradox--commit-message-face))))
  147. (mapcar (lambda (m) (list x (vector "" (propertize m 'face paradox--commit-message-face))))
  148. (cdr title)))))
  149. (defun paradox--version<= (date version)
  150. "Non-nil if commit at DATE tagged with VERSION is older or equal to `paradox--package-version'."
  151. ;; Melpa date-like versions
  152. (if (listp paradox--package-version)
  153. ;; Installed date >= to commit date
  154. (null (time-less-p paradox--package-version date))
  155. ;; Regular version numbers.
  156. (and version
  157. (ignore-errors (version<= version paradox--package-version)))))
  158. (defun paradox--commit-list-update-entries ()
  159. "Update entries of current commit-list."
  160. (setq tabulated-list-entries
  161. (paradox--commit-tabulated-list paradox--package-repo)))
  162. ;;; Commands
  163. (defun paradox-commit-list-visit-commit (&optional _)
  164. "Visit this commit on GitHub.
  165. IGNORE is ignored."
  166. (interactive)
  167. (when (derived-mode-p 'paradox-commit-list-mode)
  168. (browse-url (cdr (assoc 'html_url (tabulated-list-get-id))))))
  169. (defun paradox-previous-commit (&optional n)
  170. "Move to previous commit, which might not be the previous line.
  171. With prefix N, move to the N-th previous commit."
  172. (interactive "p")
  173. (paradox-next-commit (- n)))
  174. (defun paradox-next-commit (&optional n)
  175. "Move to next commit, which might not be the next line.
  176. With prefix N, move to the N-th next commit."
  177. (interactive "p")
  178. (dotimes (_ (abs n))
  179. (let ((d (cl-signum n)))
  180. (forward-line d)
  181. (while (looking-at " +")
  182. (forward-line d)))))
  183. ;;; Mode definition
  184. (define-derived-mode paradox-commit-list-mode
  185. tabulated-list-mode "Paradox Commit List"
  186. "Major mode for browsing a list of commits.
  187. Letters do not insert themselves; instead, they are commands.
  188. \\<paradox-commit-list-mode-map>
  189. \\{paradox-commit-list-mode-map}"
  190. (hl-line-mode 1)
  191. (setq tabulated-list-format
  192. `[("Date" ,(length (format-time-string paradox-date-format (current-time))) nil)
  193. ("Message" 0 nil)])
  194. (setq tabulated-list-padding 1)
  195. (setq tabulated-list-sort-key nil)
  196. (add-hook 'tabulated-list-revert-hook 'paradox--commit-list-update-entries nil t)
  197. (tabulated-list-init-header))
  198. (define-key paradox-commit-list-mode-map " " #'paradox-commit-list-visit-commit)
  199. (define-key paradox-commit-list-mode-map "p" #'paradox-previous-commit)
  200. (define-key paradox-commit-list-mode-map "n" #'paradox-next-commit)
  201. (provide 'paradox-commit-list)
  202. ;;; paradox-commit-list.el ends here.