X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/8f3fe2b523bfa4bb3f3ac88b263e439d885d7c9f..e7d3fea3ff81ae402a5c5ab33bc16c4630b5b459:/packages/debbugs/debbugs-gnu.el diff --git a/packages/debbugs/debbugs-gnu.el b/packages/debbugs/debbugs-gnu.el index 004560f98..5b832da2c 100644 --- a/packages/debbugs/debbugs-gnu.el +++ b/packages/debbugs/debbugs-gnu.el @@ -24,310 +24,677 @@ ;;; Commentary: +;; This package provides an interface to bug reports which are located +;; on the GNU bug tracker debbugs.gnu.org. It's main purpose is to +;; show and manipulate bug reports from Emacs, but it could be used +;; also for other GNU projects which use the same bug tracker. + +;; If you have `debbugs-gnu.el' in your load-path, you could enable +;; the bug tracker command by the following line in your ~/.emacs +;; +;; (autoload 'debbugs-gnu "debbugs-gnu" "" 'interactive) + +;; The bug tracker is called interactively by +;; +;; M-x debbugs-gnu + +;; It asks for the severities, for which bugs shall be shown. This can +;; be either just one severity, or a list of severities, separated by +;; comma. Valid severities are "serious", "important", "normal", +;; "minor" or "wishlist". Severities "critical" and "grave" are not +;; used, although configured on the GNU bug tracker. If no severity +;; is given, all bugs are selected. + +;; There is also the pseudo severity "tagged", which selects locally +;; tagged bugs. + +;; If a prefix is given to the command, more search parameters are +;; asked for, like packages (also a comma separated list, "emacs" is +;; the default), whether archived bugs shall be shown, and whether +;; closed bugs shall be shown. + +;; The bug reports are downloaded from the bug tracker. In order to +;; not generate too much load of the server, up to 500 bugs will be +;; downloaded at once. If there are more hits, you will be asked to +;; change this limit, but please don't increase this number too much. + +;; These default values could be changed also by customer options +;; `debbugs-gnu-default-severities', `debbugs-gnu-default-packages' +;; and `debbugs-gnu-default-hits-per-page'. + +;; The command creates one or more pages of bug lists. Every bug is +;; shown in one line, including the bug number, the status (combining +;; merged bug numbers, keywords and severities), the name of the +;; submitter, and the title of the bug. On every bug line you could +;; apply the following actions by the following keystrokes: + +;; RET: Show corresponding messages in Gnus +;; "C": Send a control message +;; "t": Mark the bug locally as tagged +;; "d": Show bug attributes + +;; Furthermore, you could apply the global actions + +;; "g": Rescan bugs +;; "q": Quit the buffer +;; "s": Toggle bug sorting for age or for state +;; "x": Toggle suppressing of closed bugs + +;; When you visit the related bug messages in Gnus, you could also +;; send control messages by keystroke "C". + +;; In the header line of every bug list page, you can toggle sorting +;; per column by selecting a column with the mouse. The sorting +;; happens as expected for the respective column; sorting in the Title +;; column is depending on whether you are the owner of a bug. + ;;; Code: (require 'debbugs) (require 'widget) +(require 'tabulated-list) (eval-when-compile (require 'cl)) +(autoload 'widget-convert "wid-edit.el") (autoload 'gnus-read-ephemeral-emacs-bug-group "gnus-group") (autoload 'mail-header-subject "nnheader") (autoload 'gnus-summary-article-header "gnus-sum") (autoload 'message-make-from "message") -(defface debbugs-new '((t (:foreground "red"))) +(defgroup debbugs-gnu () + "UI for the debbugs.gnu.org bug tracker." + :group 'debbugs + :version "24.1") + +(defcustom debbugs-gnu-default-severities '("normal") + "*The list severities bugs are searched for. +\"tagged\" is not a severity but marks locally tagged bugs." + :group 'debbugs-gnu + :type '(set (const "serious") + (const "important") + (const "normal") + (const "minor") + (const "wishlist") + (const "tagged")) + :version "24.1") + +(defcustom debbugs-gnu-default-packages '("emacs") + "*The list of packages to be searched for." + :group 'debbugs-gnu + :type '(set (const "automake") + (const "coreutils") + (const "emacs") + (const "gnus") + (const "libtool")) + :version "24.1") + +(defcustom debbugs-gnu-default-hits-per-page 500 + "*The number of bugs shown per page." + :group 'debbugs-gnu + :type 'integer + :version "24.1") + +(defface debbugs-gnu-new '((t (:foreground "red"))) "Face for new reports that nobody has answered.") -(defface debbugs-handled '((t (:foreground "ForestGreen"))) - "Face for new reports that nobody has answered.") +(defface debbugs-gnu-handled '((t (:foreground "ForestGreen"))) + "Face for reports that have been modified recently.") -(defface debbugs-stale '((t (:foreground "orange"))) - "Face for new reports that nobody has answered.") +(defface debbugs-gnu-pending '((t (:foreground "MidnightBlue"))) + "Face for reports that have been modified recently.") -(defface debbugs-done '((t (:foreground "DarkGrey"))) +(defface debbugs-gnu-stale '((t (:foreground "orange"))) + "Face for reports that have not been touched for a week.") + +(defface debbugs-gnu-done '((t (:foreground "DarkGrey"))) "Face for closed bug reports.") -(defun debbugs-emacs (severities &optional package suppress-done archivedp) +(defface debbugs-gnu-tagged '((t (:foreground "red"))) + "Face for reports that have been tagged locally.") + +(defvar debbugs-gnu-widgets nil) + +(defvar debbugs-gnu-widget-map + (let ((map (make-sparse-keymap))) + (define-key map "\r" 'widget-button-press) + (define-key map [mouse-1] 'widget-button-press) + (define-key map [mouse-2] 'widget-button-press) + map)) + +(defvar debbugs-gnu-local-tags nil + "List of bug numbers tagged locally, and kept persistent.") + +(defvar debbugs-gnu-persistency-file + (expand-file-name (locate-user-emacs-file "debbugs")) + "File name of a persistency store for debbugs variables") + +(defun debbugs-gnu-dump-persistency-file () + "Function to store debbugs variables persistently." + (with-temp-file debbugs-gnu-persistency-file + (insert + ";; -*- emacs-lisp -*-\n" + ";; Debbugs tags connection history. Don't change this file.\n\n" + (format "(setq debbugs-gnu-local-tags '%S)" + (sort (copy-sequence debbugs-gnu-local-tags) '<))))) + +(defvar debbugs-gnu-current-severities nil + "The severities strings to be searched for.") + +(defvar debbugs-gnu-current-packages nil + "The package names to be searched for.") + +(defvar debbugs-gnu-current-archive nil + "Whether to search in the archive.") + +(defun debbugs-gnu (severities &optional packages archivedp suppress-done) "List all outstanding Emacs bugs." (interactive - (list - (completing-read "Severity: " - '("important" "normal" "minor" "wishlist") - nil t "normal"))) + (let (archivedp) + (list + (completing-read-multiple + "Severity: " + (mapcar 'cadr (cdr (get 'debbugs-gnu-default-severities 'custom-type))) + nil t (mapconcat 'identity debbugs-gnu-default-severities ",")) + ;; The optional parameters are asked only when there is a prefix. + (if current-prefix-arg + (completing-read-multiple + "Packages: " + (mapcar 'cadr (cdr (get 'debbugs-gnu-default-packages 'custom-type))) + nil t (mapconcat 'identity debbugs-gnu-default-packages ",")) + debbugs-gnu-default-packages) + (when current-prefix-arg + (setq archivedp (y-or-n-p "Show archived bugs?"))) + (when (and current-prefix-arg (not archivedp)) + (y-or-n-p "Suppress closed bugs?"))))) + + ;; Initialize variables. + (when (and (file-exists-p debbugs-gnu-persistency-file) + (not debbugs-gnu-local-tags)) + (with-temp-buffer + (insert-file-contents debbugs-gnu-persistency-file) + (eval (read (current-buffer))))) + ;; Set lists. (unless (consp severities) (setq severities (list severities))) - (let ((debbugs-port "gnu.org") - (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) + (unless (consp packages) + (setq packages (list packages))) + + (setq debbugs-gnu-current-severities severities + debbugs-gnu-current-packages packages + debbugs-gnu-current-archive (if archivedp "1" "0") + debbugs-gnu-widgets nil) + + (let ((hits debbugs-gnu-default-hits-per-page) + (ids (debbugs-gnu-get-bugs))) + + (if (> (length ids) hits) (let ((cursor-in-echo-area nil)) - (setq default + (setq hits (string-to-number (read-string (format "How many reports (available %d, default %d): " - (length ids) default) + (length ids) hits) nil nil - (number-to-string default)))))) + (number-to-string hits)))))) - (if (> (length ids) default) + (if (> (length ids) hits) (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))) - :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) + curr-ids (butlast ids (- (length ids) hits))) + (add-to-list + 'debbugs-gnu-widgets + (widget-convert + 'push-button + :follow-link 'mouse-face + :notify (lambda (widget &rest ignore) + (debbugs-gnu-show-reports widget)) + :keymap debbugs-gnu-widget-map + :suppress-done suppress-done + :buffer-name (format "*Emacs Bugs*<%d>" i) + :bug-ids curr-ids + :help-echo (format "%d-%d" (car ids) (car (last curr-ids))) + :format " %[%v%]" + (number-to-string i)) + 'append) + (setq ids (last ids (- (length ids) hits)))) + (debbugs-gnu-show-reports (car debbugs-gnu-widgets))) + + (debbugs-gnu-show-reports + (widget-convert + 'const + :suppress-done suppress-done + :buffer-name "*Emacs Bugs*" + :bug-ids ids))))) + +(defun debbugs-gnu-get-bugs () + "Retrieve bugs numbers from debbugs.gnu.org according search criteria." + (let ((debbugs-port "gnu.org") + (args `(:archive ,debbugs-gnu-current-archive)) + (ids (when (member "tagged" debbugs-gnu-current-severities) + (copy-sequence debbugs-gnu-local-tags)))) + (dolist (severity (delete "tagged" debbugs-gnu-current-severities)) + (when (not (zerop (length severity))) + (setq args (append args `(:severity ,severity))))) + (dolist (package debbugs-gnu-current-packages) + (when (not (zerop (length package))) + (setq args (append args `(:package ,package))))) + (sort (nconc ids (apply 'debbugs-get-bugs args)) '<))) + +(defvar debbugs-gnu-current-widget nil) + +(defvar widget-mouse-face) + +(defun debbugs-gnu-show-reports (widget) "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 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)))))) - (when (or (not suppress-done) - (not (equal (cdr (assq 'pending status)) "done"))) - (let ((address (mail-header-parse-address - (decode-coding-string (cdr (assq 'originator status)) - 'utf-8))) - (subject (decode-coding-string (cdr (assq 'subject status)) - 'utf-8)) - merged) - (setq address - ;; Prefer the name over the address. - (or (cdr address) - (car address))) - (insert - (format "%5d %-20s [%-23s] %s\n" - (cdr (assq 'id status)) - (let ((words - (mapconcat - 'identity - (cons (cdr (assq 'severity status)) - (cdr (assq 'keywords status))) - ","))) - (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)) - (if (> (length address) 23) - (propertize (substring address 0 23) 'help-echo address) - 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 - (cond - ((equal (cdr (assq 'pending status)) "done") - 'debbugs-done) - ((= (cdr (assq 'date status)) - (cdr (assq 'log_modified status))) - 'debbugs-new) - ((< (- (float-time) - (cdr (assq 'log_modified status))) - (* 60 60 24 4)) - 'debbugs-handled) - (t - 'debbugs-stale))) - (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)) + (debbugs-gnu-mode) + (let ((inhibit-read-only t) + (debbugs-port "gnu.org")) + (erase-buffer) + (set (make-local-variable 'debbugs-gnu-current-widget) + widget) + + (dolist (status (apply 'debbugs-get-status (widget-get widget :bug-ids))) + (let* ((id (cdr (assq 'id status))) + (words + (mapconcat + 'identity + (cons (cdr (assq 'severity status)) + (cdr (assq 'keywords status))) + ",")) + (address (mail-header-parse-address + (decode-coding-string (cdr (assq 'originator status)) + 'utf-8))) + (owner (if (cdr (assq 'owner status)) + (car (mail-header-parse-address + (decode-coding-string (cdr (assq 'owner status)) + 'utf-8))))) + (subject (decode-coding-string (cdr (assq 'subject status)) + 'utf-8)) + merged) + (unless (equal (cdr (assq 'pending status)) "pending") + (setq words + (concat words "," (cdr (assq 'pending status))))) + (let ((packages (delete "emacs" (cdr (assq 'package status))))) + (when packages + (setq words (concat words "," (mapconcat 'identity packages ","))))) + (when (setq merged (cdr (assq 'mergedwith status))) + (setq words (format "%s,%s" + (if (numberp merged) + merged + (mapconcat 'number-to-string merged ",")) + words))) + (add-to-list + 'tabulated-list-entries + (list + status + (vector + (propertize + (format "%5d" id) + 'face + ;; Mark tagged bugs. + (if (memq id debbugs-gnu-local-tags) + 'debbugs-gnu-tagged + 'default)) + (propertize + ;; Mark status and age. + words + 'face + (cond + ((equal (cdr (assq 'pending status)) "done") + 'debbugs-gnu-done) + ((member "pending" (cdr (assq 'keywords status))) + 'debbugs-gnu-pending) + ((= (cdr (assq 'date status)) + (cdr (assq 'log_modified status))) + 'debbugs-gnu-new) + ((< (- (float-time) + (cdr (assq 'log_modified status))) + (* 60 60 24 7)) + 'debbugs-gnu-handled) + (t + 'debbugs-gnu-stale))) + (propertize + ;; Prefer the name over the address. + (or (cdr address) + (car address)) + 'face + ;; Mark own submitted bugs. + (if (and (stringp (car address)) + (string-equal (car address) user-mail-address)) + 'debbugs-gnu-tagged + 'default)) + (propertize + subject + 'face + ;; Mark owned bugs. + (if (and (stringp owner) + (string-equal owner user-mail-address)) + 'debbugs-gnu-tagged + 'default)))) + 'append))) + (tabulated-list-init-header) + (tabulated-list-print) + + (set-buffer-modified-p nil) (goto-char (point-min)))) -(defvar debbugs-mode-map +(defun debbugs-gnu-print-entry (list-id cols) + "Insert a debbugs entry at point. +Used instead of `tabulated-list-print-entry'." + ;; This shall be in `debbugs-gnu-show-reports'. But + ;; `tabulated-list-print' erases the buffer, therefore we do it + ;; here. (bug#9047) + (when (and debbugs-gnu-widgets (= (point) (point-min))) + (widget-insert "Page:") + (mapc + (lambda (obj) + (if (eq obj debbugs-gnu-current-widget) + (widget-put obj :button-face 'widget-button-pressed) + (widget-put obj :button-face 'widget-button-face)) + (widget-apply obj :create)) + debbugs-gnu-widgets) + (widget-insert "\n\n") + (save-excursion + (widget-insert "\nPage:") + (mapc (lambda (obj) (widget-apply obj :create)) debbugs-gnu-widgets) + (widget-setup))) + + (when (or (not (widget-get debbugs-gnu-current-widget :suppress-done)) + (not (equal (cdr (assq 'pending list-id)) "done"))) + (let ((beg (point)) + (pos 0) + (id (aref cols 0)) + (id-length (nth 1 (aref tabulated-list-format 0))) + (state (aref cols 1)) + (state-length (nth 1 (aref tabulated-list-format 1))) + (submitter (aref cols 2)) + (submitter-length (nth 1 (aref tabulated-list-format 2))) + (title (aref cols 3)) + (title-length (nth 1 (aref tabulated-list-format 3)))) + ;; Insert id. + (indent-to (- id-length (length id))) + (insert id) + ;; Insert state. + (indent-to (setq pos (+ pos id-length 1)) 1) + (insert (if (> (length state) state-length) + (propertize (substring state 0 state-length) + 'help-echo state) + state)) + ;; Insert submitter. + (indent-to (setq pos (+ pos state-length 1)) 1) + (insert "[" (if (> (length submitter) (- submitter-length 2)) + (propertize (substring submitter 0 (- submitter-length 2)) + 'help-echo submitter) + submitter)) + (indent-to (+ pos (1- submitter-length))) + (insert "]") + ;; Insert title. + (indent-to (setq pos (+ pos submitter-length 1)) 1) + (insert (propertize title 'help-echo title)) + ;; Add properties. + (add-text-properties + beg (point) `(tabulated-list-id ,list-id mouse-face ,widget-mouse-face)) + (insert ?\n)))) + +(defvar debbugs-gnu-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) + (set-keymap-parent map tabulated-list-mode-map) + (define-key map "\r" 'debbugs-gnu-select-report) + (define-key map [mouse-1] 'debbugs-gnu-select-report) + (define-key map [mouse-2] 'debbugs-gnu-select-report) + (define-key map "q" 'bury-buffer) + (define-key map "s" 'debbugs-gnu-toggle-sort) + (define-key map "t" 'debbugs-gnu-toggle-tag) + (define-key map "d" 'debbugs-gnu-display-status) + (define-key map "g" 'debbugs-gnu-rescan) + (define-key map "x" 'debbugs-gnu-toggle-suppress-done) + (define-key map "C" 'debbugs-gnu-send-control-message) map)) -(defvar debbugs-sort-state 'number) +(defun debbugs-gnu-rescan () + "Rescan the current set of bug reports." + (interactive) + + ;; The last page will be provided with new bug ids. + ;; TODO: Do it also for the other pages. + (when (and debbugs-gnu-widgets + (eq debbugs-gnu-current-widget (car (last debbugs-gnu-widgets)))) + (let ((first-id (car (widget-get debbugs-gnu-current-widget :bug-ids))) + (last-id (car + (last (widget-get debbugs-gnu-current-widget :bug-ids)))) + (ids (debbugs-gnu-get-bugs))) + + (while (and (<= first-id last-id) (not (memq first-id ids))) + (setq first-id (1+ first-id))) + + (when (<= first-id last-id) + (widget-put debbugs-gnu-current-widget :bug-ids (memq first-id ids))))) -(defun debbugs-mode () + ;; Refresh the buffer. `save-excursion' does not work, so we + ;; remember the position. + (let ((pos (point))) + (debbugs-gnu-show-reports debbugs-gnu-current-widget) + (goto-char pos))) + +(defvar debbugs-gnu-sort-state 'number) + +(define-derived-mode debbugs-gnu-mode tabulated-list-mode "Debbugs" "Major mode for listing bug reports. All normal editing commands are switched off. -\\ +\\ The following commands are available: -\\{debbugs-mode-map}" - (interactive) - (kill-all-local-variables) - (setq major-mode 'debbugs-mode) - (setq mode-name "Debbugs") - (use-local-map debbugs-mode-map) - (set (make-local-variable 'debbugs-sort-state) +\\{debbugs-gnu-mode-map}" + (set (make-local-variable 'debbugs-gnu-sort-state) 'number) + (setq tabulated-list-format [("Id" 5 debbugs-gnu-sort-id) + ("State" 20 debbugs-gnu-sort-state) + ("Submitter" 25 t) + ("Title" 10 debbugs-gnu-sort-title)]) + (setq tabulated-list-sort-key (cons "Id" nil)) + (setq tabulated-list-printer 'debbugs-gnu-print-entry) (buffer-disable-undo) (setq truncate-lines t) (setq buffer-read-only t)) -(defvar debbugs-state-preference - '((debbugs-new . 1) - (debbugs-stale . 2) - (debbugs-handled . 3) - (debbugs-done . 4))) - -(defun debbugs-toggle-sort () +(defun debbugs-gnu-sort-id (s1 s2) + (< (cdr (assq 'id (car s1))) + (cdr (assq 'id (car s2))))) + +(defconst debbugs-gnu-state-preference + '((debbugs-gnu-new . 1) + (debbugs-gnu-stale . 2) + (debbugs-gnu-handled . 3) + (debbugs-gnu-done . 4) + (debbugs-gnu-pending . 5))) + +(defun debbugs-gnu-get-state-preference (face-string) + (or (cdr (assq (get-text-property 0 'face face-string) + debbugs-gnu-state-preference)) + 10)) + +(defconst debbugs-gnu-severity-preference + '(("serious" . 1) + ("important" . 2) + ("normal" . 3) + ("minor" . 4) + ("wishlist" . 5))) + +(defun debbugs-gnu-get-severity-preference (state) + (or (cdr (assoc (cdr (assq 'severity state)) + debbugs-gnu-severity-preference)) + 10)) + +(defun debbugs-gnu-sort-state (s1 s2) + (let ((id1 (cdr (assq 'id (car s1)))) + (age1 (debbugs-gnu-get-state-preference (aref (nth 1 s1) 1))) + (id2 (cdr (assq 'id (car s2)))) + (age2 (debbugs-gnu-get-state-preference (aref (nth 1 s2) 1)))) + (cond + ;; Tagged bugs go to the end. + ((and (not (memq id1 debbugs-gnu-local-tags)) + (memq id2 debbugs-gnu-local-tags)) + t) + ((and (memq id1 debbugs-gnu-local-tags) + (not (memq id2 debbugs-gnu-local-tags))) + nil) + ;; Then, we check the age of the bugs. + ((< age1 age2) + t) + ((> age1 age2) + nil) + ;; If they have the same age, we check for severity. + ((< (debbugs-gnu-get-severity-preference (car s1)) + (debbugs-gnu-get-severity-preference (car s2))) + t) + (t nil)))) + +(defun debbugs-gnu-sort-title (s1 s2) + (let ((owner (if (cdr (assq 'owner (car s1))) + (car (mail-header-parse-address + (decode-coding-string (cdr (assq 'owner (car s1))) + 'utf-8)))))) + (and (stringp owner) + (string-equal owner user-mail-address)))) + +(defun debbugs-gnu-toggle-sort () "Toggle sorting by age and by state." (interactive) - (beginning-of-line) - (let ((buffer-read-only nil) - (current-bug (and (not (eobp)) - (buffer-substring (point) (+ (point) 5))))) - (goto-char (point-min)) - (setq debbugs-sort-state - (if (eq debbugs-sort-state 'number) - 'state - 'number)) - (sort-subr - nil (lambda () (forward-line 1)) 'end-of-line - (lambda () - (if (eq debbugs-sort-state 'number) - (string-to-number (buffer-substring (point) (+ (point) 5))) - (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 (concat "^" current-bug) nil t)))) - -(defvar debbugs-bug-number nil) - -(defun debbugs-current-id () - (cdr (assq 'id (get-text-property (line-beginning-position) - 'debbugs-status)))) - -(defun debbugs-select-report (id) + (if (eq debbugs-gnu-sort-state 'number) + (progn + (setq debbugs-gnu-sort-state 'state) + (setq tabulated-list-sort-key (cons "Id" nil))) + (setq debbugs-gnu-sort-state 'number) + (setq tabulated-list-sort-key (cons "State" nil))) + (tabulated-list-init-header) + (tabulated-list-print)) + +(defun debbugs-gnu-toggle-tag () + "Toggle tag of the report in the current line." + (interactive) + (save-excursion + (beginning-of-line) + (let ((inhibit-read-only t) + (id (debbugs-gnu-current-id))) + (if (memq id debbugs-gnu-local-tags) + (progn + (setq debbugs-gnu-local-tags (delq id debbugs-gnu-local-tags)) + (put-text-property (point) (+ (point) 5) 'face 'default)) + (add-to-list 'debbugs-gnu-local-tags id) + (put-text-property + (+ (point) (- 5 (length (number-to-string id)))) (+ (point) 5) + 'face 'debbugs-gnu-tagged)))) + (debbugs-gnu-dump-persistency-file)) + +(defun debbugs-gnu-toggle-suppress-done () + "Suppress bugs marked as done." + (interactive) + (widget-put debbugs-gnu-current-widget :suppress-done + (not (widget-get debbugs-gnu-current-widget :suppress-done))) + (tabulated-list-init-header) + (tabulated-list-print)) + +(defvar debbugs-gnu-bug-number nil) +(defvar debbugs-gnu-subject nil) + +(defun debbugs-gnu-current-id (&optional noerror) + (or (cdr (assq 'id (debbugs-gnu-current-status))) + (and (not noerror) + (error "No bug on the current line")))) + +(defun debbugs-gnu-current-status () + (get-text-property (line-beginning-position) 'tabulated-list-id)) + +(defun debbugs-gnu-display-status (status) + "Display the status of the report on the current line." + (interactive (list (debbugs-gnu-current-status))) + (pop-to-buffer "*Bug Status*") + (erase-buffer) + (pp status (current-buffer)) + (goto-char (point-min))) + +(defun debbugs-gnu-select-report () "Select the report on the current line." - (interactive (list (debbugs-current-id))) - (if (null id) - ;; We go to another buffer. - (widget-button-press (point)) - ;; We open the report messages. + (interactive) + ;; We open the report messages. + (let* ((status (debbugs-gnu-current-status)) + (id (cdr (assq 'id status))) + (merged (cdr (assq 'mergedwith status)))) (gnus-read-ephemeral-emacs-bug-group - id (cons (current-buffer) - (current-window-configuration))) + (cons id (if (listp merged) + merged + (list merged))) + (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)))) + (set (make-local-variable 'debbugs-gnu-bug-number) id) + (set (make-local-variable 'debbugs-gnu-subject) + (format "Re: bug#%d: %s" id (cdr (assq 'subject status)))) + (debbugs-gnu-summary-mode 1)))) -(defvar debbugs-summary-mode-map +(defvar debbugs-gnu-summary-mode-map (let ((map (make-sparse-keymap))) - (define-key map "C" 'debbugs-send-control-message) + (define-key map "C" 'debbugs-gnu-send-control-message) map)) -(define-minor-mode debbugs-summary-mode +(defvar gnus-posting-styles) + +(define-minor-mode debbugs-gnu-summary-mode "Minor mode for providing a debbugs interface in Gnus summary buffers. -\\{debbugs-summary-mode-map}" - :lighter " Debbugs" :keymap debbugs-summary-mode-map +\\{debbugs-gnu-summary-mode-map}" + :lighter " Debbugs" :keymap debbugs-gnu-summary-mode-map (set (make-local-variable 'gnus-posting-styles) - '((".*" + `((".*" (eval (with-current-buffer gnus-article-copy (set (make-local-variable 'message-prune-recipient-rules) '((".*@debbugs.*" "emacs-pretest-bug") - (".*@debbugs.*" "bug-gnu-emacs"))) + (".*@debbugs.*" "bug-gnu-emacs") + ("[0-9]+@debbugs.*" "submit@debbugs.gnu.org") + ("[0-9]+@debbugs.*" "quiet@debbugs.gnu.org"))) (set (make-local-variable 'message-alter-recipients-function) (lambda (address) (if (string-match "\\([0-9]+\\)@donarmstrong" (car address)) (let ((new (format "%s@debbugs.gnu.org" (match-string 1 (car address))))) (cons new new)) - address))))))))) + address))) + ;; `gnus-posting-styles' is eval'ed after + ;; `message-simplify-subject'. So we cannot use m-s-s. + (setq subject ,debbugs-gnu-subject))))))) -(defun debbugs-send-control-message (message) +(defun debbugs-gnu-send-control-message (message &optional reverse) "Send a control message for the current bug report. You can set the severity or add a tag, or close the report. If you use the special \"done\" MESSAGE, the report will be marked as -fixed, and then closed." +fixed, and then closed. + +If given a prefix, and given a tag to set, the tag will be +removed instead." (interactive (list (completing-read "Control message: " - '("important" "normal" "minor" "wishlist" - "done" + '("serious" "important" "normal" "minor" "wishlist" + "done" "donenotabug" "donewontfix" "doneunreproducible" "unarchive" "reopen" "close" "merge" "forcemerge" - "patch" "wontfix" "moreinfo" "unreproducible" "fixed" "notabug") - nil t))) - (let* ((id debbugs-bug-number) ; Set on group entry. + "owner" "noowner" + "invalid" + "patch" "wontfix" "moreinfo" "unreproducible" "fixed" "notabug" + "pending" "help" "security" "confirmed") + nil t) + current-prefix-arg)) + (let* ((id (or debbugs-gnu-bug-number ; Set on group entry. + (debbugs-gnu-current-id))) (version (when (member message '("close" "done")) (read-string @@ -352,27 +719,34 @@ fixed, and then closed." (format "Subject: control message for bug #%d\n" id) "\n" (cond - ((member message '("unarchive" "reopen")) + ((member message '("unarchive" "reopen" "noowner")) (format "%s %d\n" message id)) ((member message '("merge" "forcemerge")) (format "%s %d %s\n" message id (read-string "Merge with bug #: "))) + ((equal message "owner") + (format "owner %d !\n" id)) ((equal message "close") (format "close %d %s\n" id version)) ((equal message "done") (format "tags %d fixed\nclose %d %s\n" id id version)) - ((member message '("important" "normal" "minor" "wishlist")) + ((member message '("donenotabug" "donewontfix" + "doneunreproducible")) + (format "tags %d %s\nclose %d\n" id (substring message 4) id)) + ((member message '("serious" "important" "normal" + "minor" "wishlist")) (format "severity %d %s\n" id message)) + ((equal message "invalid") + (format "tags %d notabug\ntags %d wontfix\nclose %d\n" + id id id)) (t - (format "tags %d %s\n" id message)))) + (format "tags %d%s %s\n" + id (if reverse " -" "") + message)))) (funcall send-mail-function)))) (provide 'debbugs-gnu) ;;; TODO: -;; * Widget-oriented bug overview like webDDTs. -;; * Actions on bugs. -;; * Integration into gnus (nnir). - ;;; debbugs-gnu.el ends here