+(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-query nil
+ "The query object of the current search.
+It will be applied server-side, when calling `debbugs-get-bugs'.
+It has the same format as `debbugs-gnu-default-suppress-bugs'.")
+
+(defvar debbugs-gnu-current-filter nil
+ "The filter object for the current search.
+It will be applied client-side, when parsing the results of
+`debbugs-get-status'. It has a similar format as
+`debbugs-gnu-default-suppress-bugs'. In case of keys representing
+a date, value is the cons cell \(BEFORE . AFTER\).")
+
+(defun debbugs-gnu-calendar-read (prompt acceptable &optional initial-contents)
+ "Return a string read from the minibuffer.
+Derived from `calendar-read'."
+ (let ((value (read-string prompt initial-contents)))
+ (while (not (funcall acceptable value))
+ (setq value (read-string prompt initial-contents)))
+ value))
+
+(defconst debbugs-gnu-phrase-prompt
+ (propertize
+ "Enter search phrase: "
+ 'help-echo "\
+The search phrase contains words to be searched for, combined by
+operators like AND, ANDNOT and OR. If there is no operator
+between the words, AND is used by default. The phrase can also
+be empty, in this case only the following attributes are used for
+search."))
+
+;;;###autoload
+(defun debbugs-gnu-search ()
+ "Search for Emacs bugs interactively.
+Search arguments are requested interactively. The \"search
+phrase\" is used for full text search in the bugs database.
+Further key-value pairs are requested until an empty key is
+returned. If a key cannot be queried by a SOAP request, it is
+marked as \"client-side filter\"."
+ (interactive)
+
+ (unwind-protect
+ (let ((date-format "\\([[:digit:]]\\{4\\}\\)-\\([[:digit:]]\\{1,2\\}\\)-\\([[:digit:]]\\{1,2\\}\\)")
+ key val1 val2 phrase severities packages archivedp)
+
+ ;; Check for the phrase.
+ (setq phrase (read-string debbugs-gnu-phrase-prompt))
+ (if (zerop (length phrase))
+ (setq phrase nil)
+ (add-to-list 'debbugs-gnu-current-query (cons 'phrase phrase)))
+
+ ;; The other queries.
+ (catch :finished
+ (while t
+ (setq key (completing-read
+ "Enter attribute: "
+ (if phrase
+ '("severity" "package" "tags" "submitter" "date"
+ "subject" "status")
+ '("severity" "package" "archive" "src" "tag"
+ "owner" "submitter" "maint" "correspondent"
+ "date" "log_modified" "last_modified"
+ "found_date" "fixed_date" "unarchived"
+ "subject" "done" "forwarded" "msgid" "summary"))
+ nil t))
+ (cond
+ ;; Server-side queries.
+ ((equal key "severity")
+ (setq
+ severities
+ (completing-read-multiple
+ "Enter severities: "
+ (mapcar
+ 'cadr (cdr (get 'debbugs-gnu-default-severities 'custom-type)))
+ nil t
+ (mapconcat 'identity debbugs-gnu-default-severities ","))))
+
+ ((equal key "package")
+ (setq
+ packages
+ (completing-read-multiple
+ "Enter packages: "
+ (mapcar
+ 'cadr (cdr (get 'debbugs-gnu-default-packages 'custom-type)))
+ nil t (mapconcat 'identity debbugs-gnu-default-packages ","))))
+
+ ((equal key "archive")
+ ;; We simplify, by assuming just archived bugs are requested.
+ (setq archivedp t))
+
+ ((member key '("src" "tag" "tags"))
+ (setq val1 (read-string (format "Enter %s: " key)))
+ (when (not (zerop (length val1)))
+ (add-to-list
+ 'debbugs-gnu-current-query (cons (intern key) val1))))
+
+ ((member key '("owner" "submitter" "maint" "correspondent"))
+ (setq val1 (read-string "Enter email address: "))
+ (when (not (zerop (length val1)))
+ (add-to-list
+ 'debbugs-gnu-current-query (cons (intern key) val1))))
+
+ ((equal key "status")
+ (setq
+ val1
+ (completing-read "Enter status: " '("done" "forwarded" "open")))
+ (when (not (zerop (length val1)))
+ (add-to-list
+ 'debbugs-gnu-current-query (cons (intern key) val1))))
+
+ ;; Client-side filters.
+ ((member key '("date" "log_modified" "last_modified"
+ "found_date" "fixed_date" "unarchived"))
+ (setq val1
+ (debbugs-gnu-calendar-read
+ (format "Enter %s before YYYY-MM-DD%s: "
+ key (if phrase "" " (client-side filter)"))
+ (lambda (x)
+ (string-match (concat "^\\(" date-format "\\|\\)$") x))))
+ (if (string-match date-format val1)
+ (setq val1 (floor
+ (float-time
+ (encode-time
+ 0 0 0
+ (string-to-number (match-string 3 val1))
+ (string-to-number (match-string 2 val1))
+ (string-to-number (match-string 1 val1))))))
+ (setq val1 nil))
+ (setq val2
+ (debbugs-gnu-calendar-read
+ (format "Enter %s after YYYY-MM-DD%s: "
+ key (if phrase "" " (client-side filter)"))
+ (lambda (x)
+ (string-match (concat "^\\(" date-format "\\|\\)$") x))))
+ (if (string-match date-format val2)
+ (setq val2 (floor
+ (float-time
+ (encode-time
+ 0 0 0
+ (string-to-number (match-string 3 val2))
+ (string-to-number (match-string 2 val2))
+ (string-to-number (match-string 1 val2))))))
+ (setq val2 nil))
+ (when (or val1 val2)
+ (add-to-list
+ (if phrase
+ 'debbugs-gnu-current-query 'debbugs-gnu-current-filter)
+ (cons (intern key) (cons val1 val2)))))
+
+ ((not (zerop (length key)))
+ (setq val1
+ (funcall
+ (if phrase 'read-string 'read-regexp)
+ (format "Enter %s%s"
+ key (if phrase ": " " (client-side filter)"))))
+ (when (not (zerop (length val1)))
+ (add-to-list
+ (if phrase
+ 'debbugs-gnu-current-query 'debbugs-gnu-current-filter)
+ (cons (intern key) val1))))
+
+ ;; The End.
+ (t (throw :finished nil)))))
+
+ ;; Do the search.
+ (debbugs-gnu severities packages archivedp))
+
+ ;; Reset query and filter.
+ (setq debbugs-gnu-current-query nil
+ debbugs-gnu-current-filter nil)))
+
+;;;###autoload
+(defun debbugs-gnu (severities &optional packages archivedp suppress)