* [PATCH 1/3] emacs: Display guix command errors in the minibuffer.
2015-09-13 19:39 [PATCH 0/3] emacs: Add 'guix-build-log-mode' and friends Alex Kost
@ 2015-09-13 19:39 ` Alex Kost
2015-09-14 7:22 ` Alex Kost
2015-09-14 11:54 ` Ludovic Courtès
2015-09-13 19:39 ` [PATCH 2/3] emacs: Add modes for viewing build logs Alex Kost
` (2 subsequent siblings)
3 siblings, 2 replies; 14+ messages in thread
From: Alex Kost @ 2015-09-13 19:39 UTC (permalink / raw)
To: guix-devel
* emacs/guix-main.scm (output+error): New procedure.
(guix-command-output): Use it.
* emacs/guix-base.el (guix-command-output): Display error output in the
minibuffer.
---
emacs/guix-base.el | 9 ++++++---
emacs/guix-main.scm | 20 +++++++++++++++++---
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/emacs/guix-base.el b/emacs/guix-base.el
index 3bee910..8d14095 100644
--- a/emacs/guix-base.el
+++ b/emacs/guix-base.el
@@ -1129,9 +1129,12 @@ The function is called with a single argument - a command line string."
(defun guix-command-output (args)
"Return string with 'guix ARGS ...' output."
- (guix-eval-read
- (apply #'guix-make-guile-expression
- 'guix-command-output args)))
+ (cl-multiple-value-bind (output error)
+ (guix-eval (apply #'guix-make-guile-expression
+ 'guix-command-output args))
+ ;; Remove trailing new space from the error string.
+ (message (replace-regexp-in-string "\n\\'" "" (read error)))
+ (read output)))
(defun guix-help-string (&optional commands)
"Return string with 'guix COMMANDS ... --help' output."
diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm
index c9b84d3..10165c1 100644
--- a/emacs/guix-main.scm
+++ b/emacs/guix-main.scm
@@ -71,6 +71,18 @@
(define (list-maybe obj)
(if (list? obj) obj (list obj)))
+(define (output+error thunk)
+ "Call THUNK and return 2 values: output and error output as strings."
+ (let ((output-port (open-output-string))
+ (error-port (open-output-string)))
+ (with-output-to-port output-port
+ (lambda () (with-error-to-port error-port thunk)))
+ (let ((strings (list (get-output-string output-port)
+ (get-output-string error-port))))
+ (close-output-port output-port)
+ (close-output-port error-port)
+ (apply values strings))))
+
(define (full-name->name+version spec)
"Given package specification SPEC with or without output,
return two values: name and version. For example, for SPEC
@@ -953,9 +965,11 @@ GENERATIONS is a list of generation numbers."
(const #t)))
(define (guix-command-output . args)
- "Return string with 'guix ARGS ...' output."
- (with-output-to-string
- (lambda () (apply guix-command args))))
+ "Return 2 strings with 'guix ARGS ...' output and error output."
+ (output+error
+ (lambda ()
+ (guix-warning-port (current-error-port))
+ (apply guix-command args))))
(define (help-string . commands)
"Return string with 'guix COMMANDS ... --help' output."
--
2.5.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 2/3] emacs: Add modes for viewing build logs.
2015-09-13 19:39 [PATCH 0/3] emacs: Add 'guix-build-log-mode' and friends Alex Kost
2015-09-13 19:39 ` [PATCH 1/3] emacs: Display guix command errors in the minibuffer Alex Kost
@ 2015-09-13 19:39 ` Alex Kost
2015-09-14 12:01 ` Ludovic Courtès
2015-09-14 12:02 ` Ludovic Courtès
2015-09-13 19:39 ` [PATCH 3/3] emacs: Add "View build log" action to build popup Alex Kost
2015-09-14 11:54 ` [PATCH 0/3] emacs: Add 'guix-build-log-mode' and friends Ludovic Courtès
3 siblings, 2 replies; 14+ messages in thread
From: Alex Kost @ 2015-09-13 19:39 UTC (permalink / raw)
To: guix-devel
* emacs/guix-build-log.el: New file.
* emacs.am (ELFILES): Add it.
* doc/emacs.texi (Emacs Build Log): Document it. New node.
(Emacs Interface): Add it.
* doc/guix.texi (Top): Likewise.
---
doc/emacs.texi | 31 ++++++
doc/guix.texi | 1 +
emacs.am | 1 +
emacs/guix-build-log.el | 252 ++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 285 insertions(+)
create mode 100644 emacs/guix-build-log.el
diff --git a/doc/emacs.texi b/doc/emacs.texi
index db2e657..33bdbd2 100644
--- a/doc/emacs.texi
+++ b/doc/emacs.texi
@@ -11,6 +11,7 @@ Guix convenient and fun.
* Package Management: Emacs Package Management. Managing packages and generations.
* Popup Interface: Emacs Popup Interface. Magit-like interface for guix commands.
* Prettify Mode: Emacs Prettify. Abbreviating @file{/gnu/store/@dots{}} file names.
+* Build Log Mode: Emacs Build Log. Highlighting Guix build logs.
* Completions: Emacs Completions. Completing @command{guix} shell command.
@end menu
@@ -571,6 +572,36 @@ mode hooks (@pxref{Hooks,,, emacs, The GNU Emacs Manual}), for example:
@end example
+@node Emacs Build Log
+@section Build Log Mode
+
+GNU@tie{}Guix provides major and minor modes for highlighting build
+logs. So when you have a file with a package build output---for
+example, a file returned by @command{guix build --log-file @dots{}}
+command (@pxref{Invoking guix build}), you may call @kbd{M-x
+guix-build-log-mode} command in the buffer with this file. This major
+mode highlights some lines specific to build output and provides the
+following key bindings:
+
+@table @kbd
+
+@item M-n
+Move to the next build phase.
+
+@item M-p
+Move to the previous build phase.
+
+@end table
+
+There is also @kbd{M-x guix-build-log-minor-mode} which also provides
+the same highlighting (but not key bindings). And as it is a minor
+mode, it can be enabled in any buffer. For example, if you are building
+some package in a shell buffer (@pxref{Interactive Shell,,, emacs, The
+GNU Emacs Manual}), you may enable @command{guix-build-log-minor-mode}
+to make it more colorful. Guix build output is rather specific, so this
+new highlighting shouldn't conflict with the existing one.
+
+
@node Emacs Completions
@section Shell Completions
diff --git a/doc/guix.texi b/doc/guix.texi
index 9ae91a8..c9860c2 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -114,6 +114,7 @@ Emacs Interface
* Package Management: Emacs Package Management. Managing packages and generations.
* Popup Interface: Emacs Popup Interface. Magit-like interface for guix commands.
* Prettify Mode: Emacs Prettify. Abbreviating @file{/gnu/store/@dots{}} file names.
+* Build Log Mode: Emacs Build Log. Highlighting Guix build logs.
* Completions: Emacs Completions. Completing @command{guix} shell command.
Programming Interface
diff --git a/emacs.am b/emacs.am
index 5d3cb81..a9147ed 100644
--- a/emacs.am
+++ b/emacs.am
@@ -21,6 +21,7 @@ AUTOLOADS = emacs/guix-autoloads.el
ELFILES = \
emacs/guix-backend.el \
emacs/guix-base.el \
+ emacs/guix-build-log.el \
emacs/guix-command.el \
emacs/guix-emacs.el \
emacs/guix-external.el \
diff --git a/emacs/guix-build-log.el b/emacs/guix-build-log.el
new file mode 100644
index 0000000..6d71521
--- /dev/null
+++ b/emacs/guix-build-log.el
@@ -0,0 +1,252 @@
+;;; guix-build-log.el --- Major and minor modes for build logs -*- lexical-binding: t -*-
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a major mode (`guix-build-log-mode') and a minor mode
+;; (`guix-build-log-minor-mode') for highlighting Guix build logs.
+
+;;; Code:
+
+(defgroup guix-build-log nil
+ "Settings for `guix-build-log-mode'."
+ :group 'guix)
+
+(defgroup guix-build-log-faces nil
+ "Faces for `guix-build-log-mode'."
+ :group 'guix-build-log
+ :group 'guix-faces)
+
+(defface guix-build-log-title-head
+ '((t :inherit font-lock-keyword-face))
+ "Face for '@' symbol of a log title."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-title-start
+ '((t :inherit guix-build-log-title-head))
+ "Face for a log title denoting a start of a process."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-title-success
+ '((t :inherit guix-build-log-title-head))
+ "Face for a log title denoting a successful end of a process."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-title-fail
+ '((t :inherit error))
+ "Face for a log title denoting a failed end of a process."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-title-end
+ '((t :inherit guix-build-log-title-head))
+ "Face for a log title denoting an undefined end of a process."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-phase-name
+ '((t :inherit font-lock-function-name-face))
+ "Face for a phase name."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-phase-start
+ '((default :weight bold)
+ (((class grayscale) (background light)) :foreground "Gray90")
+ (((class grayscale) (background dark)) :foreground "DimGray")
+ (((class color) (min-colors 16) (background light))
+ :foreground "DarkGreen")
+ (((class color) (min-colors 16) (background dark))
+ :foreground "LimeGreen")
+ (((class color) (min-colors 8)) :foreground "green"))
+ "Face for the start line of a phase."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-phase-end
+ '((((class grayscale) (background light)) :foreground "Gray90")
+ (((class grayscale) (background dark)) :foreground "DimGray")
+ (((class color) (min-colors 16) (background light))
+ :foreground "ForestGreen")
+ (((class color) (min-colors 16) (background dark))
+ :foreground "LightGreen")
+ (((class color) (min-colors 8)) :foreground "green")
+ (t :weight bold))
+ "Face for the end line of a phase."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-phase-success
+ '((t))
+ "Face for the 'succeeded' word of a phase line."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-phase-fail
+ '((t :inherit error))
+ "Face for the 'failed' word of a phase line."
+ :group 'guix-build-log-faces)
+
+(defface guix-build-log-phase-seconds
+ '((t :inherit font-lock-constant-face))
+ "Face for the number of seconds for a phase."
+ :group 'guix-build-log-faces)
+
+(defcustom guix-build-log-mode-hook
+ ;; Not using `compilation-minor-mode' because it rebinds some standard
+ ;; keys, including M-n/M-p.
+ '(compilation-shell-minor-mode view-mode)
+ "Hook run after `guix-build-log-mode' is entered."
+ :type 'hook
+ :group 'guix-build-log)
+
+(defvar guix-build-log-phase-name-regexp "`\\([^']+\\)'"
+ "Regexp for a phase name.")
+
+(defvar guix-build-log-phase-start-regexp
+ (concat "^starting phase " guix-build-log-phase-name-regexp)
+ "Regexp for the start line of a 'build' phase.")
+
+(defun guix-build-log-title-regexp (&optional state)
+ "Return regexp for the log title.
+STATE is a symbol denoting a state of the title. It should be
+`start', `fail', `success' or `nil' (for a regexp matching any
+state)."
+ (let* ((word-rx (rx (1+ (any word "-"))))
+ (state-rx (cond ((eq state 'start) (concat word-rx "started"))
+ ((eq state 'success) (concat word-rx "succeeded"))
+ ((eq state 'fail) (concat word-rx "failed"))
+ (t word-rx))))
+ (rx-to-string
+ `(and bol (group "@") " " (group (regexp ,state-rx)))
+ t)))
+
+(defun guix-build-log-phase-end-regexp (&optional state)
+ "Return regexp for the end line of a 'build' phase.
+STATE is a symbol denoting how a build phase was ended. It should be
+`fail', `success' or `nil' (for a regexp matching any state)."
+ (let ((state-rx (cond ((eq state 'success) "succeeded")
+ ((eq state 'fail) "failed")
+ (t (regexp-opt '("succeeded" "failed"))))))
+ (rx-to-string
+ `(and bol "phase " (regexp ,guix-build-log-phase-name-regexp)
+ " " (group (regexp ,state-rx)) " after "
+ (group (1+ digit)) " seconds")
+ t)))
+
+(defvar guix-build-log-font-lock-keywords
+ `((,(guix-build-log-title-regexp 'start)
+ (1 'guix-build-log-title-head)
+ (2 'guix-build-log-title-start))
+ (,(guix-build-log-title-regexp 'success)
+ (1 'guix-build-log-title-head)
+ (2 'guix-build-log-title-success))
+ (,(guix-build-log-title-regexp 'fail)
+ (1 'guix-build-log-title-head)
+ (2 'guix-build-log-title-fail))
+ (,(guix-build-log-title-regexp)
+ (1 'guix-build-log-title-head)
+ (2 'guix-build-log-title-end))
+ (,guix-build-log-phase-start-regexp
+ (0 'guix-build-log-phase-start)
+ (1 'guix-build-log-phase-name prepend))
+ (,(guix-build-log-phase-end-regexp 'success)
+ (0 'guix-build-log-phase-end)
+ (1 'guix-build-log-phase-name prepend)
+ (2 'guix-build-log-phase-success prepend)
+ (3 'guix-build-log-phase-seconds prepend))
+ (,(guix-build-log-phase-end-regexp 'fail)
+ (0 'guix-build-log-phase-end)
+ (1 'guix-build-log-phase-name prepend)
+ (2 'guix-build-log-phase-fail prepend)
+ (3 'guix-build-log-phase-seconds prepend)))
+ "A list of `font-lock-keywords' for `guix-build-log-mode'.")
+
+(defvar guix-build-log-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map special-mode-map)
+ (define-key map (kbd "M-n") 'guix-build-log-next-phase)
+ (define-key map (kbd "M-p") 'guix-build-log-previous-phase)
+ map)
+ "Keymap for `guix-build-log-mode' buffers.")
+
+(defun guix-build-log-next-phase (&optional arg)
+ "Move to the next build phase.
+With ARG, do it that many times. Negative ARG means move
+backward."
+ (interactive "^p")
+ (if arg
+ (when (zerop arg) (user-error "Try again"))
+ (setq arg 1))
+ (let ((search-fun (if (> arg 0)
+ #'re-search-forward
+ #'re-search-backward))
+ (n (abs arg))
+ found last-found)
+ (save-excursion
+ (end-of-line (if (> arg 0) 1 0)) ; skip the current line
+ (while (and (not (zerop n))
+ (setq found
+ (funcall search-fun
+ guix-build-log-phase-start-regexp
+ nil t)))
+ (setq n (1- n)
+ last-found found)))
+ (when last-found
+ (goto-char last-found)
+ (forward-line 0))
+ (or found
+ (user-error (if (> arg 0)
+ "No next build phase"
+ "No previous build phase")))))
+
+(defun guix-build-log-previous-phase (&optional arg)
+ "Move to the previous build phase.
+With ARG, do it that many times. Negative ARG means move
+forward."
+ (interactive "^p")
+ (guix-build-log-next-phase (- (or arg 1))))
+
+;;;###autoload
+(define-derived-mode guix-build-log-mode special-mode
+ "Guix-Build-Log"
+ "Major mode for viewing Guix build logs.
+
+\\{guix-build-log-mode-map}"
+ (setq font-lock-defaults '(guix-build-log-font-lock-keywords t)))
+
+;;;###autoload
+(define-minor-mode guix-build-log-minor-mode
+ "Toggle Guix Build Log minor mode.
+
+With a prefix argument ARG, enable Guix Build Log minor mode if
+ARG is positive, and disable it otherwise. If called from Lisp,
+enable the mode if ARG is omitted or nil.
+
+When Guix Build Log minor mode is enabled, it highlights build
+log in the current buffer. This mode can be enabled
+programmatically using hooks:
+
+ (add-hook 'shell-mode-hook 'guix-build-log-minor-mode)"
+ :init-value nil
+ :lighter " Guix-Build-Log"
+ :group 'guix-build-log
+ (if guix-build-log-minor-mode
+ (font-lock-add-keywords nil guix-build-log-font-lock-keywords)
+ (font-lock-remove-keywords nil guix-build-log-font-lock-keywords))
+ (when font-lock-mode
+ (font-lock-fontify-buffer)))
+
+(provide 'guix-build-log)
+
+;;; guix-build-log.el ends here
--
2.5.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 3/3] emacs: Add "View build log" action to build popup.
2015-09-13 19:39 [PATCH 0/3] emacs: Add 'guix-build-log-mode' and friends Alex Kost
2015-09-13 19:39 ` [PATCH 1/3] emacs: Display guix command errors in the minibuffer Alex Kost
2015-09-13 19:39 ` [PATCH 2/3] emacs: Add modes for viewing build logs Alex Kost
@ 2015-09-13 19:39 ` Alex Kost
2015-09-14 12:03 ` Ludovic Courtès
2015-09-14 11:54 ` [PATCH 0/3] emacs: Add 'guix-build-log-mode' and friends Ludovic Courtès
3 siblings, 1 reply; 14+ messages in thread
From: Alex Kost @ 2015-09-13 19:39 UTC (permalink / raw)
To: guix-devel
* emacs/guix-command.el (guix-run-view-build-log): New function.
(guix-command-additional-execute-arguments,
guix-command-special-executors): Add entries for "View build log"
action.
* emacs/guix-utils.el (guix-find-file-or-url): New function.
---
emacs/guix-command.el | 19 ++++++++++++++++++-
emacs/guix-utils.el | 10 ++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/emacs/guix-command.el b/emacs/guix-command.el
index 81f619f..ddafac9 100644
--- a/emacs/guix-command.el
+++ b/emacs/guix-command.el
@@ -497,7 +497,10 @@ to be modified."
"List of default 'execute' action arguments.")
(defvar guix-command-additional-execute-arguments
- `((("graph")
+ `((("build")
+ ,(guix-command-make-argument
+ :name "log" :char ?l :doc "View build log"))
+ (("graph")
,(guix-command-make-argument
:name "view" :char ?v :doc "View graph")))
"Alist of guix commands and additional 'execute' action arguments.")
@@ -518,6 +521,8 @@ to be modified."
("repl" . guix-run-environment-command-in-repl))
(("pull")
("repl" . guix-run-pull-command-in-repl))
+ (("build")
+ ("log" . guix-run-view-build-log))
(("graph")
("view" . guix-run-view-graph)))
"Alist of guix commands and alists of special executers for them.
@@ -556,6 +561,18 @@ Perform pull-specific actions after operation, see
(apply #'guix-make-guile-expression 'guix-command args)
nil 'pull))
+(defun guix-run-view-build-log (args)
+ "Add --log-file to ARGS, run 'guix ARGS ...' build command, and
+open the log file(s)."
+ (let* ((args (if (member "--log-file" args)
+ args
+ (apply #'list (car args) "--log-file" (cdr args))))
+ (output (guix-command-output args))
+ (files (split-string output "\n" t)))
+ (dolist (file files)
+ (guix-find-file-or-url file)
+ (guix-build-log-mode))))
+
(defun guix-run-view-graph (args)
"Run 'guix ARGS ...' graph command, make the image and open it."
(let* ((graph-file (guix-dot-file-name))
diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el
index c1ce954..682609d 100644
--- a/emacs/guix-utils.el
+++ b/emacs/guix-utils.el
@@ -208,6 +208,16 @@ single argument."
(funcall guix-find-file-function file)
(message "File '%s' does not exist." file)))
+(defvar url-handler-regexp)
+
+(defun guix-find-file-or-url (file-or-url)
+ "Find FILE-OR-URL."
+ (require 'url-handlers)
+ (let ((file-name-handler-alist
+ (cons (cons url-handler-regexp 'url-file-handler)
+ file-name-handler-alist)))
+ (find-file file-or-url)))
+
(defmacro guix-while-search (regexp &rest body)
"Evaluate BODY after each search for REGEXP in the current buffer."
(declare (indent 1) (debug t))
--
2.5.1
^ permalink raw reply related [flat|nested] 14+ messages in thread