;;; Code:
(require 'debbugs)
+(require 'widget)
(eval-when-compile (require 'cl))
(autoload 'gnus-read-ephemeral-emacs-bug-group "gnus-group")
(defface debbugs-done '((t (:foreground "DarkGrey")))
"Face for closed bug reports.")
+(defvar debbugs-widget-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\r" 'widget-button-press)
+ map))
+
(defun debbugs-emacs (severities &optional package suppress-done archivedp)
"List all outstanding Emacs bugs."
(interactive
nil t "normal")))
(unless (consp severities)
(setq severities (list severities)))
- (pop-to-buffer (get-buffer-create "*Emacs Bugs*"))
- (debbugs-mode)
(let ((debbugs-port "gnu.org")
- (buffer-read-only nil)
- (ids nil)
- (default 500))
+ (default 500)
+ ids widgets)
(dolist (severity severities)
(setq ids (nconc ids
(debbugs-get-bugs :package (or package "emacs")
:severity severity
:archive (if archivedp
"1" "0")))))
+ (setq ids (sort ids '<))
+
+ (if (> (length ids) default)
+ (let ((cursor-in-echo-area nil))
+ (setq default
+ (string-to-number
+ (read-string
+ (format
+ "How many reports (available %d, default %d): "
+ (length ids) default)
+ nil
+ nil
+ (number-to-string default))))))
+
+ (if (> (length ids) default)
+ (let ((i 0)
+ curr-ids)
+ (while ids
+ (setq i (1+ i)
+ curr-ids (butlast ids (- (length ids) default))
+ widgets (append
+ widgets
+ (list
+ (widget-convert
+ 'push-button
+ :follow-link 'mouse-face
+ :notify (lambda (widget &rest ignore)
+ (debbugs-show-reports
+ (widget-get widget :suppress-done)
+ widget
+ (widget-get widget :widgets)))
+ :keymap debbugs-widget-map
+ :suppress-done suppress-done
+ :buffer-name (format "*Emacs Bugs*<%d>" i)
+ :bug-ids (butlast ids (- (length ids) default))
+ (format " %d" i))))
+ ids (last ids (- (length ids) default))))
+ (debbugs-show-reports suppress-done (car widgets) widgets))
+
+ (debbugs-show-reports suppress-done
+ (widget-convert
+ 'const
+ :buffer-name "*Emacs Bugs*"
+ :bug-ids ids)
+ nil))))
+
+(defun debbugs-show-reports (suppress-done widget widgets)
+ "Show bug reports as given in WIDGET property :bug-ids."
+ (pop-to-buffer (get-buffer-create (widget-get widget :buffer-name)))
+ (debbugs-mode)
+ (let ((inhibit-read-only t))
(erase-buffer)
- (when (> (length ids) default)
- (let* ((cursor-in-echo-area nil)
- (input
- (read-string
- (format
- "How many reports (available %d, default %d): "
- (length ids) default)
- nil
- nil
- (number-to-string default))))
- (setq ids (last (sort ids '<) (string-to-number input)))))
-
- (dolist (status (sort (apply 'debbugs-get-status ids)
+ (when widgets
+ (widget-insert "Page:")
+ (mapc
+ (lambda (obj)
+ (widget-insert " ")
+ (widget-put obj :widgets widgets)
+ (if (eq obj widget)
+ (widget-put obj :button-face 'widget-button-pressed)
+ (widget-put obj :button-face 'widget-button-face))
+ (widget-apply obj :create))
+ widgets)
+ (widget-insert "\n\n"))
+
+ (dolist (status (sort (apply 'debbugs-get-status
+ (widget-get widget :bug-ids))
(lambda (s1 s2)
(< (cdr (assq 'id s1))
(cdr (assq 'id s2))))))
(decode-coding-string (cdr (assq 'originator status))
'utf-8)))
(subject (decode-coding-string (cdr (assq 'subject status))
- 'utf-8)))
+ 'utf-8))
+ merged)
(setq address
;; Prefer the name over the address.
(or (cdr address)
(unless (equal (cdr (assq 'pending status)) "pending")
(setq words
(concat words "," (cdr (assq 'pending status)))))
+ (when (setq merged (cdr (assq 'mergedwith status)))
+ (setq words (format "%s,%s"
+ (if (numberp merged)
+ merged
+ (mapconcat 'number-to-string merged
+ ","))
+ words)))
(if (> (length words) 20)
(propertize (substring words 0 20) 'help-echo words)
words))
address)
(propertize subject 'help-echo subject)))
(forward-line -1)
+ (put-text-property (point) (1+ (point))
+ 'debbugs-status status)
(put-text-property
(+ (point) 5) (+ (point) 26)
'face
'debbugs-handled)
(t
'debbugs-stale)))
- (forward-line 1)))))
- (goto-char (point-min)))
+ (forward-line 1))))
+
+ (when widgets
+ (widget-insert "\nPage:")
+ (mapc
+ (lambda (obj)
+ (widget-insert " ")
+ (widget-put obj :widgets widgets)
+ (if (eq obj widget)
+ (widget-put obj :button-face 'widget-button-pressed)
+ (widget-put obj :button-face 'widget-button-face))
+ (widget-apply obj :create))
+ widgets)
+ (widget-setup))
-(defvar debbugs-mode-map nil)
-(unless debbugs-mode-map
- (setq debbugs-mode-map (make-sparse-keymap))
- (define-key debbugs-mode-map "\r" 'debbugs-select-report)
- (define-key debbugs-mode-map "q" 'kill-buffer))
+ (goto-char (point-min))))
+
+(defvar debbugs-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\r" 'debbugs-select-report)
+ (define-key map "q" 'kill-buffer)
+ (define-key map "s" 'debbugs-toggle-sort)
+ (define-key map "d" 'debbugs-display-status)
+ map))
+
+(defvar debbugs-sort-state 'number)
(defun debbugs-mode ()
"Major mode for listing bug reports.
(setq major-mode 'debbugs-mode)
(setq mode-name "Debbugs")
(use-local-map debbugs-mode-map)
+ (set (make-local-variable 'debbugs-sort-state)
+ 'number)
(buffer-disable-undo)
(setq truncate-lines t)
(setq buffer-read-only t))
-(defun debbugs-select-report ()
- "Select the report on the current line."
+(defvar debbugs-state-preference
+ '((debbugs-new . 1)
+ (debbugs-stale . 2)
+ (debbugs-handled . 3)
+ (debbugs-done . 4)))
+
+(defun debbugs-toggle-sort ()
+ "Toggle sorting by age and by state."
(interactive)
- (let (id)
- (save-excursion
- (beginning-of-line)
- (if (not (looking-at " *\\([0-9]+\\)"))
- (error "No bug report on the current line")
- (setq id (string-to-number (match-string 1)))))
- (gnus-read-ephemeral-emacs-bug-group
- id (cons (current-buffer)
- (current-window-configuration)))
- (with-current-buffer (window-buffer (selected-window))
- (debbugs-summary-mode 1))))
+ (beginning-of-line)
+ (let ((buffer-read-only nil)
+ (before-change-functions nil)
+ (current-bug (debbugs-current-id t)))
+ (setq debbugs-sort-state
+ (if (eq debbugs-sort-state 'number)
+ 'state
+ 'number))
+ (goto-char (point-min))
+ (while (and (not (eobp))
+ (not (get-text-property (point) 'debbugs-status)))
+ (forward-line 1))
+ (save-restriction
+ (narrow-to-region
+ (point)
+ (progn
+ (goto-char (point-max))
+ (beginning-of-line)
+ (while (and (not (bobp))
+ (not (get-text-property (point) 'debbugs-status)))
+ (forward-line -1))
+ (forward-line 1)
+ (point)))
+ (goto-char (point-min))
+ (sort-subr
+ nil (lambda () (forward-line 1)) 'end-of-line
+ (lambda ()
+ (if (eq debbugs-sort-state 'number)
+ (debbugs-current-id)
+ (or (cdr (assq (get-text-property (+ (point) 7) 'face)
+ debbugs-state-preference))
+ 10)))))
+ (if (not current-bug)
+ (goto-char (point-max))
+ (goto-char (point-min))
+ (re-search-forward (format "^%d" current-bug) nil t))))
+
+(defvar debbugs-bug-number nil)
+
+(defun debbugs-current-id (&optional noerror)
+ (or (cdr (assq 'id (get-text-property (line-beginning-position)
+ 'debbugs-status)))
+ (and (not noerror)
+ (error "No bug on the current line"))))
+
+(defun debbugs-display-status (id)
+ "Display the status of the report on the current line."
+ (interactive (list (debbugs-current-id)))
+ (let ((status (get-text-property (line-beginning-position)
+ 'debbugs-status)))
+ (pop-to-buffer "*Bug Status*")
+ (erase-buffer)
+ (pp status (current-buffer))
+ (goto-char (point-min))))
+
+(defun debbugs-select-report (id)
+ "Select the report on the current line."
+ (interactive (list (debbugs-current-id)))
+ ;; We open the report messages.
+ (gnus-read-ephemeral-emacs-bug-group
+ id (cons (current-buffer)
+ (current-window-configuration)))
+ (with-current-buffer (window-buffer (selected-window))
+ (debbugs-summary-mode 1)
+ (set (make-local-variable 'debbugs-bug-number) id)))
(defvar debbugs-summary-mode-map
(let ((map (make-sparse-keymap)))
"merge" "forcemerge"
"patch" "wontfix" "moreinfo" "unreproducible" "fixed" "notabug")
nil t)))
- (let* ((subject (mail-header-subject (gnus-summary-article-header)))
- (id
- (if (string-match "bug#\\([0-9]+\\)" subject)
- (string-to-number (match-string 1 subject))
- (error "No bug number present")))
+ (let* ((id debbugs-bug-number) ; Set on group entry.
(version
(when (member message '("close" "done"))
(read-string