guix-build-log.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. ;;; guix-build-log.el --- Major and minor modes for build logs -*- lexical-binding: t -*-
  2. ;; Copyright © 2015 Alex Kost <alezost@gmail.com>
  3. ;; This file is part of Emacs-Guix.
  4. ;; Emacs-Guix is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; Emacs-Guix is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with Emacs-Guix. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This file provides a major mode (`guix-build-log-mode') and a minor mode
  18. ;; (`guix-build-log-minor-mode') for highlighting Guix build logs.
  19. ;;; Code:
  20. (require 'guix nil t)
  21. (require 'guix-utils)
  22. (defgroup guix-build-log nil
  23. "Settings for `guix-build-log-mode'."
  24. :group 'guix)
  25. (defgroup guix-build-log-faces nil
  26. "Faces for `guix-build-log-mode'."
  27. :group 'guix-build-log
  28. :group 'guix-faces)
  29. (defface guix-build-log-title-head
  30. '((t :inherit font-lock-keyword-face))
  31. "Face for '@' symbol of a log title."
  32. :group 'guix-build-log-faces)
  33. (defface guix-build-log-title-start
  34. '((t :inherit guix-build-log-title-head))
  35. "Face for a log title denoting a start of a process."
  36. :group 'guix-build-log-faces)
  37. (defface guix-build-log-title-success
  38. '((t :inherit guix-build-log-title-head))
  39. "Face for a log title denoting a successful end of a process."
  40. :group 'guix-build-log-faces)
  41. (defface guix-build-log-title-fail
  42. '((t :inherit error))
  43. "Face for a log title denoting a failed end of a process."
  44. :group 'guix-build-log-faces)
  45. (defface guix-build-log-title-end
  46. '((t :inherit guix-build-log-title-head))
  47. "Face for a log title denoting an undefined end of a process."
  48. :group 'guix-build-log-faces)
  49. (defface guix-build-log-phase-name
  50. '((t :inherit font-lock-function-name-face))
  51. "Face for a phase name."
  52. :group 'guix-build-log-faces)
  53. (defface guix-build-log-phase-start
  54. '((default :weight bold)
  55. (((class grayscale) (background light)) :foreground "Gray90")
  56. (((class grayscale) (background dark)) :foreground "DimGray")
  57. (((class color) (min-colors 16) (background light))
  58. :foreground "DarkGreen")
  59. (((class color) (min-colors 16) (background dark))
  60. :foreground "LimeGreen")
  61. (((class color) (min-colors 8)) :foreground "green"))
  62. "Face for the start line of a phase."
  63. :group 'guix-build-log-faces)
  64. (defface guix-build-log-phase-end
  65. '((((class grayscale) (background light)) :foreground "Gray90")
  66. (((class grayscale) (background dark)) :foreground "DimGray")
  67. (((class color) (min-colors 16) (background light))
  68. :foreground "ForestGreen")
  69. (((class color) (min-colors 16) (background dark))
  70. :foreground "LightGreen")
  71. (((class color) (min-colors 8)) :foreground "green")
  72. (t :weight bold))
  73. "Face for the end line of a phase."
  74. :group 'guix-build-log-faces)
  75. (defface guix-build-log-phase-success
  76. '((t))
  77. "Face for the 'succeeded' word of a phase line."
  78. :group 'guix-build-log-faces)
  79. (defface guix-build-log-phase-fail
  80. '((t :inherit error))
  81. "Face for the 'failed' word of a phase line."
  82. :group 'guix-build-log-faces)
  83. (defface guix-build-log-phase-seconds
  84. '((t :inherit font-lock-constant-face))
  85. "Face for the number of seconds for a phase."
  86. :group 'guix-build-log-faces)
  87. (defcustom guix-build-log-mode-hook '()
  88. "Hook run after `guix-build-log-mode' is entered."
  89. :type 'hook
  90. :group 'guix-build-log)
  91. (defvar guix-build-log-phase-name-regexp "`\\([^']+\\)'"
  92. "Regexp for a phase name.")
  93. (defvar guix-build-log-phase-start-regexp
  94. (concat "^starting phase " guix-build-log-phase-name-regexp)
  95. "Regexp for the start line of a 'build' phase.")
  96. (defun guix-build-log-title-regexp (&optional state)
  97. "Return regexp for the log title.
  98. STATE is a symbol denoting a state of the title. It should be
  99. `start', `fail', `success' or `nil' (for a regexp matching any
  100. state)."
  101. (let* ((word-rx (rx (1+ (any word "-"))))
  102. (state-rx (cond ((eq state 'start) (concat word-rx "started"))
  103. ((eq state 'success) (concat word-rx "succeeded"))
  104. ((eq state 'fail) (concat word-rx "failed"))
  105. (t word-rx))))
  106. (rx-to-string
  107. `(and bol (group "@") " " (group (regexp ,state-rx)))
  108. t)))
  109. (defun guix-build-log-phase-end-regexp (&optional state)
  110. "Return regexp for the end line of a 'build' phase.
  111. STATE is a symbol denoting how a build phase was ended. It should be
  112. `fail', `success' or `nil' (for a regexp matching any state)."
  113. (let ((state-rx (cond ((eq state 'success) "succeeded")
  114. ((eq state 'fail) "failed")
  115. (t (regexp-opt '("succeeded" "failed"))))))
  116. (rx-to-string
  117. `(and bol "phase " (regexp ,guix-build-log-phase-name-regexp)
  118. " " (group (regexp ,state-rx)) " after "
  119. (group (1+ (or digit "."))) " seconds")
  120. t)))
  121. (defvar guix-build-log-phase-end-regexp
  122. ;; For efficiency, it is better to have a regexp for the general line
  123. ;; of the phase end, then to call the function all the time.
  124. (guix-build-log-phase-end-regexp)
  125. "Regexp for the end line of a 'build' phase.")
  126. (defvar guix-build-log-font-lock-keywords
  127. `((,(guix-build-log-title-regexp 'start)
  128. (1 'guix-build-log-title-head)
  129. (2 'guix-build-log-title-start))
  130. (,(guix-build-log-title-regexp 'success)
  131. (1 'guix-build-log-title-head)
  132. (2 'guix-build-log-title-success))
  133. (,(guix-build-log-title-regexp 'fail)
  134. (1 'guix-build-log-title-head)
  135. (2 'guix-build-log-title-fail))
  136. (,(guix-build-log-title-regexp)
  137. (1 'guix-build-log-title-head)
  138. (2 'guix-build-log-title-end))
  139. (,guix-build-log-phase-start-regexp
  140. (0 'guix-build-log-phase-start)
  141. (1 'guix-build-log-phase-name prepend))
  142. (,(guix-build-log-phase-end-regexp 'success)
  143. (0 'guix-build-log-phase-end)
  144. (1 'guix-build-log-phase-name prepend)
  145. (2 'guix-build-log-phase-success prepend)
  146. (3 'guix-build-log-phase-seconds prepend))
  147. (,(guix-build-log-phase-end-regexp 'fail)
  148. (0 'guix-build-log-phase-end)
  149. (1 'guix-build-log-phase-name prepend)
  150. (2 'guix-build-log-phase-fail prepend)
  151. (3 'guix-build-log-phase-seconds prepend)))
  152. "A list of `font-lock-keywords' for `guix-build-log-mode'.")
  153. (defvar guix-build-log-common-map
  154. (let ((map (make-sparse-keymap)))
  155. (define-key map (kbd "M-n") 'guix-build-log-next-phase)
  156. (define-key map (kbd "M-p") 'guix-build-log-previous-phase)
  157. (define-key map (kbd "TAB") 'guix-build-log-phase-toggle)
  158. (define-key map (kbd "<tab>") 'guix-build-log-phase-toggle)
  159. (define-key map (kbd "<backtab>") 'guix-build-log-phase-toggle-all)
  160. (define-key map [(shift tab)] 'guix-build-log-phase-toggle-all)
  161. map)
  162. "Parent keymap for 'build-log' buffers.
  163. For `guix-build-log-mode' this map is used as is.
  164. For `guix-build-log-minor-mode' this map is prefixed with 'C-c'.")
  165. (defvar guix-build-log-mode-map
  166. (let ((map (make-sparse-keymap)))
  167. (set-keymap-parent
  168. map (make-composed-keymap (list guix-build-log-common-map)
  169. special-mode-map))
  170. (define-key map (kbd "c") 'compilation-shell-minor-mode)
  171. (define-key map (kbd "v") 'view-mode)
  172. map)
  173. "Keymap for `guix-build-log-mode' buffers.")
  174. (defvar guix-build-log-minor-mode-map
  175. (let ((map (make-sparse-keymap)))
  176. (define-key map (kbd "C-c") guix-build-log-common-map)
  177. map)
  178. "Keymap for `guix-build-log-minor-mode' buffers.")
  179. (defun guix-build-log-phase-start (&optional with-header?)
  180. "Return the start point of the current build phase.
  181. If WITH-HEADER? is non-nil, do not skip 'starting phase ...' header.
  182. Return nil, if there is no phase start before the current point."
  183. (save-excursion
  184. (end-of-line)
  185. (when (re-search-backward guix-build-log-phase-start-regexp nil t)
  186. (unless with-header? (end-of-line))
  187. (point))))
  188. (defun guix-build-log-phase-end ()
  189. "Return the end point of the current build phase."
  190. (save-excursion
  191. (beginning-of-line)
  192. (when (re-search-forward guix-build-log-phase-end-regexp nil t)
  193. (point))))
  194. (defun guix-build-log-phase-hide ()
  195. "Hide the body of the current build phase."
  196. (interactive)
  197. (let ((beg (guix-build-log-phase-start))
  198. (end (guix-build-log-phase-end)))
  199. (when (and beg end)
  200. ;; If not on the header line, move to it.
  201. (when (and (> (point) beg)
  202. (< (point) end))
  203. (goto-char (guix-build-log-phase-start t)))
  204. (remove-overlays beg end 'invisible t)
  205. (let ((o (make-overlay beg end)))
  206. (overlay-put o 'evaporate t)
  207. (overlay-put o 'invisible t)))))
  208. (defun guix-build-log-phase-show ()
  209. "Show the body of the current build phase."
  210. (interactive)
  211. (let ((beg (guix-build-log-phase-start))
  212. (end (guix-build-log-phase-end)))
  213. (when (and beg end)
  214. (remove-overlays beg end 'invisible t))))
  215. (defun guix-build-log-phase-hidden-p ()
  216. "Return non-nil, if the body of the current build phase is hidden."
  217. (let ((beg (guix-build-log-phase-start)))
  218. (and beg
  219. (cl-some (lambda (o)
  220. (overlay-get o 'invisible))
  221. (overlays-at beg)))))
  222. (defun guix-build-log-phase-toggle-function ()
  223. "Return a function to toggle the body of the current build phase."
  224. (if (guix-build-log-phase-hidden-p)
  225. #'guix-build-log-phase-show
  226. #'guix-build-log-phase-hide))
  227. (defun guix-build-log-phase-toggle ()
  228. "Show/hide the body of the current build phase."
  229. (interactive)
  230. (funcall (guix-build-log-phase-toggle-function)))
  231. (defun guix-build-log-phase-toggle-all ()
  232. "Show/hide the bodies of all build phases."
  233. (interactive)
  234. (save-excursion
  235. ;; Some phases may be hidden, and some shown. Whether to hide or to
  236. ;; show them, it is determined by the state of the first phase here.
  237. (goto-char (point-min))
  238. (let ((fun (save-excursion
  239. (re-search-forward guix-build-log-phase-start-regexp nil t)
  240. (guix-build-log-phase-toggle-function))))
  241. (while (re-search-forward guix-build-log-phase-start-regexp nil t)
  242. (funcall fun)))))
  243. (defun guix-build-log-next-phase (&optional arg)
  244. "Move to the next build phase.
  245. With ARG, do it that many times. Negative ARG means move
  246. backward."
  247. (interactive "^p")
  248. (if arg
  249. (when (zerop arg) (user-error "Try again"))
  250. (setq arg 1))
  251. (let ((search-fun (if (> arg 0)
  252. #'re-search-forward
  253. #'re-search-backward))
  254. (n (abs arg))
  255. found last-found)
  256. (save-excursion
  257. (end-of-line (if (> arg 0) 1 0)) ; skip the current line
  258. (while (and (not (zerop n))
  259. (setq found
  260. (funcall search-fun
  261. guix-build-log-phase-start-regexp
  262. nil t)))
  263. (setq n (1- n)
  264. last-found found)))
  265. (when last-found
  266. (goto-char last-found)
  267. (forward-line 0))
  268. (or found
  269. (user-error (if (> arg 0)
  270. "No next build phase"
  271. "No previous build phase")))))
  272. (defun guix-build-log-previous-phase (&optional arg)
  273. "Move to the previous build phase.
  274. With ARG, do it that many times. Negative ARG means move
  275. forward."
  276. (interactive "^p")
  277. (guix-build-log-next-phase (- (or arg 1))))
  278. ;;;###autoload
  279. (define-derived-mode guix-build-log-mode special-mode
  280. "Guix-Build-Log"
  281. "Major mode for viewing Guix build logs.
  282. \\{guix-build-log-mode-map}"
  283. (setq font-lock-defaults '(guix-build-log-font-lock-keywords t)))
  284. ;;;###autoload
  285. (define-minor-mode guix-build-log-minor-mode
  286. "Toggle Guix Build Log minor mode.
  287. With a prefix argument ARG, enable Guix Build Log minor mode if
  288. ARG is positive, and disable it otherwise. If called from Lisp,
  289. enable the mode if ARG is omitted or nil.
  290. When Guix Build Log minor mode is enabled, it highlights build
  291. log in the current buffer. This mode can be enabled
  292. programmatically using hooks, like this:
  293. (add-hook 'shell-mode-hook 'guix-build-log-minor-mode)
  294. \\{guix-build-log-minor-mode-map}"
  295. :init-value nil
  296. :lighter " Guix-Build-Log"
  297. :keymap guix-build-log-minor-mode-map
  298. :group 'guix-build-log
  299. (if guix-build-log-minor-mode
  300. (font-lock-add-keywords nil guix-build-log-font-lock-keywords)
  301. (font-lock-remove-keywords nil guix-build-log-font-lock-keywords))
  302. (guix-font-lock-flush))
  303. (defun guix-build-log-find-file (file-or-url)
  304. "Open FILE-OR-URL in `guix-build-log-mode'."
  305. (guix-find-file-or-url file-or-url)
  306. (guix-build-log-mode))
  307. ;;;###autoload
  308. (add-to-list 'auto-mode-alist
  309. ;; Regexp for log files (usually placed in /var/log/guix/...)
  310. (cons (rx "/guix/drvs/" (= 2 alnum) "/" (= 30 alnum)
  311. "-" (+ (any alnum "-+.")) ".drv" string-end)
  312. 'guix-build-log-mode))
  313. (provide 'guix-build-log)
  314. ;;; guix-build-log.el ends here